#!/usr/bin/env python3
"""Independent Event Wheel v1 verifier. Python standard library only.
Usage: python3 verify-draw.py downloaded-receipt.json
An original saved pre-spin commitment anchors timing; a receipt alone verifies math.
"""
import hashlib
import hmac
import json
import re
import struct
import sys
from pathlib import Path

ALGORITHM = 'event-wheel-hmac-sha256-v1'


def encode(value):
    return json.dumps(value, ensure_ascii=False, separators=(',', ':')).encode('utf-8')


def verify(receipt):
    assert receipt['algorithm'] == ALGORITHM, 'Unsupported algorithm'
    server_seed = receipt['server_seed']
    client_seed = receipt['client_seed']
    assert re.fullmatch('[a-f0-9]{64}', server_seed), 'Invalid server seed'
    assert re.fullmatch('[a-f0-9]{64}', client_seed), 'Invalid client seed'
    seed_bytes = bytes.fromhex(server_seed)
    assert hashlib.sha256(seed_bytes).hexdigest() == receipt['commitment'], 'Server seed commitment mismatch'
    configuration = receipt['configuration']
    assert 1 <= len(configuration) <= 128, 'Invalid prize count'
    assert len({p['id'] for p in configuration}) == len(configuration), 'Duplicate prize ID'
    for prize in configuration:
        assert type(prize['weight']) is int and 1 <= prize['weight'] <= 100, 'Invalid weight'
        inventory = prize['inventory']
        assert inventory is None or (type(inventory) is int and inventory >= 0), 'Invalid inventory'
    canonical = [[p['id'], p['name'], p['color'], p['weight'], p['inventory']] for p in configuration]
    config_hash = hashlib.sha256(encode(canonical)).hexdigest()
    assert config_hash == receipt['config_hash'], 'Configuration commitment mismatch'
    eligible = [p for p in configuration if p['inventory'] is None or p['inventory'] > 0]
    total = sum(p['weight'] for p in eligible)
    assert total > 0, 'No eligible prizes'
    limit = (2**32 // total) * total
    for block in range(1024):
        message = encode([ALGORITHM, receipt['context'], receipt['draw_id'], client_seed, config_hash, block])
        digest = hmac.new(seed_bytes, message, hashlib.sha256).digest()
        for word_index, word in enumerate(struct.unpack('>8I', digest)):
            if word >= limit:
                continue
            ticket = word % total
            cursor = ticket
            for prize in eligible:
                cursor -= prize['weight']
                if cursor < 0:
                    winner = prize
                    break
            expected = {'ticket': ticket, 'word_index': word_index, 'block': block,
                        'total_weight': total, 'digest': digest.hex(), 'winning_slice_id': winner['id']}
            for key, value in expected.items():
                assert receipt[key] == value, f'Result mismatch: {key}'
            original = receipt.get('observed_commitment')
            if original:
                for key in ['draw_id', 'commitment', 'config_hash', 'context']:
                    assert original[key] == receipt[key], f'Original pre-spin commitment mismatch: {key}'
            return winner['name'], bool(original)
    raise ValueError('No accepted HMAC word')


def main():
    if len(sys.argv) != 2:
        raise SystemExit('Usage: python3 verify-draw.py receipt.json')
    path = Path(sys.argv[1])
    if path.stat().st_size > 100_000:
        raise SystemExit('Receipt is too large (maximum 100 KB).')
    try:
        receipt = json.loads(path.read_text())
        winner, anchored = verify(receipt)
    except (AssertionError, ValueError, KeyError, TypeError) as error:
        raise SystemExit(f'FAIL: {error}') from error
    print(f'PASS: {winner} — result matches the committed seeds and weighted draw.')
    print('Original saved pre-spin commitment matches.' if anchored else 'No saved pre-spin commitment: mathematical reproduction only.')
    if receipt.get('status') == 'aborted':
        print('ABORTED DRAW: no prize was awarded.')


if __name__ == '__main__':
    main()
