"""Verify the published synthetic case independently. Python 3 standard library.

Run: python verify.py [directory]. No generator code is used for arithmetic.
Literal counts and closed-form formulas are deliberate independent oracles;
this checks the published case, not arbitrary edited counts. If present, the
manifest must list every sibling file except itself exactly once.
"""
from pathlib import Path
from fractions import Fraction
import csv
import hashlib
import json
import re
import sys


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


def unique_object(pairs):
    result = {}
    for key, value in pairs:
        require(key not in result, "Duplicate JSON key: " + key)
        result[key] = value
    return result


def read_csv(path, header):
    with path.open(newline="", encoding="utf-8") as stream:
        reader = csv.DictReader(stream)
        require(reader.fieldnames == header, "Unexpected CSV header: " + path.name)
        rows = list(reader)
    require(all(set(row) == set(header) and all(v is not None for v in row.values())
                for row in rows), "Malformed CSV row: " + path.name)
    return rows


def closed_form(weight):
    # Percent formulas: earlier = 40 + 50w; later = 50 + 45w.
    require(Fraction(0) <= weight <= Fraction(1), "Invalid reference weight")
    earlier = (40 + 50 * weight) / 100
    later = (50 + 45 * weight) / 100
    return earlier, later, (10 - 5 * weight) / 100


def verify_manifest(root):
    manifest = root / "MANIFEST.sha256"
    if not manifest.exists():
        return "Manifest absent; arithmetic verified, publication hashes not checked."
    expected_files = {path.name for path in root.iterdir()
                      if path.is_file() and path.name != manifest.name}
    entries = {}
    for line in manifest.read_text(encoding="utf-8").splitlines():
        match = re.fullmatch(r"([a-fA-F0-9]{64})  (.+)", line)
        require(match is not None, "Malformed manifest line")
        digest, name = match.groups()
        require(name not in entries, "Duplicate manifest file: " + name)
        require(name in expected_files and Path(name).name == name,
                "Unexpected or unsafe manifest file: " + name)
        entries[name] = digest.lower()
    require(set(entries) == expected_files, "Manifest file coverage differs from directory")
    for name, expected in entries.items():
        actual = hashlib.sha256((root / name).read_bytes()).hexdigest()
        require(actual == expected, "Checksum mismatch: " + name)
    return "Manifest verified: " + str(len(entries)) + " files."


def verify(root):
    expected_rows = [
        {"period": "earlier", "category": "familiar", "accepted": "72", "attempts": "80"},
        {"period": "earlier", "category": "developing", "accepted": "8", "attempts": "20"},
        {"period": "later", "category": "familiar", "accepted": "19", "attempts": "20"},
        {"period": "later", "category": "developing", "accepted": "40", "attempts": "80"},
    ]
    rows = read_csv(root / "counts.csv", ["period", "category", "accepted", "attempts"])
    require(rows == expected_rows, "Counts differ from four published cells")
    data = json.loads((root / "results.json").read_text(encoding="utf-8"),
                      object_pairs_hook=unique_object)
    require(set(data) == {"scope", "rows", "pooled", "benchmarks"}, "Unexpected JSON fields")
    require(data["scope"] == "synthetic binary accepted decisions; descriptive arithmetic only",
            "Unexpected example scope")
    require(data["rows"] == expected_rows, "JSON counts disagree with CSV or published cells")
    # Separate literal pooled-count oracles, not reuse of the generator's fold.
    require(data["pooled"] == {"earlier": "4/5", "later": "59/100"}, "Pooled rate mismatch")
    require(Fraction(72 + 8, 100) == Fraction(data["pooled"]["earlier"]), "Earlier pool mismatch")
    require(Fraction(19 + 40, 100) == Fraction(data["pooled"]["later"]), "Later pool mismatch")
    weights = {"equal": Fraction(1, 2), "earlier_mix": Fraction(4, 5),
               "later_mix": Fraction(1, 5)}
    require(set(data["benchmarks"]) == set(weights), "Benchmark names mismatch")
    for name, weight in weights.items():
        benchmark = data["benchmarks"][name]
        require(set(benchmark) == {"familiar_weight", "earlier", "later", "change"},
                "Unexpected benchmark fields")
        actual = tuple(Fraction(benchmark[key]) for key in ("earlier", "later", "change"))
        require(Fraction(benchmark["familiar_weight"]) == weight, "Benchmark weight mismatch")
        require(actual == closed_form(weight), "Benchmark rate mismatch: " + name)
    sweep = read_csv(root / "weight-sensitivity.csv", ["familiar_weight", "earlier", "later", "change"])
    require(len(sweep) == 101, "Expected exactly 101 sensitivity rows")
    for index, row in enumerate(sweep):
        weight = Fraction(row["familiar_weight"])
        require(weight == Fraction(index, 100), "Missing, duplicate, or out-of-order sweep weight")
        actual = tuple(Fraction(row[key]) for key in ("earlier", "later", "change"))
        require(actual == closed_form(weight), "Sensitivity closed-form mismatch")
        require(actual[1] - actual[0] == actual[2], "Sensitivity change mismatch")
    return verify_manifest(root)


if __name__ == "__main__":
    try:
        require(len(sys.argv) <= 2, "Usage: python verify.py [directory]")
        root = Path(sys.argv[1]).resolve() if len(sys.argv) == 2 else Path(__file__).resolve().parent
        manifest_status = verify(root)
        print("PASS: literal counts, JSON/CSV parity, three benchmarks and 101 exact weight rows.")
        print(manifest_status)
    except (ValueError, KeyError, TypeError, OSError, ZeroDivisionError, csv.Error) as error:
        print("FAIL: " + str(error), file=sys.stderr)
        sys.exit(1)
