#!/usr/bin/env python3
"""Standalone verifier for the CAIN-42 multi-region Byzantine-test bundle. No CAIN imports.
Needs Python 3.8+, `cryptography`, and verify_pbft_qc_bundle.py in the same directory.

    python3 verify_byzantine_bundle.py BYZANTINE_BUNDLE.json

1. Every check of verify_pbft_qc_bundle.py on the three honest replicas
   (Ed25519 votes, quorums, identical decision chains, view change, tampering).
2. The equivocation proof against the Byzantine primary: two PRE_PREPAREs for
   the same (view, sequence) with DIFFERENT digests, BOTH signed by the accused
   replica's own pinned key -- so the accusation needs no trust in the accuser.
3. The Byzantine replica is not among the replicas whose history is certified,
   and every stated test result is consistent with its recorded numbers.
"""
from __future__ import annotations

import json
import os
import sys

sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import verify_pbft_qc_bundle as Q  # noqa: E402


def verify(b):
    base = Q.verify_bundle(b)
    checks = list(base["checks"])

    def check(name, ok, detail=""):
        checks.append({"check": name, "result": "PASS" if ok else "FAIL", "detail": detail})
    mb = Q.Membership(b["membership"])
    bt = b["byzantine_tests"]
    res = {r["test"]: r for r in bt["results"]}
    b1, b2 = res.get("B1_forging_backup"), res.get("B2_equivocating_primary")
    check("B1 forging backup: all writes committed and forged votes rejected",
          b1 and b1["committed"] == b1["writes"] and sum(b1["forged_votes_rejected_by"].values()) > 0 and b1["result"] == "PASS",
          json.dumps(b1 and b1["forged_votes_rejected_by"]))
    check("B2 equivocating primary: quarantined by all 3 honest replicas, view changed, commits continued",
          b2 and len(b2["quarantined_by"]) == 3 and b2["view_after"] > b2["view_before"]
          and b2["committed_after_quarantine"] == b2["writes_after"] and b2["result"] == "PASS",
          b2 and f"view {b2['view_before']} -> {b2['view_after']}")
    accused = b2["byzantine"] if b2 else None
    check("the Byzantine primary's history is not among the certified replicas", accused not in b["nodes"])
    proofs = bt.get("equivocation_proofs") or []
    ok_proof = False
    for p in proofs:
        ma, mbm = p.get("message_A") or {}, p.get("message_B") or {}
        same_slot = (ma.get("view"), ma.get("sequence")) == (mbm.get("view"), mbm.get("sequence"))
        both_accused = ma.get("sender_id") == mbm.get("sender_id") == accused == p.get("node_id")
        differ = ma.get("request_digest") != mbm.get("request_digest")
        sigs = all(Q.ed25519_ok(mb.keys[accused], m["signature_b64"], Q.msg_digest(m).encode()) for m in (ma, mbm)) \
            if accused in mb.keys else False
        ok_proof = ok_proof or (same_slot and both_accused and differ and sigs)
    check("equivocation proof: two different proposals for one (view, sequence), both signed by the accused's own key",
          ok_proof, f"{len(proofs)} proof(s)")
    failed = [c for c in checks if c["result"] == "FAIL"]
    return {"checks": checks, "passed": len(checks) - len(failed), "total": len(checks),
            "certificates_verified": base["certificates_verified"], "verdict": "ALL_CHECKS_PASSED" if not failed else "FAILED"}


def main(argv):
    if len(argv) < 2:
        print(__doc__); return 2
    r = verify(Q.load(argv[1]))
    for c in r["checks"]:
        print(f"[{c['result']}] {c['check']}" + (f"  ({c['detail']})" if c["detail"] else ""))
    print(f"\n{r['verdict']}: {r['passed']}/{r['total']} checks, {r['certificates_verified']} certificates verified")
    return 0 if r["verdict"] == "ALL_CHECKS_PASSED" else 1


if __name__ == "__main__":
    sys.exit(main(sys.argv))
