#!/usr/bin/env python3
"""Verify both supplied archives and print reproducible source diffs.
Usage: python compare_revisions.py paper-revision-snapshots.zip initial-commit-verification.zip
Uses only the Python standard library; reads ZIP entries without extracting files.
"""
import argparse
import difflib
import hashlib
import json
import zipfile

INITIAL = "adc7f1241b42e322a6451854ab7e4b4c146bf78a"
LATER = "fd4aeeb2ee4fc729c18d98444fed42fd0529eeeb"
PAIRS = {
    "Taming": (
        "Taming-implies-compatibility-on-four-manifolds-September-23-2026",
        "Taming-implies-compatibility-on-four-manifolds-October-6-2026"),
    "Box Transport": (
        "Incompressible-Box-Transport-and-Finite-Computation-September-27-2026",
        "Incompressible-Box-Transport-and-Finite-Computation-October-6-2026"),
    "BSD": (
        "Exact-Birch-Swinnerton-Dyer-Formula-from-Low-Selmer-Corank-October-3-2026",
        "Exact-Birch-Swinnerton-Dyer-Formula-from-Low-Selmer-Corank-October-7-2026")
}

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

def git_hash(kind, data):
    return hashlib.sha1(kind.encode() + b" " + str(len(data)).encode() + b"\0" + data).hexdigest()

def tree_hash(entries):
    entries = sorted(entries, key=lambda e: (
        e["path"] + ("/" if e["type"] == "tree" else "")).encode())
    data = b"".join(
        e["mode"].lstrip("0").encode() + b" " + e["path"].encode()
        + b"\0" + bytes.fromhex(e["sha"]) for e in entries)
    return git_hash("tree", data)

def load_folder(archive, folder):
    prefix = "preprints/" + folder + "/"
    return {n[len(prefix):]: archive.read(n) for n in archive.namelist()
            if n.startswith(prefix) and not n.endswith("/")}

def compare(snapshot_path, verification_path):
    with zipfile.ZipFile(snapshot_path) as z, zipfile.ZipFile(verification_path) as v:
        check(len(z.namelist()) == len(set(z.namelist())), "Duplicate snapshot ZIP paths")
        check(len(v.namelist()) == len(set(v.namelist())), "Duplicate verification ZIP paths")
        p = json.loads(z.read("provenance.json"))
        t = json.loads(v.read("initial-commit-paper-trees.json"))
        c = json.loads(v.read("initial-snapshot-comparison.json"))
        commits = json.loads(z.read("repository-commits.json"))
        check(p["commit"] == c["currentCommit"] == LATER, "Later commit mismatch")
        check(t["commit"] == c["initialCommit"] == INITIAL, "Initial commit mismatch")
        manifest = {f["path"]: f for f in p["files"]}
        check(len(manifest) == len(p["files"]) == 91, "Expected 91 unique manifest files")
        for path, f in manifest.items():
            data = z.read(path)
            check(len(data) == f["bytes"], "Length mismatch: " + path)
            check(hashlib.sha256(data).hexdigest() == f["sha256"], "SHA-256 mismatch: " + path)
            check(git_hash("blob", data) == f["gitBlobSha"], "Blob mismatch: " + path)

        comparisons = {x["path"]: x for x in c["comparisons"]}
        check(len(comparisons) == len(c["comparisons"]) == 45, "Expected 45 unique comparison rows")
        check({x["folder"] for x in t["papers"]} == {x[0] for x in PAIRS.values()},
              "Unexpected older paper folders")
        checked = []
        counts = {}
        for paper in t["papers"]:
            prefix = "preprints/" + paper["folder"] + "/"
            entries = paper["files"]
            check(len({e["path"] for e in entries}) == len(entries), "Duplicate tree entries")
            blobs = [e for e in entries if e["type"] == "blob"]
            supplied_paths = {prefix + n for n in load_folder(z, paper["folder"])}
            check(supplied_paths == {prefix + e["path"] for e in blobs}, "Incomplete older folder")
            for e in blobs:
                path = prefix + e["path"]
                data = z.read(path)
                row = comparisons[path]
                actual = git_hash("blob", data)
                check(len(data) == e["size"], "Initial size mismatch: " + path)
                check(actual == e["sha"] == row["initialGitBlobSha"]
                      == row["suppliedGitBlobSha"] == manifest[path]["gitBlobSha"],
                      "Initial blob mismatch: " + path)
                check(row["identical"] is True, "Recorded comparison mismatch: " + path)
                checked.append(path)
            directories = [("", paper["treeSha"])] + [
                (e["path"], e["sha"]) for e in entries if e["type"] == "tree"]
            for directory, expected in directories:
                children = []
                for entry in entries:
                    parent, _, name = entry["path"].rpartition("/")
                    if parent == directory:
                        children.append(dict(entry, path=name))
                check(tree_hash(children) == expected, "Subtree mismatch: " + prefix + directory)
            counts[paper["folder"]] = len(blobs)
        check(len(checked) == 45 and set(checked) == set(comparisons), "Old-file coverage mismatch")
        root = t["root"]
        check(root.get("truncated") is False, "Truncated initial root listing")
        initial_metadata = next(x for x in commits if x["sha"] == INITIAL)
        root_sha = tree_hash(root["tree"])
        check(root_sha == initial_metadata["commit"]["tree"]["sha"], "Root-tree mismatch")
        check(next(e["sha"] for e in root["tree"] if e["path"] == "preprints")
              == t["preprintsTreeSha"], "Preprints-tree identifier mismatch")

        print(json.dumps({
            "initial_commit": INITIAL,
            "later_commit": LATER,
            "manifest_files_verified": len(manifest),
            "older_files_verified": len(checked),
            "older_folder_counts": counts,
            "initial_root_tree_reconstructed": root_sha,
            "preprints_tree_id": t["preprintsTreeSha"],
            "limits": "Offline verification of supplied records. The full preprints tree listing "
                      "is not included, so the paper-tree membership edge is supplied provenance, "
                      "not independently reconstructed. No live GitHub or proof verification."
        }, indent=2))
        for title, (older, newer) in PAIRS.items():
            print("\n# " + title)
            a, b = load_folder(z, older), load_folder(z, newer)
            for name in sorted(a.keys() | b.keys()):
                status = ("added" if name not in a else "removed" if name not in b
                          else "identical" if a[name] == b[name] else "changed")
                print(status + "\t" + name)
                if status != "identical" and not name.endswith(".pdf"):
                    print("".join(difflib.unified_diff(
                        a.get(name, b"").decode().splitlines(True),
                        b.get(name, b"").decode().splitlines(True),
                        fromfile=INITIAL + "/preprints/" + older + "/" + name,
                        tofile=LATER + "/preprints/" + newer + "/" + name)), end="")

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("snapshots_zip")
    parser.add_argument("initial_verification_zip")
    args = parser.parse_args()
    compare(args.snapshots_zip, args.initial_verification_zip)
