#!/usr/bin/env python3
"""Offline addendum only: never rewrite the original observation or its seal."""
import copy
import hashlib
import json
import math
import subprocess
from pathlib import Path

R = Path(__file__).resolve().parent


def compare(original, derived):
    differences = []
    checked_strings = []
    def walk(a, b, path):
        if isinstance(a, dict):
            assert isinstance(b, dict) and a.keys() == b.keys(), path
            for k in a:
                walk(a[k], b[k], path + [k])
        elif isinstance(a, list):
            assert isinstance(b, list) and len(a) == len(b), path
            for i, (x, y) in enumerate(zip(a, b)):
                walk(x, y, path + [i])
        elif isinstance(a, (int, float)) and not isinstance(a, bool):
            assert isinstance(b, (int, float)) and not isinstance(b, bool), path
            assert a == b, (path, repr(a), repr(b))
            if a == 0 and math.copysign(1, a) != math.copysign(1, b):
                assert math.copysign(1, a) == -1 and math.copysign(1, b) == 1, path
                differences.append({"path": path, "raw_replay_number_repr": repr(a),
                                    "sealed_derived_number_repr": repr(b)})
        else:
            assert type(a) is type(b) and a == b, path
            if isinstance(a, str) and path[-1] in ["native_double_hex", "float17g", "byte_order"]:
                checked_strings.append(path)
    walk(original, derived, [])
    return differences, checked_strings


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


def main():
    seal = json.loads((R / "seal.json").read_bytes())
    for name, sha in seal["public_sha256"].items():
        assert digest(R / name) == sha, name
    raw = json.loads((R / "raw-manifest.json").read_bytes())
    for name, sha in raw["raw_files"].items():
        assert digest(R / name) == sha, name
    a = json.loads(subprocess.check_output(["python3", str(R / "analyze-evidence.py")]))
    b = json.loads((R / "observations.json").read_bytes())
    differences, strings = compare(a, b)
    stats = {}
    points = {lane: {p["case_id"]: p for p in a["lanes"][lane]["points"]} for lane in ["B", "C"]}
    for candidate in a["candidate_comparison"]["results"]:
        stats[candidate] = {}
        for label, view in [("B_stored_computed_double", "computed_point"), ("B_physical_getter", "native_point")]:
            matches, sign_only = 0, []
            for case_id, bp in points["B"].items():
                expected = bp["reads"][view]["typed_rows"][0]["power"]
                actual = points["C"][case_id]["reads"]["computed_point"]["typed_rows"][0][candidate]
                same = expected["native_double_hex"] == actual["native_double_hex"]
                matches += same
                if not same and expected["value"] == actual["value"] == 0:
                    sign_only.append({"case_id": case_id, "expected_hex": expected["native_double_hex"],
                                      "actual_hex": actual["native_double_hex"]})
            numeric = a["candidate_comparison"]["results"][candidate]["matches_" + label]
            assert matches + len(sign_only) == numeric
            stats[candidate][label] = {"total": 48, "bit_matches": matches,
                                      "old_numeric_matches": numeric, "signed_zero_only_mismatches": sign_only}
    zero_case = points["B"]["S-H24"] if points["B"]["S-H24"]["original_lexeme"] == "-0" else None
    zero_facts = []
    for lane in ["B", "C"]:
        for p in points[lane].values():
            if p["mode"] == "S" and p["original_lexeme"] == "-0":
                zero_facts.append({"lane": lane, "case_id": p["case_id"], "original_lexeme": "-0",
                                   "native": {k: {t: v[t] for t in ["php_type", "float17g", "native_double_hex", "byte_order"]}
                                              for k, v in p["reads"]["native_point"]["typed_rows"][0].items()
                                              if k in ["power", "raw_value"]}})
    negatives = 0
    for mutated in [{"x": 1}, {"x": "0"}, {"x": True}, {"x": -0.0, "extra": 0}]:
        try:
            compare({"x": -0.0}, mutated)
        except AssertionError:
            negatives += 1
        else:
            raise AssertionError("non-sign mutation accepted")
    try:
        compare({"native_double_hex": "0000000000000080"}, {"native_double_hex": "0000000000000000"})
    except AssertionError:
        negatives += 1
    else:
        raise AssertionError("hex mutation accepted")
    try:
        compare({"x": 0.0}, {"x": -0.0})
    except AssertionError:
        negatives += 1
    else:
        raise AssertionError("reverse sign mutation accepted")
    return {"status": "ADDENDUM_ONLY_OLD_FILES_UNCHANGED", "seal_sha256": digest(R / "seal.json"),
            "old_public_files_verified": len(seal["public_sha256"]), "old_raw_files_verified": len(raw["raw_files"]),
            "signed_zero_only_path_count": len(differences), "signed_zero_paths": differences,
            "unchanged_hex_float17g_byteorder_string_paths": len(strings), "bit_aware_candidates": stats,
            "original_negative_zero_native_facts": zero_facts, "mutation_rejections": negatives,
            "number_comparison_policy": "JSON int/float lexical category ignored; exact numeric equality, only -0 to +0 sign loss permitted. Every nonnumeric leaf remains strict.",
            "future_oracle_policy": "Use original raw typed native_double_hex and float17g strings. Never infer signed zero from derived numeric value."}


if __name__ == "__main__":
    print(json.dumps(main(), ensure_ascii=False, indent=2))
