#!/usr/bin/env python3
"""Verify that a hosted CAIN-42 decision was really ordered by a PBFT quorum.

No CAIN imports: needs Python 3.8+, `cryptography`, and verify_pbft_qc_bundle.py
(published next to this file). Trusts no website: the membership (4 Ed25519 keys)
is pinned from membership.json, whose configuration hash must match the one inside
the certificate.

  python3 verify_hosted_decision.py decision.json qc.json membership.json
  python3 verify_hosted_decision.py --live https://cainstudio.online membership.json
      (runs a public demo decision, fetches its certificate, verifies both)

Checks:
  DIGEST     the certificate's proposal digest equals the digest recomputed HERE from
             the decision's own id, commitment and preliminary verdict -- so the
             certificate is for this decision and no other
  QUORUM     the commit certificate verifies: >= 3 distinct pinned signers, domain
             separation, leader binding, configuration hash (verify_pbft_qc_bundle.verify_qc)
  SEQUENCE   the certificate's sequence is the one the decision reports
  GATEWAY    the gateway's own recorded check names the same certificate hash
"""
from __future__ import annotations

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

sys.path.insert(0, str(Path(__file__).resolve().parent))
import verify_pbft_qc_bundle as Q  # noqa: E402


def _get(url, data=None):
    req = urllib.request.Request(url, data=data, method="POST" if data is not None else "GET",
                                 headers={"User-Agent": "cain42-verify-hosted-decision"})
    with urllib.request.urlopen(req, timeout=60) as r:
        return json.loads(r.read())


def verify(decision, qc, membership):
    c = decision.get("consensus") or {}
    mb = Q.Membership(membership["membership"])
    out = []
    op = {"action": "fabric_decision", "resource": f"fabric/{decision.get('decision_id')}",
          "data": {"decision_id": decision.get("decision_id"), "commitment_sha256": c.get("commitment_sha256"),
                   "pre_verdict": c.get("pre_verdict")}}
    want = Q.proposal_request_digest({"operation": op, "request_id": decision.get("decision_id"),
                                      "client_id": "cain-hosted-fabric"})
    out.append(("DIGEST", qc.get("proposal_digest") == want, f"certificate digest {str(qc.get('proposal_digest'))[:16]}, recomputed {want[:16]}"))
    ok, why = Q.verify_qc(qc, mb)
    out.append(("QUORUM", ok, why or f"{len(qc.get('signer_set') or [])} signers: {', '.join(qc.get('signer_set') or [])}"))
    out.append(("SEQUENCE", qc.get("sequence") == c.get("sequence"), f"certificate {qc.get('sequence')}, decision {c.get('sequence')}"))
    g = c.get("certificate_verified_by_gateway") or {}
    out.append(("GATEWAY", bool(g.get("verified")) and g.get("certificate_hash") == Q.qc_hash(qc),
                f"gateway recorded {str(g.get('certificate_hash'))[:16]}, recomputed {Q.qc_hash(qc)[:16]}"))
    return out


def main(argv):
    if len(argv) >= 3 and argv[0] == "--live":
        base, membership = argv[1].rstrip("/"), json.loads(Path(argv[2]).read_text())
        decision = _get(f"{base}/fabric/try?scenario=safe-read", data=b"")
        qc = _get(f"{base}/api/v1/live-cluster/qc/{decision['consensus']['sequence']}")["commit_qc"]
    elif len(argv) == 3:
        decision, qc, membership = (json.loads(Path(p).read_text()) for p in argv)
        qc = qc.get("commit_qc", qc)
    else:
        print(__doc__); return 2
    res = verify(decision, qc, membership)
    print(f"decision {decision.get('decision_id')}  verdict {decision.get('verdict')}  cluster {qc.get('cluster_id')}")
    for name, ok, detail in res:
        print(f"[{'PASS' if ok else 'FAIL'}] {name:9s} {detail}")
    good = all(ok for _, ok, _ in res)
    print("\nVERIFIED: this decision was ordered by a PBFT quorum" if good else "\nINVALID")
    return 0 if good else 1


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