#!/usr/bin/env python3
"""Check published synthetic evidence, without executing the OpenAuditor engine.

Uses only Python's standard library. Reads files and prints a report; writes
nothing. This checks recorded outcomes and internal consistency, not the engine
implementation, authenticity of the measurements or production reliability.
"""
import argparse
from collections import Counter
from decimal import Decimal
import hashlib
import json
from pathlib import Path
import zipfile

PINNED_REVISION = '6205721f91352039de5ce861c1c94d15d9903c7d'
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--evidence', type=Path, default=Path(__file__).resolve().parent,
                    help='Directory containing the published evidence files.')
args = parser.parse_args()
base = args.evidence.resolve()
checks = []


def require(name, condition):
    checks.append({'check': name, 'passed': bool(condition)})
    if not condition:
        raise AssertionError(name)


def read(name):
    return json.loads((base / name).read_text())


def digest_request(request):
    # Public input_sha256 uses canonical JSON, not whitespace from the download.
    encoded = json.dumps(request, sort_keys=True, separators=(',', ':'),
                         ensure_ascii=False, allow_nan=False).encode('utf-8')
    return hashlib.sha256(encoded).hexdigest()


requests = read('inputs.json')
rows = read('results.json')
manifest = read('manifest.json')
local_checks = read('checks.json')
indexed = {(r['scenario'], r['item_id']): r for r in rows}
require('33 recorded scenarios', len(requests) == 33 == manifest['scenarios'])
require('40 unique focused results', len(rows) == len(indexed) == 40 == manifest['focused_results'])
require('pinned recorded revision', manifest['revision'] == PINNED_REVISION)
require('synthetic provenance', manifest['synthetic'] is True)
require('recorded no-model provenance', manifest['model'] == 'none' and manifest['model_calls'] == 0)
require('297 recorded local checks passed', len(local_checks) == 297 == manifest['checks']
        and all(c['passed'] is True for c in local_checks) and manifest['failed_checks'] == 0)
for name, request in requests.items():
    require(name + ': questions disabled in recorded input', request['options'] == {'questions': 'none'})
for row in rows:
    request = requests[row['scenario']]
    require(row['scenario'] + ': input hash', row['input_sha256'] == digest_request(request))
    require(row['scenario'] + ': supplied scope', row['scope']['evidence'] == 'supplied')

expected = {}


def expect(scenario, item_id, duplicate, verdict, matched=None):
    row = indexed[(scenario, item_id)]
    key = (scenario, item_id)
    expected[key] = (duplicate, verdict, matched)
    require(scenario + ':' + item_id + ': duplicate assertion',
            row['assertions']['not_duplicate'] == duplicate)
    require(scenario + ':' + item_id + ': overall verdict', row['verdict'] == verdict)
    if matched is not None:
        require(scenario + ':' + item_id + ': matched assertion', row['assertions']['matched'] == matched)


pair = ('ERP-ROW-A', 'ERP-ROW-B')
for history in ('omitted', 'empty', 'full'):
    for item_id in pair:
        expect('joint-' + history, item_id, 'fails', 'false')
        expect('split-audit-' + history + '-' + item_id, item_id,
               'fails' if history == 'full' else 'holds', 'false' if history == 'full' else 'uncertain')
        expect('single-verify-' + history + '-' + item_id, item_id,
               {'omitted': 'insufficient_evidence', 'empty': 'holds', 'full': 'fails'}[history],
               'false' if history == 'full' else 'uncertain')
        for prefix in ('joint-', 'split-audit-', 'single-verify-'):
            scenario = prefix + history + ('' if prefix == 'joint-' else '-' + item_id)
            records = requests[scenario]['context']['records']
            require(scenario + ': declared history shape',
                    ('invoice' not in records if history == 'omitted' else
                     records['invoice'] == [] if history == 'empty' else
                     {r['id'] for r in records['invoice']} == set(pair)))
expect('single-peer-only-history', pair[0], 'fails', 'false')
expect('split-after-joint-same-runner', pair[0], 'holds', 'uncertain')
expect('rolling-history-first', pair[0], 'holds', 'uncertain')
expect('rolling-history-second', pair[1], 'fails', 'false')
for item_id in pair:
    for scenario in ('joint-reversed', 'internal-chunk-34-items', 'vendor-partition-pair'):
        expect(scenario, item_id, 'fails', 'false')
for group, item_id in ((1, pair[0]), (2, pair[1])):
    expect(f'external-chunk-{group}-no-history', item_id, 'holds', 'uncertain')
    expect(f'external-chunk-{group}-full-history', item_id, 'fails', 'false')

# Verify the same business rows really occur across the different request shapes.
for item_id in pair:
    originals = []
    for row in rows:
        if row['item_id'] != item_id:
            continue
        request = requests[row['scenario']]
        items = request.get('items', [request.get('item')])
        originals.append(next(item for item in items if item['id'] == item_id))
        require(row['scenario'] + ': other invoice controls hold', all(
            row['assertions'][key] == 'holds' for key in ('arithmetic', 'vendor_active', 'po_present')))
        require(row['scenario'] + ': invoice judgment not asked',
                row['assertions']['goods_specific'] == 'not_asked')
    require(item_id + ': unchanged input across scenarios', all(item == originals[0] for item in originals))
require('internal group has 34 items', len(requests['internal-chunk-34-items']['items']) == 34)
require('external groups have 32 and 2 items',
        [len(requests[f'external-chunk-{group}-no-history']['items']) for group in (1, 2)] == [32, 2])

expect('payments-joint', 'PAY-A', 'holds', 'true', 'holds')
expect('payments-joint', 'PAY-B', 'holds', 'false', 'fails')
for history in ('omitted', 'empty', 'full'):
    for item_id in ('PAY-A', 'PAY-B'):
        fails = history == 'full' and item_id == 'PAY-B'
        expect('payments-split-' + history + '-' + item_id, item_id,
               'holds', 'false' if fails else 'true', 'fails' if fails else 'holds')
require('every published focused result was checked', set(expected) == set(indexed))
payment_request = requests['payments-joint']
billed = Decimal(payment_request['context']['records']['invoice'][0]['amount'])
payments = {item['id']: Decimal(item['data']['amount']) for item in payment_request['items']}
require('recorded payment arithmetic', billed == 1000 and payments == {'PAY-A': Decimal(600), 'PAY-B': Decimal(500)}
        and billed - payments['PAY-A'] == 400 and sum(payments.values()) - billed == 100)
for scenario, open_balance, prior in [('payments-joint', '400.00', '600.00'),
                                    ('payments-split-omitted-PAY-B', '1000.00', '0'),
                                    ('payments-split-empty-PAY-B', '1000.00', '0'),
                                    ('payments-split-full-PAY-B', '400.00', '600.00')]:
    facts = indexed[(scenario, 'PAY-B')]['matched_facts']
    require(scenario + ': recorded balance facts',
            facts['open'] == open_balance and facts['paid_elsewhere'] == prior and facts['billed'] == '1000.00')

# Full raw responses are available in the archive or after extracting it.
archive = zipfile.ZipFile(base / 'evidence.zip') if (base / 'evidence.zip').is_file() else None
full_response_count = 0
for name, request in requests.items():
    relative = 'results/' + name + '.json'
    if archive is not None:
        response = json.loads(archive.read(relative))
        recorded_input = json.loads(archive.read('inputs/' + name + '.json'))
    else:
        require('full response file: ' + name, (base / relative).is_file())
        response = json.loads((base / relative).read_text())
        recorded_input = json.loads((base / 'inputs' / (name + '.json')).read_text())
    require(name + ': raw input agrees with combined input', recorded_input == request)
    outputs = response['items'] if 'items' in request else [response]
    input_items = request['items'] if 'items' in request else [request['item']]
    require(name + ': output item coverage',
            [r['item']['id'] for r in outputs] == [item['id'] for item in input_items])
    for output in outputs:
        require(name + ':' + output['item']['id'] + ': recorded zero model usage',
                output['usage'] == {'questions_asked': 0, 'questions_cached': 0, 'model_ms': 0})
        require(name + ': full response input hash', output['input_sha256'] == digest_request(request))
        key = (name, output['item']['id'])
        if key in indexed:
            row = indexed[key]
            require(name + ': summary agrees with complete response',
                    row['verdict'] == output['verdict'] and row['assertions'] ==
                    {a['id']: a['outcome'] for a in output['assertions']})
    full_response_count += 1
if archive is not None:
    archive.close()
print(json.dumps({'evidence_checks': len(checks), 'failed': 0,
                  'recorded_scenarios': len(requests), 'focused_results': len(rows),
                  'full_responses_checked': full_response_count,
                  'interpretation': 'Published recorded evidence is internally consistent. '
                  'This does not rerun the engine or authenticate the original experiment.'}, indent=2))
