#!/usr/bin/env python3
"""Verify exported reference coverage, repair texts and provenance offline."""
import argparse
import hashlib
import json
from pathlib import Path
import re
import sys

ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
from src.output.originals import load_site
from src.output.alignment import read_output


def unique_object(pairs):
    result = {}
    for key, value in pairs:
        if key in result:
            raise ValueError(f'Duplicate JSON key: {key}')
        result[key] = value
    return result


def read(path):
    return json.loads(path.read_text(), object_pairs_hook=unique_object)


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


def verify(release, audit_path, bundle_path, source):
    manifest, audit, bundle = map(read, (release / 'manifest.json', audit_path, bundle_path))
    assert manifest['audit_sha256'] == sha(audit_path), 'Audit hash mismatch'
    assert audit['numbering_ready'], 'Unresolved numbering audit'
    assert not any(audit[k] for k in ('missing_passages', 'conflicts', 'held_records'))
    raw, files = read_output(source)
    assert files == audit['input_files'], 'Raw source changed'
    site, codes = load_site()
    reverse = {code: book for book, code in codes.items()}
    actual, headings = {}, 0
    assert set(manifest['files']) == {code + '.json' for code in codes.values()}
    assert {p.name for p in release.iterdir()} == set(manifest['files']) | {'manifest.json'}
    for filename, expected in manifest['files'].items():
        path = release / filename
        assert sha(path) == expected, f'Asset hash mismatch: {filename}'
        book = read(path)
        assert book['translation'] == '日常訳第1版' and book['schema'] == 1
        assert book['book'] == path.stem
        for ch, chapter in book['chapters'].items():
            headings += bool(chapter['heading'])
            for verse, text in chapter['verses'].items():
                key = (reverse[book['book']], int(ch), int(verse))
                assert int(verse) > 0 and key not in actual
                assert text.strip() and not re.search(r'[\x00-\x1f\x7f<>\ufffd\u0370-\u03ff\u0590-\u05ff]|```', text), key
                actual[key] = text
    assert actual.keys() == site.keys(), 'Missing or extra release references'
    for key, text in actual.items():
        ref = ':'.join(map(str, key))
        expected = audit['aligned_changes'].get(ref, {}).get('text', raw.get(ref))
        assert text == expected, f'Export differs from approved alignment: {ref}'
    for decision in bundle['decisions']:
        book, ch, verse = decision['target'].split(':')
        assert actual[(book, int(ch), int(verse))] == decision['text'], decision['target']
    counts = {'books': len({k[0] for k in actual}), 'chapters': len({k[:2] for k in actual}), 'verses': len(actual)}
    assert all(manifest[k] == value for k, value in counts.items())
    return {'status': 'pass', **counts, 'heading_blocks': headings, 'verified_repairs': len(bundle['decisions']),
            'audit_sha256': sha(audit_path), 'bundle_sha256': sha(bundle_path), 'manifest_sha256': sha(release / 'manifest.json')}


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument('--release', type=Path, default=ROOT / 'reports/releases/nichijo-v1')
    parser.add_argument('--audit', type=Path, default=ROOT / 'reports/nichijo-v1-numbering-alignment.json')
    parser.add_argument('--bundle', type=Path, default=ROOT / 'reports/numbering-repairs/repair-bundle.json')
    parser.add_argument('--source', type=Path, default=ROOT / 'data/output')
    parser.add_argument('--report', type=Path)
    args = parser.parse_args()
    try:
        result = verify(args.release, args.audit, args.bundle, args.source)
    except (AssertionError, ValueError, KeyError, OSError) as exc:
        print(f'Release verification failed: {exc}', file=sys.stderr)
        sys.exit(1)
    output = json.dumps(result, ensure_ascii=False, indent=2) + '\n'
    if args.report:
        args.report.write_text(output)
    print(output, end='')
