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

    python3 verify_host_loss_bundle.py HOST_LOSS_BUNDLE.json

1. Every check of verify_pbft_qc_bundle.py (votes, quorums, identical chains, tampering controls).
2. One replica per host (no host holds two), in as many regions as hosts.
3. For each single-host-loss phase: every decision committed while that host was down carries NO
   signature from its replicas -- a stopped replica cannot sign -- so the schedule is proven, not stated.
4. The two-hosts-down phase committed nothing, and none of its requests is in any decision chain.
5. Every stated phase result is recomputed from its recorded responses.
"""
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(n, ok, d=""):
        checks.append({"check": n, "result": "PASS" if ok else "FAIL", "detail": d})
    ht = b["host_loss_tests"]
    place = ht["placement"]
    regions = {}
    for n, r in place.items():
        regions.setdefault(r, []).append(n)
    check("one replica per host", all(len(v) == 1 for v in regions.values()) and len(regions) == len(place),
          f"{len(place)} replicas on {len(regions)} hosts: {sorted(regions)}")
    qcs = {}
    for node in b["nodes"].values():
        for e in node["certificates"]:
            qcs.setdefault(e["sequence"], []).append(e)
    signers = lambda q: set(q["bundle"]["individual_signatures"]) | ({q["leader_proposal"]["sender_id"]} if q.get("leader_proposal") else set())
    for p in ht["phases"]:
        allowed = sum(1 for r in p["responses"] if r["decision"] == "ALLOW")
        want_commit = p["expected"] == "COMMIT"
        consistent = (allowed == p["allowed"]) and ((allowed == p["requests"]) if want_commit else (allowed == 0)) == (p["result"] == "PASS")
        check(f"{p['phase']}: stated {p['result']} recomputed ({allowed}/{p['requests']} committed, expected {p['expected']})",
              consistent and p["result"] == "PASS")
        if want_commit:
            down = set(p["stopped_replicas"])
            seqs = [r["sequence"] for r in p["responses"] if r["decision"] == "ALLOW"]
            hit = [s for s in seqs for e in qcs.get(s, []) if signers(e["commit_qc"]) & down]
            check(f"{p['phase']}: {len(seqs)} decisions carry no signature from the stopped host {sorted(down)}",
                  bool(seqs) and not hit, f"sequences {seqs}" if not hit else f"signed by stopped replica: {hit}")
        else:
            res = {"hl/" + r["request_id"] for r in p["responses"]}
            leaked = [s for s, es in qcs.items() for e in es
                      if any(x in json.dumps((e["prepare_qc"].get("leader_proposal") or {}).get("payload"), sort_keys=True) for x in res)]
            check(f"{p['phase']}: none of its requests is in any decision chain (never executed later)", not leaked)
    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))
