"""Check public file hashes and recompute descriptive counts (Python 3).

Run beside provenance.json and allocated-outcomes.csv. This checks the published
summaries, not signatures or private source records. No network or model calls.
"""
import argparse
import csv
import hashlib
import io
import json
from pathlib import Path

MODELS = ('gpt-6-sol', 'gpt-6.1-sol')


def summarize(data):
    records = {}
    for row in csv.DictReader(io.StringIO(data.decode('utf-8'))):
        key = (row['task_id'], row['replica'], row['model'])
        if key in records or row['model'] not in MODELS:
            raise ValueError('Duplicate identity or unexpected model')
        if row['quality'] and float(row['quality']) not in (0, 1):
            raise ValueError('Invalid quality value')
        records[key] = row
    primary = {}
    for (task, replica, model), row in records.items():
        if replica == '0' and row['quality']:
            primary.setdefault(task, {})[model] = row
    pairs = {task: rows for task, rows in primary.items() if len(rows) == 2}
    outcomes = {'both_passed': 0, 'gain': 0, 'loss': 0, 'both_failed': 0}
    labels = {(1, 1): 'both_passed', (0, 1): 'gain', (1, 0): 'loss', (0, 0): 'both_failed'}
    fewer = {key: 0 for key in ('observed_total_tokens', 'elapsed_seconds', 'repository_tool_calls')}
    changes, repeats = [], []
    for task, rows in sorted(pairs.items()):
        grades = tuple(int(float(rows[m]['quality'])) for m in MODELS)
        outcome = labels[grades]
        outcomes[outcome] += 1
        if outcome == 'both_passed':
            for key in fewer:
                values = [float(rows[m][key]) for m in MODELS]
                if not all(0 < value < float('inf') for value in values):
                    raise ValueError('Missing or invalid shared-success measurement')
                fewer[key] += values[1] < values[0]
        if outcome in ('gain', 'loss'):
            changes.append({'task_id': task, 'outcome': outcome, 'category': rows[MODELS[0]]['category']})
            series = [[records.get((task, str(i), m), {}).get('quality', '') for i in range(3)] for m in MODELS]
            if all(value for values in series for value in values):
                repeats.append({'task_id': task, 'first_result': outcome,
                                'grades_in_order': [[int(float(v)) for v in values] for values in series]})
    return {'complete_pairs': len(pairs), 'outcomes': outcomes,
            'fewer_on_shared_successes': fewer, 'changed_tasks': changes,
            'changed_tasks_with_three_attempts': repeats}


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument('--directory', type=Path, default=Path(__file__).resolve().parent)
    parser.add_argument('--write-changes', type=Path, help='Write a new CSV of changed tasks; existing files are preserved')
    args = parser.parse_args()
    manifest = json.loads((args.directory / 'provenance.json').read_text())
    data = (args.directory / 'allocated-outcomes.csv').read_bytes()
    expected = manifest['public_files']['allocated-outcomes.csv']
    if len(data) != expected['bytes'] or hashlib.sha256(data).hexdigest() != expected['sha256']:
        raise ValueError('CSV does not match the supplied provenance manifest')
    result = summarize(data)
    if args.write_changes:
        with args.write_changes.open('x', newline='') as output:
            writer = csv.DictWriter(output, fieldnames=['task_id', 'outcome', 'category'])
            writer.writeheader()
            writer.writerows(result['changed_tasks'])
    print(json.dumps(result, indent=2))


if __name__ == '__main__':
    main()
