#!/usr/bin/env python3
"""NEW portable stdlib wrapper: audit existing evidence, never train or infer.

The original executed scorer/generator/reporting modules are byte-identical
under executed-source/candidate. This wrapper changes only package discovery
and the scope of integrity checking to the documented public evidence subset.
"""
from __future__ import annotations
import argparse
from collections import Counter
import hashlib
import json
from pathlib import Path, PurePosixPath
import sys

sys.dont_write_bytecode = True
ROOT = Path(__file__).resolve().parent
SOURCE = ROOT / 'executed-source'
CANDIDATE = SOURCE / 'candidate'


def require(value, message):
    if not value:
        raise ValueError(message)


def digest(path):
    return hashlib.sha256(path.read_bytes()).hexdigest()


def child(relative):
    require(isinstance(relative, str) and relative and not PurePosixPath(relative).is_absolute()
            and all(p not in ('', '.', '..') for p in relative.split('/')) and '\\' not in relative,
            'Unsafe package path')
    path = ROOT / relative
    require(not any(p.is_symlink() for p in (path, *path.parents)), 'Symlink not accepted')
    return path


def check_checksums():
    checks = {}
    for line in (ROOT / 'SHA256SUMS').read_text().splitlines():
        expected, relative = line.split('  ', 1)
        require(relative not in checks, 'Duplicate checksum path')
        path = child(relative)
        require(path.is_file() and digest(path) == expected, 'Checksum mismatch: ' + relative)
        checks[relative] = expected
    files = {p.relative_to(ROOT).as_posix() for p in ROOT.rglob('*') if p.is_file()}
    require(files == set(checks) | {'SHA256SUMS'}, 'Package file inventory differs')
    return checks


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument('--regenerate-data', type=Path,
                        help='Also write the five regenerated dataset/audit files into a NEW directory outside the package')
    args = parser.parse_args()
    checks = check_checksums()
    sys.path.insert(0, str(CANDIDATE))
    import prepare
    import runner
    import scoring
    import result_pipeline as pipeline
    from fixed_runtime import validate_pairing
    read = pipeline.read_json
    transform = read(ROOT / 'TRANSFORMATIONS.json')
    entries = {entry['public_path']: entry for entry in transform['files']}
    for rel, entry in entries.items():
        if 'public_sha256' in entry:
            require(digest(child(rel)) == entry['public_sha256'], 'Transformation output differs: ' + rel)
        if entry['operation'] == 'byte-identical copy':
            require(digest(child(rel)) == entry['source_sha256'], 'Original bytes differ: ' + rel)
    # The original source-snapshot validator and dataset generator are unmodified.
    protocol, snapshot = runner.verify_candidate()
    generated = prepare.make_files()
    for name, data in generated.items():
        require((CANDIDATE / name).read_bytes() == data, 'Regenerated dataset differs: ' + name)
    freeze = read(ROOT / 'run/frozen-run.public.json')
    original_freeze = transform['source_anchors']['original_frozen_manifest_sha256']
    for name, record in freeze['source_files'].items():
        entry = entries['executed-source/candidate/' + name]
        require(entry['source_sha256'] == record['sha256'] and entry['source_bytes'] == record['bytes'],
                'Frozen source provenance differs: ' + name)
    require(freeze['protocol'] == protocol, 'Frozen protocol differs')
    require(read(ROOT / 'model-manifest.json') == freeze['model_manifest'], 'Model manifest differs')
    require(digest(ROOT / 'requirements-training-lock.txt') == freeze['dependency_lock']['sha256'], 'Dependency lock differs')
    run = read(ROOT / 'run/run-complete.json')
    require(digest(ROOT / 'run/run-complete.json') == transform['source_anchors']['original_run_complete_sha256'], 'Original completion receipt differs')
    require(run['status'] == 'complete' and run['raw_responses'] == 180 and run['frozen_manifest_sha256'] == original_freeze,
            'Run completion identity/count mismatch')
    # Validate every included run artifact against the immutable original receipt.
    checked_artifacts = 0
    for relative, record in run['artifact_hashes'].items():
        path = ROOT / 'run' / relative
        if path.is_file():
            require(digest(path) == record['sha256'] and path.stat().st_size == record['bytes'], 'Run artifact differs: ' + relative)
            checked_artifacts += 1
    data = {split: read(CANDIDATE / 'data' / (split + '.json')) for split in ('train', 'dev', 'test')}
    require([len(data[s]) for s in ('train','dev','test')] == [45,18,18], 'Dataset sizes differ')
    report = read(ROOT / 'results/report.public.json')
    require(report['frozen_manifest_sha256'] == original_freeze and report['run_complete_sha256'] == digest(ROOT / 'run/run-complete.json'), 'Report anchors differ')
    require(report['scorer_version'] == scoring.SCORER_VERSION, 'Scorer version differs')
    queue = read(ROOT / 'reviews/queue.json')
    reviews = read(ROOT / 'reviews/reviews.json')
    joined = read(ROOT / 'reviews/join.public.json')
    queue_sha = digest(ROOT / 'reviews/queue.json')
    require(queue_sha == reviews['queue_sha256'] == joined['queue_sha256'] == report['review_receipts']['queue_sha256'], 'Review queue receipt mismatch')
    require(digest(ROOT / 'reviews/reviews.json') == report['review_receipts']['reviews_sha256'], 'Review receipt mismatch')
    require(joined['frozen_manifest_sha256'] == original_freeze and joined['run_complete_sha256'] == digest(ROOT / 'run/run-complete.json'), 'Review join anchor mismatch')
    pass_sets = []
    for name in ('pass-one.json', 'pass-two.json'):
        obj = read(ROOT / 'reviews' / name)
        require(obj['queue_sha256'] == queue_sha, 'Pass queue mismatch')
        items = {r['opaque_id']: r['pass'] for r in obj['items']}
        require(len(items) == len(obj['items']) == 3, 'Missing/duplicate assistant pass')
        pass_sets.append(items)
    review_map = {r['opaque_id']: r['passes'] for r in reviews['items']}
    queue_map = {r['opaque_id']: r for r in queue['items']}
    require(set(review_map) == set(queue_map) == set(joined['items']) == set(pass_sets[0]) == set(pass_sets[1])
            and len(review_map) == len(reviews['items']) == len(queue['items']) == 3, 'Review inventory mismatch')
    for opaque, passes in review_map.items():
        require(passes == [m[opaque] for m in pass_sets], 'Original pass receipts changed')
    rows_by_path = {r['raw_path']: r for r in report['rows']}
    require(len(rows_by_path) == len(report['rows']) == 180, 'Report row inventory differs')
    rows = []
    observed_paths = set()
    review_paths = {v['raw_path']: k for k,v in joined['items'].items()}
    require(len(review_paths) == 3, 'Duplicate review join')
    initializations = {}
    plans = {c['condition']: c for c in read(ROOT / 'preflight/planned-budgets.json')['conditions']}
    total_steps = 0
    for condition in protocol['conditions']:
        tag = condition['tag']; directory = ROOT / 'run/conditions' / tag
        complete = read(directory / 'condition-complete.json')
        require(digest(directory / 'condition-complete.json') == run['condition_complete_hashes']['conditions/' + tag + '/condition-complete.json']['sha256'], 'Condition completion hash differs')
        stable = complete['artifact_hashes']
        initializations[tag] = pipeline._validate_training(directory, tag, freeze, data['train'], stable)
        summary = read(directory / 'monitor-summary.json')
        require(summary['exit_code'] == 0 and summary['stop_reason'] is None and summary['limits'] == freeze['limits'], 'Monitor completion differs')
        require(summary['peak_observed_sum_vm_hwm_bytes'] < freeze['limits']['rss_bytes'] and summary['minimum_available_memory_bytes'] >= freeze['limits']['available_memory_bytes'], 'Resource guard violated')
        if tag != 'baseline':
            for expected_step in plans[tag]['steps']:
                step = read(directory / 'steps' / ('%03d.json' % expected_step['step']))
                require(all(step[k] == v for k,v in expected_step.items()), 'Training batch differs from frozen plan')
                total_steps += 1
        for split in ('dev', 'test'):
            for item in data[split]:
                relative = 'conditions/%s/raw/%s/%s.json' % (tag, split, item['id'])
                record = read(ROOT / 'run' / relative)
                pipeline._validate_raw(record, item, tag, split, original_freeze, 151645)
                require(relative in run['artifact_hashes'], 'Raw file absent from original completion receipt')
                observed_paths.add(relative)
                bundle, passes, opaque = None, [], review_paths.get(relative)
                extracted = scoring.extract(record['output'], record['hit_limit'])
                if record['hit_limit'] or not record['output'].strip():
                    bundle = scoring.adjudicate_review_bundle(record['output'], [], record['hit_limit'])
                    pathway = 'mechanical_null'
                elif extracted['review_required']:
                    require(opaque in review_map, 'Missing required review')
                    q, join = queue_map[opaque], joined['items'][opaque]
                    require(q['output'] == record['output'] and q['output_sha256'] == record['output_sha256'] and q['hit_limit'] == record['hit_limit'], 'Queue output differs')
                    require(join['raw_artifact_sha256'] == digest(ROOT / 'run' / relative) and join['output_sha256'] == record['output_sha256'] and join['condition'] == tag and join['split'] == split and join['item_id'] == item['id'], 'Review/source join differs')
                    passes = review_map[opaque]
                    bundle = scoring.adjudicate_review_bundle(record['output'], passes, False)
                    pathway = 'assistant_reviewed'
                else:
                    require(opaque is None, 'Automatic output received a review')
                    pathway = 'automatic'
                score = scoring.score_output(record['output'], record['expected'], record['hit_limit'], review=bundle['final_review'] if bundle else None)
                expected_row = dict(raw_path=relative, raw_file_sha256=digest(ROOT / 'run' / relative), condition=tag,
                                    split=split, item_id=item['id'], task=item['task'], pathway=pathway, score=score,
                                    review_bundle=bundle, original_assistant_passes=passes, opaque_id=opaque)
                require(expected_row == rows_by_path[relative], 'Recomputed row/score differs: ' + relative)
                rows.append(expected_row)
    validate_pairing(initializations, protocol['conditions'])
    require(total_steps == 144, 'Training step total differs')
    actual_raw = {p.relative_to(ROOT / 'run').as_posix() for p in (ROOT / 'run').glob('conditions/*/raw/*/*.json')}
    require(actual_raw == observed_paths == set(rows_by_path) and len(observed_paths) == 180, 'Raw row inventory differs')
    coverage = pipeline._coverage(rows, 180)
    require(coverage == report['coverage'], 'Total coverage differs')
    for tag, splits in report['by_condition_split'].items():
        for split, cell in splits.items():
            chosen = [r for r in rows if r['condition'] == tag and r['split'] == split]
            value = {**pipeline._coverage(chosen,18), 'tasks': {t:pipeline._coverage([r for r in chosen if r['task']==t],6) for t in pipeline.TASKS}}
            require(value == cell, 'Condition/split/task totals differ')
    for arm, cell in report['by_arm'].items():
        chosen = [r for r in rows if pipeline._arm(r['condition']) == arm]
        require(pipeline._coverage(chosen,36 if arm=='baseline' else 72) == cell, 'Arm totals differ')
    if args.regenerate_data:
        destination = args.regenerate_data.expanduser().resolve()
        require(not destination.exists() and not destination.is_relative_to(ROOT), 'Regeneration requires a new directory outside package')
        destination.mkdir(parents=True, exist_ok=False)
        for name, content in generated.items():
            (destination / Path(name).name).write_bytes(content)
    summary = dict(status='PASS',package_payload_files=len(checks),original_run_artifacts_checked=checked_artifacts,
                   regenerated_dataset_rows=81,raw_records=180,recomputed_scores=180,assistant_reviewed_outputs=3,
                   original_assistant_passes=6,validated_training_steps=144,
                   automatic_outputs=coverage['automatic_value'],blank=coverage['blank'],capped=coverage['capped'],
                   unresolved=coverage['unresolved'],final_number_matches=coverage['final_number_match_correct'],
                   by_condition={c:{s:cell['final_number_match_correct'] for s,cell in splits.items()} for c,splits in report['by_condition_split'].items()},
                   scope='Dataset regeneration, existing raw-record/token-accounting and score audit; no token decoding, model execution, independent review or training replication')
    print(json.dumps(summary,indent=2,sort_keys=True))

if __name__ == '__main__':
    try:
        main()
    except (ValueError, OSError, KeyError, TypeError) as error:
        raise SystemExit('AUDIT FAILED: ' + str(error))
