#!/usr/bin/env python3
"""CAIN-42 L5 identity/authority clean-room verifier (Prompt 2, Part 41).

INDEPENDENT: imports NO CAIN code. Re-implements canonical JSON, the Ed25519 check, the authority
intersection and the delegation-scope rule, so it can catch a bundle produced by a broken engine.

    python3 scripts/cain42_l5/verify_authority_bundle.py CAIN42_L5_AUTHORITY_BUNDLE.json

Verdict: VALID / INVALID / INCOMPLETE. Only VALID means every check passed.
"""
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 import ed25519

D_IDENTITY = "CAIN42/L5-AGENT-IDENTITY/v1"
D_GRANT = "CAIN42/L5-AUTHORITY-GRANT/v1"
D_DELEGATION = "CAIN42/L5-DELEGATION-GRANT/v1"
D_DECISION = "CAIN42/L5-AUTHORIZATION-DECISION/v1"

IDENTITY_FIELDS = (
    "identity_version", "agent_id", "agent_type", "model_family", "model_identifier", "model_version",
    "runtime_identifier", "runtime_version", "owner_principal", "organization", "created_at", "expires_at",
    "status", "capabilities", "public_key", "key_algorithm", "attestation_reference", "policy_binding",
    "trust_reference", "delegation_root", "issuer", "issued_at",
)
GRANT_FIELDS = (
    "grant_id", "principal_id", "agent_id", "issuer_id", "parent_grant_id", "scope", "capabilities",
    "resources", "operations", "constraints", "risk_limit", "budget_limit", "delegation_limit",
    "time_start", "time_expiry", "max_duration", "renewal_policy", "policy_version", "trust_floor",
    "environment_constraints", "revocation_reference", "single_use", "status",
)
DELEGATION_FIELDS = ("delegation_id", "delegator", "delegate", "parent_grant_id", "delegated_capabilities",
                     "delegated_resources", "constraints", "depth", "maximum_depth", "time_start",
                     "time_expiry", "policy_version")
DECISION_FIELDS = ("decision_id", "action_id", "agent_id", "requested_capability", "resource", "operation",
                   "effective_authority", "policy_version", "trust_state", "delegation_chain", "risk_state",
                   "decision", "reason_codes", "constraints", "timestamp", "expiry", "action_hash",
                   "parameters_hash", "resource_hash", "context_hash", "policy_hash", "evidence_reference")


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


def digest(domain: str, fields: dict) -> str:
    return hashlib.sha256(canon({"domain": domain, **fields})).hexdigest()


def verify_sig(pub: str, sig: str, domain: str, fields: dict) -> 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 body_of(obj: dict, fields) -> dict:
    out = {}
    for name in fields:
        value = obj.get(name)
        out[name] = sorted(value) if isinstance(value, list) else value
    return out


def pattern_within(child: str, parent: str) -> bool:
    return child == parent or (parent.endswith("*") and child.startswith(parent[:-1]))


def set_within(a, b) -> bool:
    return all(any(pattern_within(x, y) for y in b) for x in a)


def main() -> int:
    if len(sys.argv) != 2:
        print("usage: verify_authority_bundle.py <bundle.json>", file=sys.stderr)
        return 2
    bundle = json.loads(Path(sys.argv[1]).read_text())
    issuer_pub = bundle.get("issuer_public_key", "")
    checks = []

    def check(name, ok, detail=""):
        checks.append({"check": name, "ok": bool(ok), "detail": detail})

    identity = body_of(bundle.get("identity", {}), IDENTITY_FIELDS)
    grant = body_of(bundle.get("grant", {}), GRANT_FIELDS)
    delegation = body_of(bundle.get("delegation", {}), DELEGATION_FIELDS)
    decision = body_of(bundle.get("decision", {}), DECISION_FIELDS)
    revoked = body_of(bundle.get("revoked_grant", {}), GRANT_FIELDS)
    revoked_decision = body_of(bundle.get("revoked_decision", {}), DECISION_FIELDS)

    # identity
    check("identity.signature", verify_sig(issuer_pub, bundle["identity"].get("identity_signature", ""),
                                           D_IDENTITY, identity))
    check("identity.digest", digest(D_IDENTITY, identity) == bundle["identity"].get("digest"))
    check("identity.public_key_binding", identity.get("public_key") == bundle.get("agent_public_key"))
    # grant
    check("grant.signature", verify_sig(issuer_pub, bundle["grant"].get("signature", ""), D_GRANT, grant))
    check("grant.digest", digest(D_GRANT, grant) == bundle["grant"].get("digest"))
    # delegation
    check("delegation.signature", verify_sig(issuer_pub, bundle["delegation"].get("signature", ""),
                                             D_DELEGATION, delegation))
    check("delegation.digest", digest(D_DELEGATION, delegation) == bundle["delegation"].get("digest"))
    check("delegation.cannot_escalate_capabilities",
          set(delegation.get("delegated_capabilities", [])) <= set(grant.get("capabilities", [])))
    check("delegation.cannot_escalate_resources",
          set_within(delegation.get("delegated_resources", []), grant.get("resources", [])))
    check("delegation.depth_within_limit", delegation.get("depth", 1) <= delegation.get("maximum_depth", 0))
    check("delegation.within_parent_expiry",
          (delegation.get("time_expiry") or 0) <= (grant.get("time_expiry") or float("inf")))
    # decision
    check("decision.signature", verify_sig(issuer_pub, bundle["decision"].get("signature", ""), D_DECISION, decision))
    check("decision.digest", digest(D_DECISION, decision) == bundle["decision"].get("digest"))
    check("decision.is_allow", decision.get("decision") == "ALLOW")
    check("decision.action_bound", bool(decision.get("action_hash")))
    check("decision.binds_grant_scope", decision.get("requested_capability") in grant.get("capabilities", []))
    # independent intersection: capabilities are intersected; resources take the narrower delegation set
    eff = decision.get("effective_authority", {}) or {}
    expected_caps = sorted(set(grant.get("capabilities", [])) & set(delegation.get("delegated_capabilities", [])))
    check("decision.effective_capabilities_intersect", sorted(eff.get("capabilities", [])) == expected_caps,
          f"{eff.get('capabilities')} vs {expected_caps}")
    check("decision.effective_resources_narrowest",
          sorted(eff.get("resources", [])) == sorted(set(delegation.get("delegated_resources", []))))
    check("decision.trust_floor_is_max", eff.get("trust_floor") == grant.get("trust_floor"))
    # revocation must deny
    check("revoked_grant.signed", verify_sig(issuer_pub, bundle["revoked_grant"].get("signature", ""), D_GRANT, revoked))
    check("revoked_decision.denied", revoked_decision.get("decision") != "ALLOW")
    check("revoked_decision.reason", "DENY_REVOKED_PRINCIPAL" in (revoked_decision.get("reason_codes") or []))

    failed = [c["check"] for c in checks if not c["ok"]]
    verdict = "VALID" if not failed else "INVALID"
    print(json.dumps({"bundle": Path(sys.argv[1]).name, "schema": bundle.get("schema"), "verdict": verdict,
                      "checks_passed": sum(1 for c in checks if c["ok"]), "checks_failed": len(failed),
                      "failed": failed, "checks": checks, "clean_room": True, "imports_cain": False}, indent=2))
    return 0 if verdict == "VALID" else 1


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