#!/usr/bin/env python3
"""Clean-room verifier for the live hosted-evolution run (CAIN42_L5_HOSTED_EVOLUTION_LIVE_RUN.json).

INDEPENDENT IMPLEMENTATION: imports NOTHING from CAIN (standard library + `cryptography`).

Checks, from the published files alone:
  1. the enforcement event chain: order, links, payload hashes, event hashes and every Ed25519 signature under the
     governance key; it contains EVOLUTION_DECISION / EVOLUTION_DEPLOYED / EVOLUTION_ROLLED_BACK events;
  2. every EVOLUTION_DEPLOYED / ROLLED_BACK event changes the capability commitment, names the leases it revoked,
     and every DEPLOYED event names a registered operator approval that is not the proposing agent;
  3. every cluster certification is verified, by >= quorum distinct signers, and certifies exactly the commitment
     the event chain says was deployed at that point;
  4. every case is as expected, and every refusal names a reason;
  5. the export (the hosted gate's records) exists and its decisions match the DECISION events one for one.
     (Recomputing the decisions themselves is verify_adaptive_bundle.py's job, run on the export.)
With --live: the governance key published at /fabric/mcp/system/key on all three domains equals the run's, and
for every certification sequence the cluster's OWN record (/api/v1/live-cluster/qc/{seq}) contains the certified
capability commitment in the ordered operation.

    python3 verify_hosted_evolution_run.py CAIN42_L5_HOSTED_EVOLUTION_LIVE_RUN.json [--live]
"""
from __future__ import annotations

import base64
import hashlib
import json
import sys
import urllib.request
from pathlib import Path

from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives.asymmetric import ed25519

D_SYSEVENT = "CAIN42/CAG-L5-SYSTEM-EVENT/v1"
SITES = ["https://cainstudio.online", "https://mcpgate.online", "https://clawx.click"]


def C(o) -> bytes:
    return json.dumps(o, sort_keys=True, separators=(",", ":"), ensure_ascii=True).encode()


def H(o) -> str:
    return hashlib.sha256(C(o)).hexdigest()


def digest(domain, fields) -> str:
    return H({"domain": domain, **fields})


def vsig(pub, sig, domain, fields) -> bool:
    try:
        ed25519.Ed25519PublicKey.from_public_bytes(base64.b64decode(pub)).verify(
            base64.b64decode(sig), digest(domain, fields).encode())
        return True
    except (InvalidSignature, ValueError, TypeError):
        return False


def get(url):
    req = urllib.request.Request(url, headers={"User-Agent": "cain42-clean-room-verifier"})
    with urllib.request.urlopen(req, timeout=30) as r:
        return json.loads(r.read())


def verify(doc: dict, export: dict, live: bool) -> dict:
    fails, checks = [], 0

    def check(ok, msg):
        nonlocal checks
        checks += 1
        if not ok:
            fails.append(msg)

    gov = doc["governance_public_key"]
    evs = doc["events"]
    prev = ""
    for i, e in enumerate(evs):
        body = {k: e[k] for k in ("seq", "kind", "payload_hash", "prev", "system_digest")}
        check(e["seq"] == i and e["prev"] == prev, f"event {i}: order/link")
        check(H(e["payload"]) == e["payload_hash"], f"event {i}: payload hash")
        check(digest(D_SYSEVENT, body) == e["event_hash"], f"event {i}: event hash")
        check(vsig(gov, e["signature"], D_SYSEVENT, body), f"event {i}: signature")
        prev = e["event_hash"]
    kinds = [e["kind"] for e in evs]
    for k in ("EVOLUTION_DECISION", "EVOLUTION_DEPLOYED", "EVOLUTION_ROLLED_BACK"):
        check(k in kinds, f"no {k} event")
    agents = set(export["roots"]["agents"])
    operators = export["roots"]["operators"]
    changes = [e for e in evs if e["kind"] in ("EVOLUTION_DEPLOYED", "EVOLUTION_ROLLED_BACK")]
    for e in changes:
        p = e["payload"]
        check(p["old_capability_commitment"] != p["new_capability_commitment"],
              f"event {e['seq']}: {e['kind']} did not change the capability commitment")
        check(isinstance(p.get("leases_revoked"), list) and p["leases_revoked"], f"event {e['seq']}: no leases revoked")
        if e["kind"] == "EVOLUTION_DEPLOYED":
            appr = p.get("approvals") or []
            check(bool(appr) and all(a in operators and a not in agents for a in appr),
                  f"event {e['seq']}: deployed without a registered non-agent operator approval")
        else:
            check(p.get("by") in operators, f"event {e['seq']}: rollback not by a registered operator")
    # certifications: the first is the registration; each later one certifies the commitment deployed at that point
    certs = doc["cluster_certifications"]
    for c in certs:
        check(c.get("verified") is True, f"certification seq {c.get('sequence')}: not verified")
        check(len(set(c.get("signers") or [])) >= (c.get("quorum") or 99), f"certification seq {c.get('sequence')}: quorum")
    # the chain is append-only and keeps earlier runs for this tenant: this run's changes are its last len(certs)-1
    run_changes = changes[-(len(certs) - 1):] if len(certs) > 1 else []
    check(len(run_changes) == len(certs) - 1, "fewer deploy/rollback events than certifications")
    check([c["capability_commitment"] for c in certs[1:]] == [e["payload"]["new_capability_commitment"]
                                                             for e in run_changes],
          "certified commitments differ from the commitments the event chain deployed")
    for x in doc["cases"]:
        check(x["as_expected"] is True and x["allowed"] is x["expected_allowed"], f"case {x['case']} not as expected")
        if x["allowed"] is False:
            check(bool(x.get("system_reasons") or x.get("l5_reasons") or x.get("reason")), f"case {x['case']}: no reason")
    dec_events = [e["payload"] for e in evs if e["kind"] == "EVOLUTION_DECISION"]
    recs = [r["decision"] for r in export["gate_records"]]
    tail = dec_events[-len(recs):] if recs else []
    check(len(tail) == len(recs) and all(a["decision_hash"] == H(b) and a["decision"] == b["decision"]
                                         for a, b in zip(tail, recs)),
          "export decisions do not match the EVOLUTION_DECISION events")
    live_out = None
    if live:
        live_out = {}
        for s in SITES:
            k = get(f"{s}/fabric/mcp/system/key")["governance_public_key"]
            check(k == gov, f"live: {s} publishes a different governance key")
        for c in certs:
            rec = json.dumps(get(f"{SITES[0]}/api/v1/live-cluster/qc/{c['sequence']}?cluster={c['cluster_id']}"))
            ok = c["capability_commitment"] in rec and c["state_digest"] in rec
            live_out[str(c["sequence"])] = ok
            check(ok, f"live: cluster record for seq {c['sequence']} does not carry the certified commitment/state")
    return {"verdict": "VALID" if not fails else "INVALID", "checks": checks, "failures": fails,
            "events": len(evs), "certifications": len(certs), "cases": len(doc["cases"]), "live": live_out}


def main() -> int:
    args = [a for a in sys.argv[1:] if not a.startswith("--")]
    if not args:
        print(__doc__)
        return 2
    p = Path(args[0])
    doc = json.loads(p.read_text())
    export = json.loads((p.parent / doc["clean_room_verification"]["export"]).read_text())
    out = verify(doc, export, "--live" in sys.argv)
    print(json.dumps(out, indent=1))
    return 0 if out["verdict"] == "VALID" else 1


if __name__ == "__main__":
    raise SystemExit(main())
