#!/usr/bin/env python3
"""
vericull_verify.py — Standalone Zero-Dependency Offline Verifier for Vericull ZSVD Dossiers.

Requires only Python 3.9+ standard library (json, hashlib, math, struct, sys).
Recomputes every ZSND byte-span needle claim, 64-bit FNV-1a64 sweep digest, IEEE-754 float32
XGBoost compact tree walk, CraftMemory 5-tier Hebbian recall, 95% Wilson confidence interval,
Engine B SLAB multi-boot demotion wall, ZSVD verdict seal, and canonical SHA-256 record digest.

Usage:
  python3 vericull_verify.py forensic-dossiers.json [--bundle-hash <expected_bundle_sha256>]
"""

import argparse
import hashlib
import json
import math
import struct
import sys

FNV_OFFSET_64 = 0xCBF29CE484222325
FNV_PRIME_64 = 0x100000001B3
MASK_64 = 0xFFFFFFFFFFFFFFFF
ZSVD_SALT_64 = 0x5A53454330565253


def f32(x: float) -> float:
    return struct.unpack("<f", struct.pack("<f", float(x)))[0]


def sweep_needles(haystack_bytes: bytes, claims: list[dict]) -> tuple[int, int, int]:
    failed_index = -1
    h = FNV_OFFSET_64
    passed = 0
    hlen = len(haystack_bytes)

    for c_idx, cl in enumerate(claims):
        nb = cl["needle"].encode("utf-8")[:63]
        nlen = len(nb)
        start = int(cl["span_start"])
        raw_end = int(cl["span_end"])
        end = hlen if (raw_end == 0 or raw_end > hlen) else raw_end
        if start > end:
            return 0, c_idx, h

        if nlen == 0:
            found = True
        elif end - start >= nlen:
            found = haystack_bytes.find(nb, start, end) != -1
        else:
            found = False

        satisfied = (not found) if int(cl["must_be_absent"]) != 0 else found
        h = ((h ^ (c_idx + 1)) * FNV_PRIME_64) & MASK_64
        h = ((h ^ (0xA5 if satisfied else 0x5A)) * FNV_PRIME_64) & MASK_64

        if not satisfied:
            if failed_index == -1:
                failed_index = c_idx
        else:
            passed += 1

    return passed, failed_index, h


def eval_router(trees: list[dict], base_score: float, n_features: int, overrides: dict) -> tuple[int, float, int]:
    feats = [0.0] * n_features
    for k, v in overrides.items():
        idx = int(k)
        if 0 <= idx < n_features:
            feats[idx] = f32(v)

    s = f32(base_score) if math.isfinite(base_score) else f32(0.5)
    for tree in trees:
        node = 0
        for _ in range(7):
            if node < 0 or node >= 7:
                break
            left = int(tree["left_children"][node])
            if left == -1:
                break
            sf = int(tree["split_indices"][node])
            sv = f32(tree["split_conditions"][node])
            fv = feats[sf] if 0 <= sf < n_features else f32(0.0)
            node = left if fv < sv else int(tree["right_children"][node])
        if 0 <= node < 7:
            s = f32(s + f32(tree["base_weights"][node]))

    prob = f32(1.0 / (1.0 + math.exp(-float(s))))
    if prob >= f32(0.85):
        return 3, prob, 0x00
    if prob <= f32(0.25):
        return 0, prob, 0x00
    if prob > f32(0.50):
        return 2, prob, 0x07
    return 1, prob, 0x3F


def eval_craft_memory(memories: list[dict]) -> float:
    priors = [f32(0.0), f32(0.15), f32(0.20), f32(0.25), f32(0.30)]
    best = f32(-1.0)
    n_denom = f32(len(memories) if memories else 1)
    for m in memories[:32]:
        ov = int(m["query_overlap"])
        if ov == 0:
            continue
        lvl = int(m["level"]) if 0 <= int(m["level"]) <= 4 else 0
        norm_ov = f32(f32(ov) / f32(math.sqrt(f32(int(m["token_count"]) + 1.0))))
        recency = f32(f32(0.10) * f32(f32(int(m["created_at"])) / n_denom))
        score = f32(f32(norm_ov + priors[lvl]) + recency)
        if score > best:
            best = score
    return f32(0.0) if best < 0 else best


def wilson_ci95(passes: int, attempts: int) -> tuple[float, float]:
    if attempts == 0:
        return 0.0, 1.0
    z = f32(1.959963984540054)
    z2 = f32(z * z)
    k = min(passes, attempts)
    n = f32(attempts)
    p = f32(f32(k) / n)
    denom = f32(1.0 + f32(z2 / n))
    center = f32(f32(p + f32(z2 / f32(2.0 * n))) / denom)
    rad = f32(f32(p * f32(1.0 - p)) / n + f32(z2 / f32(4.0 * n * n)))
    margin = f32(f32(z * f32(math.sqrt(rad))) / denom)
    lo = 0.0 if k == 0 else max(0.0, f32(center - margin))
    hi = 1.0 if k == attempts else min(1.0, f32(center + margin))
    return round(float(lo), 4), round(float(hi), 4)


def canonical_record_bytes(rec: dict) -> bytes:
    core = {
        "case_id": rec["case_id"],
        "file_path": rec["file_path"],
        "source_snippet": rec["source_snippet"],
        "claims": [
            {
                "needle": c["needle"],
                "span_start": c["span_start"],
                "span_end": c["span_end"],
                "must_be_absent": c["must_be_absent"],
            }
            for c in rec["claims"]
        ],
        "feature_overrides": {str(k): rec["feature_overrides"][k] for k in sorted(rec["feature_overrides"], key=lambda x: int(x))},
        "repro_passes": rec["repro_passes"],
        "repro_attempts": rec["repro_attempts"],
        "has_bounded_check": rec["has_bounded_check"],
        "canary_mask": rec["canary_mask"],
        "verdict_state": rec["verdict_state"],
        "highest_ladder_rung": rec["highest_ladder_rung"],
        "sweep_digest_hex": rec["sweep_digest_hex"],
        "provenance_seal_hash": rec["provenance_seal_hash"],
    }
    body = json.dumps(core, sort_keys=True, separators=(",", ":"), ensure_ascii=False)
    return ("VERICULL-ZSVD-V1\n" + body).encode("utf-8")


def verify_bundle(doc: dict, expected_bundle_hash: str | None = None) -> tuple[bool, list[str]]:
    errors: list[str] = []
    router = doc["router_model"]
    memories = doc["craft_memories"]
    top_mem = round(float(eval_craft_memory(memories)), 4)

    rec_hashes: list[str] = []
    for rec in doc["dossiers"]:
        cid = rec["case_id"]
        hay = rec["source_snippet"].encode("utf-8")
        passed, failed_idx, sweep_h = sweep_needles(hay, rec["claims"])
        r_dec, tp_prob, mask = eval_router(
            router["compact_trees"], router["base_score_logit"], router["n_features"], rec["feature_overrides"]
        )
        passes = int(rec["repro_passes"])
        attempts = int(rec["repro_attempts"])
        canary = int(rec["canary_mask"])
        multi_boot_ok = attempts >= 3 and (passes * 5 >= attempts * 3)

        rung = -1
        if (canary & (1 << 4)) and multi_boot_ok:
            rung = 4
        elif (canary & (1 << 3)) and multi_boot_ok:
            rung = 3
        elif canary & (1 << 2):
            rung = 2
        elif canary & (1 << 1):
            rung = 1
        elif canary & (1 << 0):
            rung = 0

        if failed_idx != -1 or passed < len(rec["claims"]):
            verdict = 0
        elif rung == 4:
            verdict = 4
        elif passes > 0 and rung >= 1:
            verdict = 3
        elif int(rec["has_bounded_check"]) != 0:
            verdict = 2
        else:
            verdict = 1

        seal = (sweep_h ^ ZSVD_SALT_64) & MASK_64
        for val in (verdict, r_dec, rung + 10, ((passes << 16) | attempts)):
            seal = ((seal ^ (val & MASK_64)) * FNV_PRIME_64) & MASK_64

        sweep_hex = f"0x{sweep_h:016x}"
        seal_hex = f"0x{seal:016x}"
        lo, hi = wilson_ci95(passes, attempts)
        rec_sha = hashlib.sha256(canonical_record_bytes(rec)).hexdigest()
        rec_hashes.append(rec_sha)

        checks = [
            (passed == rec["structural_claims_passed"], f"{cid}: passed claims {passed} != {rec['structural_claims_passed']}"),
            (failed_idx == rec["failed_claim_index"], f"{cid}: failed_claim_index {failed_idx} != {rec['failed_claim_index']}"),
            (sweep_hex == rec["sweep_digest_hex"], f"{cid}: sweep_digest {sweep_hex} != {rec['sweep_digest_hex']}"),
            (verdict == rec["verdict_state"], f"{cid}: verdict {verdict} != {rec['verdict_state']}"),
            (rung == rec["highest_ladder_rung"], f"{cid}: ladder rung {rung} != {rec['highest_ladder_rung']}"),
            (r_dec == rec["router_decision"], f"{cid}: router_decision {r_dec} != {rec['router_decision']}"),
            (round(float(tp_prob), 4) == rec["tp_probability"], f"{cid}: tp_probability mismatch"),
            (f"0x{mask:02x}" == rec["layers_to_run_mask_hex"], f"{cid}: layers mask mismatch"),
            ([lo, hi] == rec["wilson_ci95"], f"{cid}: wilson_ci95 {[lo, hi]} != {rec['wilson_ci95']}"),
            (top_mem == rec["top_craft_memory_score"], f"{cid}: craft memory score mismatch"),
            (seal_hex == rec["provenance_seal_hash"], f"{cid}: ZSVD seal {seal_hex} != {rec['provenance_seal_hash']}"),
            (rec_sha == rec["record_sha256"], f"{cid}: record_sha256 mismatch"),
        ]
        for ok_cond, msg in checks:
            if not ok_cond:
                errors.append(msg)

    calc_bundle = hashlib.sha256(b"VERICULL-BUNDLE-V1\n" + "\n".join(rec_hashes).encode("utf-8")).hexdigest()
    if calc_bundle != doc.get("bundle_sha256"):
        errors.append(f"bundle_sha256 mismatch: computed {calc_bundle} != file {doc.get('bundle_sha256')}")
    if expected_bundle_hash and calc_bundle != expected_bundle_hash.lower():
        errors.append(f"pinned bundle_sha256 mismatch: computed {calc_bundle} != pinned {expected_bundle_hash}")

    return len(errors) == 0, errors


def main():
    ap = argparse.ArgumentParser(description="Verify Vericull ZSVD forensic dossiers offline.")
    ap.add_argument("path", help="Path to forensic-dossiers.json")
    ap.add_argument("--bundle-hash", help="Pinned SHA-256 bundle hash from the page", default=None)
    args = ap.parse_args()

    doc = json.load(open(args.path, "r", encoding="utf-8"))
    ok_all, errs = verify_bundle(doc, args.bundle_hash)
    if not ok_all:
        for e in errs:
            print(f"FAIL  {e}", file=sys.stderr)
        sys.exit(1)

    for rec in doc["dossiers"]:
        print(
            f"OK    {rec['short_id']} ({rec['verdict_code']}) | "
            f"claims={rec['structural_claims_passed']}/{rec['structural_claims_total']} | "
            f"rung={rec['ladder_code']} | wilson95={rec['wilson_ci95']} | "
            f"ZSVD={rec['provenance_seal_hash']} | SHA256={rec['record_sha256'][:16]}…"
        )
    print(f"VERIFIED {len(doc['dossiers'])} dossiers · bundle SHA-256 {doc['bundle_sha256']}")


if __name__ == "__main__":
    main()
