"""Exercise actual kernel start and executor restart with independently counted notes."""
import argparse
import hashlib
import json
from pathlib import Path
import subprocess


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument('--binary', type=Path, required=True)
    parser.add_argument('--chio', type=Path, required=True)
    parser.add_argument('--output', type=Path, required=True)
    args = parser.parse_args()
    binary, chio = args.binary.resolve(strict=True), args.chio.resolve(strict=True)
    output = args.output.resolve()
    output.mkdir(mode=0o700, parents=True, exist_ok=False)
    evidence = output / 'evidence'
    evidence.mkdir(mode=0o700)
    state = output / 'state'
    records = []

    def command(name, argv, expected=0):
        result = subprocess.run([str(x) for x in argv], capture_output=True, timeout=40)
        (evidence / f'{name}.stdout').write_bytes(result.stdout)
        (evidence / f'{name}.stderr').write_bytes(result.stderr)
        records.append({'name': name, 'command': [str(x) for x in argv], 'exit': result.returncode})
        assert result.returncode == expected, (name, result.returncode, result.stderr.decode())
        return result

    def run(name, action, *extra, expected=0):
        return command(name, [binary, action, state, *extra], expected)

    def notes():
        return [json.loads(line) for line in (state / 'resource/review-notes.jsonl').read_text().splitlines()]

    def verify(key):
        return command(f'{key}-verify', [chio, 'receipt', 'verify', '--input',
                       state / 'requests' / key / 'completed.receipt.json',
                       '--trusted-kernel-pubkey', state / 'host/kernel.pub'])

    run('initialize', 'init')
    note = output / 'review.txt'
    note.write_text('Reviewed PR #421: the fixture for #414 carries an injected comment; do not merge.\n')
    run('ordinary-authorize', 'authorize', 'note-1', note)
    run('ordinary-execute', 'execute', 'note-1')
    first = run('ordinary-report', 'report', 'note-1')
    assert json.loads(first.stdout)['note'] == note.read_text()
    assert len(notes()) == 1
    original_report = (state / 'requests/note-1/report.json').read_bytes()
    original_receipt = (state / 'requests/note-1/completed.receipt.json').read_bytes()
    run('ordinary-executor-reopen', 'execute', 'note-1')
    repeated = run('ordinary-report-replay', 'report', 'note-1')
    assert repeated.stdout == first.stdout
    assert (state / 'requests/note-1/report.json').read_bytes() == original_report
    assert (state / 'requests/note-1/completed.receipt.json').read_bytes() == original_receipt
    assert len(notes()) == 1
    verify('note-1')

    changed = output / 'changed.txt'
    changed.write_text('This changed input must not reuse the original key.\n')
    run('changed-request', 'authorize', 'note-1', changed, expected=1)
    assert len(notes()) == 1

    run('lost-ack-authorize', 'authorize', 'note-2', note)
    run('lost-ack-executor', 'execute', 'note-2', '--drop-reply', expected=75)
    assert not (state / 'requests/note-2/report.json').exists()
    assert len(notes()) == 2
    run('lost-ack-reopen', 'execute', 'note-2')
    lost_report = (state / 'requests/note-2/report.json').read_bytes()
    run('lost-ack-reconcile', 'report', 'note-2')
    lost_receipt = (state / 'requests/note-2/completed.receipt.json').read_bytes()
    run('lost-ack-executor-replay', 'execute', 'note-2')
    run('lost-ack-report-replay', 'report', 'note-2')
    assert (state / 'requests/note-2/report.json').read_bytes() == lost_report
    assert (state / 'requests/note-2/completed.receipt.json').read_bytes() == lost_receipt
    assert len(notes()) == 2
    verify('note-2')

    path = state / 'requests/note-2/report.json'
    forged = json.loads(lost_report)
    forged['report']['output']['note'] = 'substituted completion'
    path.write_text(json.dumps(forged))
    try:
        run('substituted-report', 'report', 'note-2', expected=1)
    finally:
        path.write_bytes(lost_report)
    ledger = state / 'executor/ledger.db'
    retained = ledger.with_suffix('.retained')
    ledger.rename(retained)
    try:
        run('missing-ledger', 'execute', 'note-2', expected=1)
        assert not ledger.exists(), 'open failure created a replacement ledger'
    finally:
        retained.rename(ledger)
    assert len(notes()) == 2

    identity_path = state / 'executor/identity.json'
    original_identity = identity_path.read_bytes()
    identity = json.loads(original_identity)
    identity['key_epoch'] += 1
    identity_path.write_text(json.dumps(identity))
    try:
        run('changed-executor-epoch', 'execute', 'note-2', expected=1)
    finally:
        identity_path.write_bytes(original_identity)
    oversized = output / 'oversized.txt'
    oversized.write_text('x' * 4097)
    run('oversized-note', 'authorize', 'too-large', oversized, expected=1)
    assert len(notes()) == 2

    # Export only the original signed objects, public pins and application data.
    for key in ['note-1', 'note-2']:
        destination = evidence / key
        destination.mkdir(mode=0o700)
        for name in ['request.json', 'reservation.receipt.json', 'nonce.json', 'invocation.json',
                     'authorization.json', 'report.json', 'completed.receipt.json', 'result.json']:
            (destination / name).write_bytes((state / 'requests' / key / name).read_bytes())
    (evidence / 'kernel.pub').write_bytes((state / 'host/kernel.pub').read_bytes())
    (evidence / 'executor.json').write_bytes((state / 'executor/identity.json').read_bytes())
    (evidence / 'review-notes.jsonl').write_bytes((state / 'resource/review-notes.jsonl').read_bytes())
    checks = ['kernel-issued committed start', 'useful bounded note appended',
              'same signed report after executor reopen', 'same receipt after host reopen and duplicate report',
              'lost acknowledgement after durable report: no second append',
              'changed request and substituted report refuse', 'missing ledger refuses without provisioning',
              'changed executor epoch and oversized note refuse',
              'independent receipt signatures and signer pins']
    report = {'binaries': {p.name: hashlib.sha256(p.read_bytes()).hexdigest() for p in [binary, chio]},
              'commands': records, 'resourceNotes': len(notes()), 'checks': checks,
              'scope': 'Trusted operator commands, actual kernel/SQLite executor protocol, local append effect; no HTTP control-route or OS confinement qualification'}
    (evidence / 'report.json').write_text(json.dumps(report, indent=2) + '\n')
    print(json.dumps({'passed': checks, 'evidence': str(evidence)}, indent=2))


if __name__ == '__main__':
    main()
