"""
Kevros Governance Client — Agentic Identity Trust.

Your agent needs a verifiable identity. Kevros gives you one: cryptographically
signed, hash-chained, post-quantum attested, independently verifiable.

Usage:
    # Auto-signup (zero config — signs up automatically on first use)
    from kevros_governance import GovernanceClient
    client = GovernanceClient(agent_id="my-agent")
    result = client.verify(action_type="trade", action_payload={...}, agent_id="my-agent")

    # Explicit key
    client = GovernanceClient(api_key="kvrs_...")

    # Async
    async with GovernanceClient(agent_id="my-agent") as client:
        result = await client.averify(action_type="trade", action_payload={...}, agent_id="my-agent")
"""

from __future__ import annotations

import logging
import os

import httpx
from typing import Any, Dict, List, Optional

from .auto_signup import get_or_create_api_key
from ._bootstrap import (
    BootstrapResult,
    DistributionHeartbeat,
    _get_artifact_hash,
    _get_runtime_fingerprint,
    _lock_client,
    _sha256,
    bootstrap_verify,
)
from .models import (
    AttestResponse,
    BindIntentResponse,
    BundleResponse,
    GatewayHealth,
    IntentSource,
    IntentType,
    VerifyCertificateResponse,
    VerifyOutcomeResponse,
    VerifyResponse,
)
from .x402_payment import (
    X402PaymentClient,
    X402PaymentError,
    X402WalletNotConfiguredError,
    is_x402_response,
)

logger = logging.getLogger("kevros.client")

DEFAULT_BASE_URL = "https://governance.taskhawktech.com"
_TIMEOUT = 30.0


class GovernanceError(Exception):
    """Raised when the governance gateway returns an error."""

    def __init__(self, status_code: int, detail: str):
        self.status_code = status_code
        self.detail = detail
        super().__init__(f"HTTP {status_code}: {detail}")


class CapExceededError(GovernanceError):
    """Free tier call limit exceeded. Upgrade for more calls."""

    def __init__(self, detail: str, upgrade_url: str = "", tiers: dict = None):
        super().__init__(402, detail)
        self.upgrade_url = upgrade_url
        self.tiers = tiers or {}


class PaymentRequiredError(GovernanceError):
    """x402 payment required and failed or no wallet configured."""

    def __init__(self, detail: str, requirements: dict = None):
        super().__init__(402, detail)
        self.requirements = requirements or {}


class GovernanceClient:
    """
    Client for Kevros Agentic Identity Trust.

    Build a verifiable identity for your agent: cryptographically signed decisions,
    hash-chained provenance, post-quantum attestations. Independently verifiable
    by any counterparty without contacting Kevros.

    Two payment modes:

    **API Key (default)** — Auto-signup, free tier 100 calls/mo:
        client = GovernanceClient(agent_id="my-agent")

    **x402 Wallet** — Pay per call with USDC on Base, no signup needed:
        client = GovernanceClient(
            agent_id="my-agent",
            wallet_private_key="0xabc...",
            payment_mode="x402",
        )

    Args:
        api_key: Your Kevros API key (starts with kvrs_). Optional if agent_id is provided.
        agent_id: Your agent's identifier. Used for auto-signup if no api_key.
        base_url: Gateway URL. Defaults to https://governance.taskhawktech.com
        timeout: Request timeout in seconds. Defaults to 30.
        payment_mode: "api_key" (default) or "x402" (pay per call with USDC wallet).
        wallet_private_key: Private key for x402 payments (hex string with 0x prefix).
            Also reads from KEVROS_WALLET_KEY env var.
    """

    def __init__(
        self,
        api_key: str = "",
        agent_id: str = "",
        base_url: str = DEFAULT_BASE_URL,
        timeout: float = _TIMEOUT,
        payment_mode: str = "api_key",
        wallet_private_key: str = "",
    ):
        self._base_url = base_url.rstrip("/")
        self._timeout = timeout
        self._payment_mode = payment_mode
        self._x402_client: Optional[X402PaymentClient] = None

        # Initialize x402 wallet if configured
        wallet_key = wallet_private_key or os.environ.get("KEVROS_WALLET_KEY", "")
        if wallet_key:
            self._x402_client = X402PaymentClient(wallet_key)
            if payment_mode != "x402":
                # Wallet provided but mode not set — enable x402 as fallback
                self._payment_mode = "x402_fallback"
            logger.info(
                "x402 wallet configured: %s (mode=%s)",
                self._x402_client.address, self._payment_mode,
            )

        # Resolve API key: explicit > env var > cached > auto-signup
        if payment_mode == "x402" and self._x402_client:
            # Pure x402 mode — no API key needed
            self._api_key = ""
        elif api_key and api_key.startswith("kvrs_"):
            self._api_key = api_key
        elif agent_id:
            self._api_key = get_or_create_api_key(
                agent_id=agent_id, gateway_url=self._base_url,
            )
        else:
            # Try env/cache even without agent_id
            self._api_key = get_or_create_api_key(
                agent_id="anonymous", gateway_url=self._base_url,
            )

        self._headers: Dict[str, str] = {"Content-Type": "application/json"}
        if self._api_key:
            self._headers["X-API-Key"] = self._api_key
        self._sync_client: Optional[httpx.Client] = None
        self._async_client: Optional[httpx.AsyncClient] = None

        # Bootstrap verification (fail-closed distribution authorization)
        self._locked: bool = False
        self._session_id: str = ""
        self._heartbeat: Optional[DistributionHeartbeat] = None

        skip_bootstrap = os.environ.get("KEVROS_SKIP_BOOTSTRAP", "").strip()
        if skip_bootstrap == "1":
            logger.warning(
                "KEVROS_SKIP_BOOTSTRAP=1: bootstrap verification skipped "
                "(development/testing only)"
            )
        else:
            self._run_bootstrap()

    def _run_bootstrap(self) -> None:
        """Execute bootstrap verification and lock the client on failure."""
        result = bootstrap_verify(
            api_key=self._api_key,
            gateway_url=self._base_url,
        )
        if not result.allowed:
            logger.debug("Bootstrap verification denied")
            _lock_client(self)
            return

        self._session_id = result.session_id

        # Start heartbeat daemon thread.
        api_key_hash = _sha256(self._api_key) if self._api_key else ""
        artifact_hash = _get_artifact_hash()
        self._heartbeat = DistributionHeartbeat(
            gateway_url=self._base_url,
            api_key_hash=api_key_hash,
            session_id=self._session_id,
            artifact_hash=artifact_hash,
            runtime_fingerprint=_get_runtime_fingerprint(),
            interval=result.heartbeat_interval,
            lock_callback=lambda: _lock_client(self),
        )
        self._heartbeat.start()

    @property
    def api_key(self) -> str:
        """The resolved API key."""
        return self._api_key

    # --- Context managers ---

    def __enter__(self) -> GovernanceClient:
        self._sync_client = httpx.Client(
            base_url=self._base_url,
            headers=self._headers,
            timeout=self._timeout,
        )
        return self

    def __exit__(self, *args: Any) -> None:
        if self._sync_client:
            self._sync_client.close()
            self._sync_client = None

    async def __aenter__(self) -> GovernanceClient:
        self._async_client = httpx.AsyncClient(
            base_url=self._base_url,
            headers=self._headers,
            timeout=self._timeout,
        )
        return self

    async def __aexit__(self, *args: Any) -> None:
        if self._async_client:
            await self._async_client.aclose()
            self._async_client = None

    # --- Internal HTTP helpers ---

    def _get_sync(self) -> httpx.Client:
        if self._sync_client is None:
            self._sync_client = httpx.Client(
                base_url=self._base_url,
                headers=self._headers,
                timeout=self._timeout,
            )
        return self._sync_client

    def _get_async(self) -> httpx.AsyncClient:
        if self._async_client is None:
            self._async_client = httpx.AsyncClient(
                base_url=self._base_url,
                headers=self._headers,
                timeout=self._timeout,
            )
        return self._async_client

    def _handle_response(self, resp: httpx.Response) -> Dict[str, Any]:
        if resp.status_code == 402:
            data = {}
            detail = "Payment required"
            try:
                data = resp.json()
                detail = data.get("detail", data.get("error", "Payment required"))
            except Exception:
                pass

            # Disambiguate: x402 payment-required vs quota-exceeded
            if is_x402_response(data):
                raise PaymentRequiredError(
                    detail=detail,
                    requirements=data,
                )
            else:
                # Quota-exceeded 402 from payment middleware
                upgrade_url = data.get("upgrade_url", "")
                if not upgrade_url:
                    upgrade_url = data.get("upgrade", {}).get("url", "")
                raise CapExceededError(
                    detail=detail,
                    upgrade_url=upgrade_url,
                    tiers=data.get("tiers", {}),
                )
        if resp.status_code >= 400:
            raise GovernanceError(resp.status_code, f"Request failed (HTTP {resp.status_code})")
        return resp.json()

    def _pay_and_retry_sync(
        self,
        method: str,
        path: str,
        json_body: Optional[Dict[str, Any]] = None,
    ) -> Dict[str, Any]:
        """Make request, handle x402 payment if needed, and return response.

        For x402 mode: sends request without API key → gets 402 → signs payment
        → retries with PAYMENT-SIGNATURE header.

        For x402_fallback mode: only triggers if API key request returns x402 402.
        """
        client = self._get_sync()

        # First attempt
        if method == "POST":
            resp = client.post(path, json=json_body)
        else:
            resp = client.get(path)

        # If not 402, handle normally
        if resp.status_code != 402:
            return self._handle_response(resp)

        # Parse 402 response
        try:
            data = resp.json()
        except Exception:
            return self._handle_response(resp)

        # If it's an x402 response and we have a wallet, pay and retry
        if is_x402_response(data) and self._x402_client:
            try:
                payment_header = self._x402_client.create_payment_header(data)
            except X402PaymentError:
                raise
            except Exception as e:
                raise X402PaymentError(f"Payment signing failed: {e}")

            # Retry with payment signature
            headers = {"PAYMENT-SIGNATURE": payment_header}
            if method == "POST":
                resp2 = client.post(path, json=json_body, headers=headers)
            else:
                resp2 = client.get(path, headers=headers)
            return self._handle_response(resp2)

        # x402 response but no wallet configured
        if is_x402_response(data):
            raise PaymentRequiredError(
                "x402 payment required but no wallet configured. "
                "Pass wallet_private_key to GovernanceClient or set KEVROS_WALLET_KEY env var. "
                "Alternatively, use an API key (payment_mode='api_key').",
                requirements=data,
            )

        # Quota-exceeded (not x402) — standard handling
        return self._handle_response(resp)

    async def _pay_and_retry_async(
        self,
        method: str,
        path: str,
        json_body: Optional[Dict[str, Any]] = None,
    ) -> Dict[str, Any]:
        """Async version of _pay_and_retry_sync."""
        client = self._get_async()

        if method == "POST":
            resp = await client.post(path, json=json_body)
        else:
            resp = await client.get(path)

        if resp.status_code != 402:
            return self._handle_response(resp)

        try:
            data = resp.json()
        except Exception:
            return self._handle_response(resp)

        if is_x402_response(data) and self._x402_client:
            try:
                payment_header = self._x402_client.create_payment_header(data)
            except X402PaymentError:
                raise
            except Exception as e:
                raise X402PaymentError(f"Payment signing failed: {e}")

            headers = {"PAYMENT-SIGNATURE": payment_header}
            if method == "POST":
                resp2 = await client.post(path, json=json_body, headers=headers)
            else:
                resp2 = await client.get(path, headers=headers)
            return self._handle_response(resp2)

        if is_x402_response(data):
            raise PaymentRequiredError(
                "x402 payment required but no wallet configured. "
                "Pass wallet_private_key to GovernanceClient or set KEVROS_WALLET_KEY env var. "
                "Alternatively, use an API key (payment_mode='api_key').",
                requirements=data,
            )

        return self._handle_response(resp)

    # =========================================================================
    # VERIFY — Action verification ($0.01)
    # =========================================================================

    def verify(
        self,
        action_type: str,
        action_payload: Dict[str, Any],
        agent_id: str,
        policy_context: Optional[Dict[str, Any]] = None,
        idempotency_key: Optional[str] = None,
    ) -> VerifyResponse:
        """
        Verify an action against policy bounds before executing it.

        Returns ALLOW (proceed), CLAMP (proceed with modified values), or DENY (stop).
        Every call is recorded in the hash-chained provenance ledger.

        Args:
            action_type: Type of action (e.g., "trade", "api_call", "motor_command").
            action_payload: The action details to verify.
            policy_context: Optional policy constraints (max_values, forbidden_keys).
            agent_id: Your agent's identifier.
            idempotency_key: Optional key for retry safety.

        Returns:
            VerifyResponse with decision, release_token, and provenance_hash.
        """
        body: Dict[str, Any] = {
            "action_type": action_type,
            "action_payload": action_payload,
            "agent_id": agent_id,
        }
        if policy_context is not None:
            body["policy_context"] = policy_context
        if idempotency_key is not None:
            body["idempotency_key"] = idempotency_key

        if self._payment_mode == "x402":
            data = self._pay_and_retry_sync("POST", "/governance/verify", body)
            return VerifyResponse.from_dict(data)
        resp = self._get_sync().post("/governance/verify", json=body)
        try:
            return VerifyResponse.from_dict(self._handle_response(resp))
        except (CapExceededError, PaymentRequiredError):
            if self._x402_client:
                data = self._pay_and_retry_sync("POST", "/governance/verify", body)
                return VerifyResponse.from_dict(data)
            raise

    async def averify(
        self,
        action_type: str,
        action_payload: Dict[str, Any],
        agent_id: str,
        policy_context: Optional[Dict[str, Any]] = None,
        idempotency_key: Optional[str] = None,
    ) -> VerifyResponse:
        """Async version of verify()."""
        body: Dict[str, Any] = {
            "action_type": action_type,
            "action_payload": action_payload,
            "agent_id": agent_id,
        }
        if policy_context is not None:
            body["policy_context"] = policy_context
        if idempotency_key is not None:
            body["idempotency_key"] = idempotency_key

        if self._payment_mode == "x402":
            data = await self._pay_and_retry_async("POST", "/governance/verify", body)
            return VerifyResponse.from_dict(data)
        resp = await self._get_async().post("/governance/verify", json=body)
        try:
            return VerifyResponse.from_dict(self._handle_response(resp))
        except (CapExceededError, PaymentRequiredError):
            if self._x402_client:
                data = await self._pay_and_retry_async("POST", "/governance/verify", body)
                return VerifyResponse.from_dict(data)
            raise

    # =========================================================================
    # ATTEST — Provenance attestation ($0.02)
    # =========================================================================

    def attest(
        self,
        agent_id: str,
        action_description: str,
        action_payload: Dict[str, Any],
        context: Optional[Dict[str, Any]] = None,
        prior_attestation_hash: Optional[str] = None,
    ) -> AttestResponse:
        """
        Create a hash-chained provenance record for an action you've taken.

        Each attestation extends the append-only evidence chain.
        The hash can be independently verified by any third party.

        Args:
            agent_id: Your agent's identifier.
            action_description: What you did (human-readable).
            action_payload: Full action details for the record.
            context: Optional additional context.
            prior_attestation_hash: Your last attestation hash (for agent-side continuity).

        Returns:
            AttestResponse with hash_prev, hash_curr, and chain_length.
        """
        body: Dict[str, Any] = {
            "agent_id": agent_id,
            "action_description": action_description,
            "action_payload": action_payload,
        }
        if context is not None:
            body["context"] = context
        if prior_attestation_hash is not None:
            body["prior_attestation_hash"] = prior_attestation_hash

        if self._payment_mode == "x402":
            data = self._pay_and_retry_sync("POST", "/governance/attest", body)
            return AttestResponse.from_dict(data)
        resp = self._get_sync().post("/governance/attest", json=body)
        try:
            return AttestResponse.from_dict(self._handle_response(resp))
        except (CapExceededError, PaymentRequiredError):
            if self._x402_client:
                data = self._pay_and_retry_sync("POST", "/governance/attest", body)
                return AttestResponse.from_dict(data)
            raise

    async def aattest(
        self,
        agent_id: str,
        action_description: str,
        action_payload: Dict[str, Any],
        context: Optional[Dict[str, Any]] = None,
        prior_attestation_hash: Optional[str] = None,
    ) -> AttestResponse:
        """Async version of attest()."""
        body: Dict[str, Any] = {
            "agent_id": agent_id,
            "action_description": action_description,
            "action_payload": action_payload,
        }
        if context is not None:
            body["context"] = context
        if prior_attestation_hash is not None:
            body["prior_attestation_hash"] = prior_attestation_hash

        if self._payment_mode == "x402":
            data = await self._pay_and_retry_async("POST", "/governance/attest", body)
            return AttestResponse.from_dict(data)
        resp = await self._get_async().post("/governance/attest", json=body)
        try:
            return AttestResponse.from_dict(self._handle_response(resp))
        except (CapExceededError, PaymentRequiredError):
            if self._x402_client:
                data = await self._pay_and_retry_async("POST", "/governance/attest", body)
                return AttestResponse.from_dict(data)
            raise

    # =========================================================================
    # BIND — Intent binding ($0.02)
    # =========================================================================

    def bind(
        self,
        agent_id: str,
        intent_type: IntentType,
        intent_description: str,
        command_payload: Dict[str, Any],
        intent_source: IntentSource = IntentSource.AI_PLANNER,
        goal_state: Optional[Dict[str, Any]] = None,
        max_duration_ms: Optional[float] = None,
        parent_intent_id: Optional[str] = None,
    ) -> BindIntentResponse:
        """
        Declare an intent and cryptographically bind it to a command.

        Proves that the command was issued in service of the declared intent.
        Use verify_outcome() after execution to close the loop.

        Args:
            agent_id: Your agent's identifier.
            intent_type: Category of intent (NAVIGATION, COMMUNICATION, etc.).
            intent_description: What you intend to accomplish.
            command_payload: The command being bound to this intent.
            intent_source: Who/what generated the intent.
            goal_state: Target state to verify against later.
            max_duration_ms: Expected max duration.
            parent_intent_id: Parent intent for hierarchical chains.

        Returns:
            BindIntentResponse with intent_id, binding_hmac, and hashes.
        """
        body: Dict[str, Any] = {
            "agent_id": agent_id,
            "intent_type": intent_type.value,
            "intent_description": intent_description,
            "command_payload": command_payload,
            "intent_source": intent_source.value,
        }
        if goal_state is not None:
            body["goal_state"] = goal_state
        if max_duration_ms is not None:
            body["max_duration_ms"] = max_duration_ms
        if parent_intent_id is not None:
            body["parent_intent_id"] = parent_intent_id

        if self._payment_mode == "x402":
            data = self._pay_and_retry_sync("POST", "/governance/bind", body)
            return BindIntentResponse.from_dict(data)
        resp = self._get_sync().post("/governance/bind", json=body)
        try:
            return BindIntentResponse.from_dict(self._handle_response(resp))
        except (CapExceededError, PaymentRequiredError):
            if self._x402_client:
                data = self._pay_and_retry_sync("POST", "/governance/bind", body)
                return BindIntentResponse.from_dict(data)
            raise

    async def abind(
        self,
        agent_id: str,
        intent_type: IntentType,
        intent_description: str,
        command_payload: Dict[str, Any],
        intent_source: IntentSource = IntentSource.AI_PLANNER,
        goal_state: Optional[Dict[str, Any]] = None,
        max_duration_ms: Optional[float] = None,
        parent_intent_id: Optional[str] = None,
    ) -> BindIntentResponse:
        """Async version of bind()."""
        body: Dict[str, Any] = {
            "agent_id": agent_id,
            "intent_type": intent_type.value,
            "intent_description": intent_description,
            "command_payload": command_payload,
            "intent_source": intent_source.value,
        }
        if goal_state is not None:
            body["goal_state"] = goal_state
        if max_duration_ms is not None:
            body["max_duration_ms"] = max_duration_ms
        if parent_intent_id is not None:
            body["parent_intent_id"] = parent_intent_id

        if self._payment_mode == "x402":
            data = await self._pay_and_retry_async("POST", "/governance/bind", body)
            return BindIntentResponse.from_dict(data)
        resp = await self._get_async().post("/governance/bind", json=body)
        try:
            return BindIntentResponse.from_dict(self._handle_response(resp))
        except (CapExceededError, PaymentRequiredError):
            if self._x402_client:
                data = await self._pay_and_retry_async("POST", "/governance/bind", body)
                return BindIntentResponse.from_dict(data)
            raise

    # =========================================================================
    # VERIFY OUTCOME — Outcome verification (free with bind)
    # =========================================================================

    def verify_outcome(
        self,
        agent_id: str,
        intent_id: str,
        binding_id: str,
        actual_state: Dict[str, Any],
        tolerance: float = 0.1,
    ) -> VerifyOutcomeResponse:
        """
        Verify that an executed action achieved its declared intent.

        Closes the loop: intent -> command -> action -> outcome -> verification.

        Args:
            agent_id: Your agent's identifier.
            intent_id: The intent_id from bind().
            binding_id: The binding_id from bind().
            actual_state: The state after action execution.
            tolerance: Acceptable deviation (0=exact, 1=any). Default 0.1.

        Returns:
            VerifyOutcomeResponse with status and achieved_percentage.
        """
        body = {
            "agent_id": agent_id,
            "intent_id": intent_id,
            "binding_id": binding_id,
            "actual_state": actual_state,
            "tolerance": tolerance,
        }
        resp = self._get_sync().post("/governance/verify-outcome", json=body)
        return VerifyOutcomeResponse.from_dict(self._handle_response(resp))

    async def averify_outcome(
        self,
        agent_id: str,
        intent_id: str,
        binding_id: str,
        actual_state: Dict[str, Any],
        tolerance: float = 0.1,
    ) -> VerifyOutcomeResponse:
        """Async version of verify_outcome()."""
        body = {
            "agent_id": agent_id,
            "intent_id": intent_id,
            "binding_id": binding_id,
            "actual_state": actual_state,
            "tolerance": tolerance,
        }
        resp = await self._get_async().post("/governance/verify-outcome", json=body)
        return VerifyOutcomeResponse.from_dict(self._handle_response(resp))

    # =========================================================================
    # BUNDLE — Compliance package ($0.25)
    # =========================================================================

    def bundle(
        self,
        agent_id: str,
        time_range_start: Optional[str] = None,
        time_range_end: Optional[str] = None,
        include_intent_chains: bool = True,
        include_pqc_signatures: bool = True,
        include_verification_instructions: bool = True,
    ) -> BundleResponse:
        """
        Generate a certifier-grade compliance evidence bundle.

        Contains hash-chained provenance, intent bindings, PQC attestations,
        and verification instructions. Independently verifiable without Kevros access.

        Args:
            agent_id: Your agent's identifier.
            time_range_start: ISO 8601 start (default: all records).
            time_range_end: ISO 8601 end (default: now).
            include_intent_chains: Include intent binding proofs.
            include_pqc_signatures: Include PQC block references.
            include_verification_instructions: Include verification procedure.

        Returns:
            BundleResponse with records, chain_integrity, and bundle_hash.
        """
        body: Dict[str, Any] = {
            "agent_id": agent_id,
            "include_intent_chains": include_intent_chains,
            "include_pqc_signatures": include_pqc_signatures,
            "include_verification_instructions": include_verification_instructions,
        }
        if time_range_start is not None:
            body["time_range_start"] = time_range_start
        if time_range_end is not None:
            body["time_range_end"] = time_range_end

        if self._payment_mode == "x402":
            data = self._pay_and_retry_sync("POST", "/governance/bundle", body)
            return BundleResponse.from_dict(data)
        resp = self._get_sync().post("/governance/bundle", json=body)
        try:
            return BundleResponse.from_dict(self._handle_response(resp))
        except (CapExceededError, PaymentRequiredError):
            if self._x402_client:
                data = self._pay_and_retry_sync("POST", "/governance/bundle", body)
                return BundleResponse.from_dict(data)
            raise

    async def abundle(
        self,
        agent_id: str,
        time_range_start: Optional[str] = None,
        time_range_end: Optional[str] = None,
        include_intent_chains: bool = True,
        include_pqc_signatures: bool = True,
        include_verification_instructions: bool = True,
    ) -> BundleResponse:
        """Async version of bundle()."""
        body: Dict[str, Any] = {
            "agent_id": agent_id,
            "include_intent_chains": include_intent_chains,
            "include_pqc_signatures": include_pqc_signatures,
            "include_verification_instructions": include_verification_instructions,
        }
        if time_range_start is not None:
            body["time_range_start"] = time_range_start
        if time_range_end is not None:
            body["time_range_end"] = time_range_end

        if self._payment_mode == "x402":
            data = await self._pay_and_retry_async("POST", "/governance/bundle", body)
            return BundleResponse.from_dict(data)
        resp = await self._get_async().post("/governance/bundle", json=body)
        try:
            return BundleResponse.from_dict(self._handle_response(resp))
        except (CapExceededError, PaymentRequiredError):
            if self._x402_client:
                data = await self._pay_and_retry_async("POST", "/governance/bundle", body)
                return BundleResponse.from_dict(data)
            raise

    # =========================================================================
    # PEER TRUST — Verify other agents (free, no API key needed)
    # =========================================================================

    def verify_peer(self, agent_id: str) -> Dict[str, Any]:
        """
        Check another agent's trust score and provenance history.

        Free, no API key needed. Calls GET /governance/reputation/{agent_id}.

        Args:
            agent_id: The agent to look up.

        Returns:
            dict with agent_id, trust_score, chain_length, attestation_count, etc.
        """
        resp = self._get_sync().get(f"/governance/reputation/{agent_id}")
        if resp.status_code == 404:
            return {"error": "not_found", "detail": f"No records for agent: {agent_id}"}
        return self._handle_response(resp)

    async def averify_peer(self, agent_id: str) -> Dict[str, Any]:
        """Async version of verify_peer()."""
        resp = await self._get_async().get(f"/governance/reputation/{agent_id}")
        if resp.status_code == 404:
            return {"error": "not_found", "detail": f"No records for agent: {agent_id}"}
        return self._handle_response(resp)

    def verify_peer_token(self, release_token: str, token_preimage: str) -> Dict[str, Any]:
        """
        Verify a release token from another agent.

        Free, no API key needed. Calls POST /governance/verify-token.

        Args:
            release_token: HMAC digest from a verify() response.
            token_preimage: The token preimage (provided alongside the release token).

        Returns:
            dict with valid, decision, epoch, chain_found.
        """
        resp = self._get_sync().post("/governance/verify-token", json={
            "release_token": release_token,
            "token_preimage": token_preimage,
        })
        return self._handle_response(resp)

    async def averify_peer_token(self, release_token: str, token_preimage: str) -> Dict[str, Any]:
        """Async version of verify_peer_token()."""
        resp = await self._get_async().post("/governance/verify-token", json={
            "release_token": release_token,
            "token_preimage": token_preimage,
        })
        return self._handle_response(resp)

    def verify_certificate(self, bundle: Dict[str, Any]) -> VerifyCertificateResponse:
        """
        Verify a trust certificate bundle independently.

        Replays the hash chain, checks PQC block signatures, and verifies
        bundle integrity. Free and public — no API key required.

        Args:
            bundle: The full bundle object from bundle().

        Returns:
            VerifyCertificateResponse with valid, chain_integrity, and pqc_signatures_valid.
        """
        resp = self._get_sync().post(
            "/governance/verify-certificate", json={"bundle": bundle}
        )
        return VerifyCertificateResponse.from_dict(self._handle_response(resp))

    async def averify_certificate(self, bundle: Dict[str, Any]) -> VerifyCertificateResponse:
        """Async version of verify_certificate()."""
        resp = await self._get_async().post(
            "/governance/verify-certificate", json={"bundle": bundle}
        )
        return VerifyCertificateResponse.from_dict(self._handle_response(resp))

    def build_trust_headers(
        self,
        action_type: str,
        action_payload: Dict[str, Any],
        agent_id: str,
    ) -> Dict[str, str]:
        """
        Get trust headers to attach to outbound HTTP requests.

        Calls verify() to get a release token, then reconstructs the preimage
        and returns headers that downstream services can verify.

        Args:
            action_type: Type of action being taken.
            action_payload: The action details.
            agent_id: Your agent identifier.

        Returns:
            dict of HTTP headers: X-Kevros-Release-Token, X-Kevros-Token-Preimage,
            X-Kevros-Agent-Id.
        """
        result = self.verify(action_type, action_payload, agent_id)
        if result.release_token and result.hash_prev:
            import hashlib, json as _json
            record = {
                "verification_id": result.verification_id,
                "epoch": result.epoch,
                "decision": result.decision.value,
                "agent_id": agent_id,
                "action_type": action_type,
                "timestamp_utc": result.timestamp_utc,
            }
            record_bytes = _json.dumps(
                record, sort_keys=True, separators=(",", ":"), ensure_ascii=False,
            ).encode("utf-8")
            record_hash = hashlib.sha256(record_bytes).hexdigest()
            preimage = f"KEVROSv1|{result.decision.value}|{result.epoch}|{result.hash_prev}|{record_hash}"
            return {
                "X-Kevros-Release-Token": result.release_token,
                "X-Kevros-Token-Preimage": preimage,
                "X-Kevros-Agent-Id": agent_id,
            }
        return {"X-Kevros-Agent-Id": agent_id}

    def trusted_request(
        self,
        method: str,
        url: str,
        agent_id: str,
        action_type: str = "http_request",
        action_payload: Optional[Dict[str, Any]] = None,
        **kwargs: Any,
    ) -> "httpx.Response":
        """
        Make an HTTP request with auto-remediation on 403 kevros_trust_required.

        If the target returns 403 with error=kevros_trust_required, automatically
        calls verify(), attaches trust headers, and retries once.

        Args:
            method: HTTP method (GET, POST, etc.).
            url: Target URL.
            agent_id: Your agent identifier.
            action_type: Action type for verify().
            action_payload: Action details for verify().
            **kwargs: Passed to httpx.request().

        Returns:
            httpx.Response from the target.
        """
        with httpx.Client(timeout=self._timeout) as c:
            resp = c.request(method, url, **kwargs)
            if resp.status_code == 403:
                try:
                    body = resp.json()
                except Exception:
                    return resp
                if body.get("error") == "kevros_trust_required":
                    trust_headers = self.build_trust_headers(
                        action_type, action_payload or {}, agent_id,
                    )
                    headers = dict(kwargs.pop("headers", {}) or {})
                    headers.update(trust_headers)
                    resp = c.request(method, url, headers=headers, **kwargs)
            return resp

    # =========================================================================
    # HEALTH — Gateway health check (free)
    # =========================================================================

    def health(self) -> GatewayHealth:
        """Check governance gateway health."""
        resp = self._get_sync().get("/governance/health")
        return GatewayHealth.from_dict(self._handle_response(resp))

    async def ahealth(self) -> GatewayHealth:
        """Async version of health()."""
        resp = await self._get_async().get("/governance/health")
        return GatewayHealth.from_dict(self._handle_response(resp))
