"""
Kevros Governance Client — Synchronous + Async HTTP client for the A2A Gateway.

Usage:
    # Synchronous
    from kevros_governance import GovernanceClient
    client = GovernanceClient(api_key="kvrs_...")
    result = client.verify(action_type="trade", action_payload={...}, agent_id="bot-1")

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

from __future__ import annotations

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

from .models import (
    AttestResponse,
    BindIntentResponse,
    BundleResponse,
    GatewayHealth,
    IntentSource,
    IntentType,
    VerifyOutcomeResponse,
    VerifyResponse,
)

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 GovernanceClient:
    """
    Client for the Kevros A2A Governance Gateway.

    Provides both synchronous and async methods for all governance primitives:
    verify, attest, bind, verify_outcome, bundle, health.

    Args:
        api_key: Your Kevros API key (starts with kvrs_).
        base_url: Gateway URL. Defaults to https://governance.taskhawktech.com
        timeout: Request timeout in seconds. Defaults to 30.
    """

    def __init__(
        self,
        api_key: str,
        base_url: str = DEFAULT_BASE_URL,
        timeout: float = _TIMEOUT,
    ):
        self._api_key = api_key
        self._base_url = base_url.rstrip("/")
        self._timeout = timeout
        self._headers = {
            "X-API-Key": api_key,
            "Content-Type": "application/json",
        }
        self._sync_client: Optional[httpx.Client] = None
        self._async_client: Optional[httpx.AsyncClient] = None

    # --- 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 >= 400:
            detail = resp.text
            try:
                detail = resp.json().get("detail", resp.text)
            except Exception:
                pass
            raise GovernanceError(resp.status_code, detail)
        return resp.json()

    # =========================================================================
    # 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", "deploy").
            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

        resp = self._get_sync().post("/governance/verify", json=body)
        return VerifyResponse.from_dict(self._handle_response(resp))

    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

        resp = await self._get_async().post("/governance/verify", json=body)
        return VerifyResponse.from_dict(self._handle_response(resp))

    # =========================================================================
    # 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

        resp = self._get_sync().post("/governance/attest", json=body)
        return AttestResponse.from_dict(self._handle_response(resp))

    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

        resp = await self._get_async().post("/governance/attest", json=body)
        return AttestResponse.from_dict(self._handle_response(resp))

    # =========================================================================
    # 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

        resp = self._get_sync().post("/governance/bind", json=body)
        return BindIntentResponse.from_dict(self._handle_response(resp))

    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

        resp = await self._get_async().post("/governance/bind", json=body)
        return BindIntentResponse.from_dict(self._handle_response(resp))

    # =========================================================================
    # 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

        resp = self._get_sync().post("/governance/bundle", json=body)
        return BundleResponse.from_dict(self._handle_response(resp))

    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

        resp = await self._get_async().post("/governance/bundle", json=body)
        return BundleResponse.from_dict(self._handle_response(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))
