#!/usr/bin/env python3
"""Clean-room verifier for the CAIN-42 E8 hosted action-commit proof bundle.

Imports NO CAIN code. Needs Python 3.8+ and `cryptography`. It:
  1. checks every file in MANIFEST.json against its SHA-256;
  2. re-verifies each governance token's Ed25519 signature over its canonical body;
  3. recomputes each action hash and checks it binds the recorded body;
  4. re-checks the kernel evidence hash-chain (links + entry hashes);
  5. checks every case reached its expected outcome and the 30 E8 invariants hold.

Usage: python3 verify_e8_hosted_commit.py <bundle-dir>
"""
from __future__ import annotations

import base64
import hashlib
import json
import sys
from pathlib import Path

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


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


def h(o) -> str:
    return hashlib.sha256(canon(o)).hexdigest()


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


def sig_ok(pub_b64: str, sig_b64: str, domain: str, fields: dict) -> bool:
    try:
        Ed25519PublicKey.from_public_bytes(base64.b64decode(pub_b64)).verify(
            base64.b64decode(sig_b64), digest(domain, fields).encode())
        return True
    except (InvalidSignature, ValueError, TypeError):
        return False


def verify(base: Path) -> list:
    out = []
    manifest = json.loads((base / "MANIFEST.json").read_text())
    run = json.loads((base / "E8_HOSTED_COMMIT_RUN.json").read_text())
    pub = run["public_key_b64"]
    D_TOKEN, D_ACTION, D_EVID = run["domains"]["token"], run["domains"]["action"], run["domains"]["evidence"]

    bad = [n for n, want in manifest["files"].items() if hashlib.sha256((base / n).read_bytes()).hexdigest() != want]
    out.append(("MANIFEST", not bad, f"{len(manifest['files'])} files" + (f"; mismatched {bad}" if bad else "")))

    token_checked = action_checked = 0
    tok_problems = []
    for case in run["cases"]:
        token = case.get("token")
        action = case.get("action")
        if token:
            token_checked += 1
            if not sig_ok(token["issuer"], token["signature_b64"], D_TOKEN, token["body"]):
                tok_problems.append(f"{case['case']}: token signature invalid")
            if token["issuer"] != pub:
                tok_problems.append(f"{case['case']}: issuer != published key")
        if action:
            action_checked += 1
            if digest(D_ACTION, action["body"]) != action["action_hash"]:
                tok_problems.append(f"{case['case']}: action hash mismatch")
        receipt = case.get("receipt") or {}
        if receipt.get("hosted_e8_commit") == "COMMITTED":
            token_checked += 1
            if not sig_ok(receipt["issuer"], receipt["token_signature_b64"], D_TOKEN, receipt["token_body"]):
                tok_problems.append(f"{case['case']}: receipt token signature invalid")
            action_checked += 1
            if digest(D_ACTION, receipt["action_body"]) != receipt["action_hash"]:
                tok_problems.append(f"{case['case']}: receipt action hash mismatch")
    out.append(("SIGNATURES", not tok_problems,
                f"{token_checked} tokens, {action_checked} action hashes" + (f"; {tok_problems[:4]}" if tok_problems else "")))

    chain_problems, prev = [], "0" * 64
    for i, e in enumerate(run["evidence_chain"]):
        body = {k: v for k, v in e.items() if k != "entry_hash"}
        if body.get("prev") != prev:
            chain_problems.append(f"entry {i}: broken link")
        if digest(D_EVID, body) != e.get("entry_hash"):
            chain_problems.append(f"entry {i}: hash mismatch")
        prev = e["entry_hash"]
    out.append(("EVIDENCE_CHAIN", not chain_problems,
                f"{len(run['evidence_chain'])} entries" + (f"; {chain_problems[:3]}" if chain_problems else "")))

    case_bad = [c["case"] for c in run["cases"] if not c["ok"]]
    out.append(("CASES", not case_bad, f"{len(run['cases']) - len(case_bad)}/{len(run['cases'])} hold"
                + (f"; failed {case_bad}" if case_bad else "")))

    inv = run["invariants"]
    out.append(("INVARIANTS", inv.get("all_hold") is True and inv.get("checked") == 30,
                f"{inv.get('checked')} checked, failed {inv.get('failed')}"))
    return out


def main(argv) -> int:
    if len(argv) != 1:
        print(__doc__)
        return 2
    res = verify(Path(argv[0]))
    for name, ok, detail in res:
        print(f"[{'PASS' if ok else 'FAIL'}] {name:15s} {detail}")
    good = all(ok for _, ok, _ in res)
    print(f"\n{sum(ok for _, ok, _ in res)}/{len(res)} checks")
    print("VERIFIED" if good else "NOT VERIFIED")
    return 0 if good else 1


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