#!/usr/bin/env python3
"""Educational HMAC chain and separately held checkpoint. NOT production signing."""
import copy
import hashlib
import hmac
import json
import tempfile
from pathlib import Path

KEY = b'public-demonstration-key-not-a-secret'
ZERO = '0' * 64


def mac(record):
    body = {k: v for k, v in record.items() if k != 'mac'}
    canonical = json.dumps(body, sort_keys=True, separators=(',', ':'), ensure_ascii=True).encode()
    return hmac.new(KEY, canonical, hashlib.sha256).hexdigest()


def checkpoint(rows):
    return {'count': len(rows), 'head': rows[-1]['mac'] if rows else ZERO}


def verify(rows, witness=None):
    previous = ZERO
    seen = set()
    for expected, row in enumerate(rows, 1):
        if row['seq'] != expected or row['prev'] != previous or row['id'] in seen:
            return False
        if not hmac.compare_digest(row['mac'], mac(row)):
            return False
        seen.add(row['id'])
        previous = row['mac']
    if witness is not None:
        count = witness['count']
        if len(rows) < count:
            return False
        anchored = rows[count-1]['mac'] if count else ZERO
        if not hmac.compare_digest(anchored, witness['head']):
            return False
    return True


def append(rows, event_id, action, epoch='boot-1', producer_seq=None):
    row = {'seq': len(rows)+1, 'epoch': epoch, 'id': event_id, 'action': action,
           'producer_seq': producer_seq, 'prev': rows[-1]['mac'] if rows else ZERO}
    row['mac'] = mac(row)
    rows.append(row)


def producer_contiguous(rows):
    return [r['producer_seq'] for r in rows] == list(range(1, len(rows)+1))


def main():
    rows = []
    for index, action in enumerate(('proposed', 'allowed', 'execution_receipt'), 1):
        append(rows, f'event-{index}', action, producer_seq=index)
    with tempfile.TemporaryDirectory(prefix='log-source-') as source, tempfile.TemporaryDirectory(prefix='log-witness-') as witness_dir:
        log_path = Path(source) / 'events.json'
        witness_path = Path(witness_dir) / 'checkpoint.json'
        log_path.write_text(json.dumps(rows))
        witness_path.write_text(json.dumps(checkpoint(rows)))
        witness = json.loads(witness_path.read_text())
        cases = {}
        def expect(name, actual, wanted):
            assert actual == wanted, name
            cases[name] = {'accepted': actual, 'expected': wanted}
        expect('original_anchored', verify(rows, witness), True)
        edited = copy.deepcopy(rows)
        edited[1]['action'] = 'different_action'
        expect('edit', verify(edited, witness), False)
        expect('middle_delete', verify([rows[0], rows[2]], witness), False)
        expect('replay_duplicate', verify(rows + [rows[1]], witness), False)
        expect('truncated_without_checkpoint', verify(rows[:-1]), True)
        expect('truncated_with_checkpoint', verify(rows[:-1], witness), False)
        # Fresh state loaded from disk, verified before appending under a new boot ID.
        restarted = json.loads(log_path.read_text())
        assert verify(restarted, witness)
        append(restarted, 'event-4', 'restart_resumed', epoch='boot-2', producer_seq=4)
        expect('restart_from_verified_state', verify(restarted, witness), True)
        reset = []
        append(reset, 'event-4', 'restart_reset_sequence', epoch='boot-2', producer_seq=4)
        expect('restart_lost_state', verify(reset, witness), False)
        dropped = []
        append(dropped, 'event-1', 'proposed', producer_seq=1)
        append(dropped, 'event-3', 'execution_receipt', producer_seq=3)
        expect('producer_drop_chain_only', verify(dropped), True)
        expect('producer_drop_sequence_check', producer_contiguous(dropped), False)
        # No allocated ID or external receipt for the omitted action: chain cannot know.
        never_emitted = []
        append(never_emitted, 'event-1', 'only_action_reported', producer_seq=1)
        expect('never_emitted_invisible', verify(never_emitted), True)
        rewritten = []
        for index in range(1, 4):
            append(rewritten, f'event-{index}', 'rewritten_with_key', producer_seq=index)
        expect('signer_rewrite_without_checkpoint', verify(rewritten), True)
        expect('signer_rewrite_original_checkpoint', verify(rewritten, witness), False)
        compromised = copy.deepcopy(rows)
        append(compromised, 'event-4', 'false_execution_receipt', producer_seq=4)
        expect('signer_forges_after_checkpoint', verify(compromised, witness), True)
        print(json.dumps({'cases': cases, 'checkpoint': witness,
            'limitations': ['Public fixed HMAC key; all readers can forge.',
                'Separate local directories simulate independent custody only.',
                'No protected retention, asymmetric signature, fsync/crash recovery or concurrent writers.',
                'Restart means fresh state deserialization, not a power-loss test.',
                'All actions are labels; no agent or external tool executed.'],
            'cleanup': 'Both temporary directories removed automatically.'}, indent=2))


if __name__ == '__main__':
    main()
