"""Synthetic detector coverage exercise. Python 3.9+, no packages or network.
Run: python3 benchmark.py --output results.json
Fixtures are hand-authored, not production data or a vendor benchmark.
"""
import argparse
import json
from pathlib import Path


def duplicate_reads(calls):
    seen = set()
    for call in calls:
        if not call.get('read_only') or call.get('purpose') == 'poll':
            continue
        if not call.get('ok'):
            continue
        key = (call['tool'], call['scope'], call['version'], call['purpose'],
               json.dumps(call['args'], sort_keys=True), call['result_hash'])
        if key in seen:
            return True
        seen.add(key)
    return False


def contract_findings(run):
    findings = []
    if run.get('execution_error'):
        findings.append('execution_error')
    if run.get('business_error'):
        findings.append('business_error')
    if duplicate_reads(run.get('calls', [])):
        findings.append('duplicate_read')
    claim = run.get('completion_claim')
    if claim:
        receipt = run.get('receipt')
        if not receipt or any(receipt.get(k) != claim[k] for k in ('action', 'entity', 'tenant')) or receipt.get('state') != 'completed':
            findings.append('unsupported_completion')
    if 'answer_days' in run and run['answer_days'] != run.get('retrieved_days'):
        findings.append('source_contradiction')
    if run.get('source_version') and run['source_version'] != run.get('required_version'):
        findings.append('stale_source')
    if run.get('result_tenant') and run['result_tenant'] != run.get('tenant'):
        findings.append('wrong_tenant')
    if run.get('handoff_required') and not run.get('handoff_accepted'):
        findings.append('missing_handoff')
    return findings


def classify(run, detector):
    # Incomplete telemetry cannot establish a clean or failed outcome.
    if not run['evidence_complete']:
        return None
    if detector == 'exception_only':
        return bool(run.get('execution_error'))
    if detector == 'exception_or_call_count':
        return bool(run.get('execution_error')) or len(run.get('calls', [])) > 2
    return bool(contract_findings(run))


def measure(cases, detector):
    counts = dict(tp=0, fp=0, tn=0, fn=0, unknown=0)
    rows = []
    for case in cases:
        predicted = classify(case, detector)
        truth = case['failure']
        rows.append({'id': case['id'], 'failure': truth, 'predicted_failure': predicted})
        if predicted is None or truth is None:
            counts['unknown'] += 1
        else:
            counts['tp' if predicted and truth else 'fp' if predicted else 'fn' if truth else 'tn'] += 1
    counts['precision'] = counts['tp'] / (counts['tp'] + counts['fp']) if counts['tp'] + counts['fp'] else None
    counts['recall'] = counts['tp'] / (counts['tp'] + counts['fn']) if counts['tp'] + counts['fn'] else None
    return {'counts': counts, 'cases': rows}


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('--output', type=Path)
    args = parser.parse_args()
    cases = json.loads(Path(__file__).with_name('fixtures.json').read_text())
    results = {'scope': '17 hand-authored synthetic cases; no model, vendor, or live API calls',
               'detectors': {name: measure(cases, name) for name in ('exception_only', 'exception_or_call_count', 'contract_checks')}}
    output = json.dumps(results, indent=2) + '\n'
    if args.output:
        args.output.write_text(output)
    print(output)

if __name__ == '__main__':
    main()
