"""
x402 Autonomous Payment — Pay per call with USDC on Base.

Agents with a wallet can pay for governance calls without API keys,
subscriptions, or human intervention. The x402 protocol handles
micropayments natively at the HTTP layer.

Usage:
    from kevros_governance import GovernanceClient

    # Agent with wallet pays per call (no signup, no API key needed)
    client = GovernanceClient(
        agent_id="my-agent",
        wallet_private_key="0xabc...",  # Base wallet with USDC
        payment_mode="x402",
    )
    result = client.verify(action_type="trade", action_payload={...}, agent_id="my-agent")
    # Automatically pays $0.01 USDC per verify call

Requires: eth-account (for EIP-712 signing), x402 SDK (for payment protocol)
Both are optional dependencies — only needed if payment_mode="x402".
"""

from __future__ import annotations

import base64
import json
import logging
from typing import Any, Dict, Optional, Tuple

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

# USDC on Base mainnet
USDC_BASE = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
NETWORK = "eip155:8453"  # Base mainnet (CAIP-2)


class X402PaymentError(Exception):
    """Raised when x402 payment fails."""

    def __init__(self, detail: str, requirements: Optional[Dict] = None):
        self.detail = detail
        self.requirements = requirements or {}
        super().__init__(detail)


class X402WalletNotConfiguredError(X402PaymentError):
    """Raised when x402 payment is needed but no wallet is configured."""

    def __init__(self):
        super().__init__(
            "x402 payment required but no wallet configured. "
            "Pass wallet_private_key to GovernanceClient or set KEVROS_WALLET_KEY env var."
        )


def is_x402_response(data: dict) -> bool:
    """Check if a 402 response is an x402 payment-required response.

    x402 responses have an 'x402Version' field.
    Quota-exceeded 402s from the payment middleware have 'upgrade_url' / 'tiers'.
    """
    return "x402Version" in data


def parse_x402_requirements(data: dict) -> dict:
    """Parse payment requirements from an x402 402 response body."""
    return data


class EvmWalletSigner:
    """EVM wallet signer using eth-account for EIP-712 typed data signing.

    Implements the x402 ClientEvmSigner protocol.
    """

    def __init__(self, private_key: str):
        try:
            from eth_account import Account
        except ImportError:
            raise ImportError(
                "eth-account is required for x402 wallet payments. "
                "Install it: pip install eth-account"
            )
        self._account = Account.from_key(private_key)

    @property
    def address(self) -> str:
        """The wallet address."""
        return self._account.address

    def sign_typed_data(
        self,
        domain: Any,
        types: dict,
        primary_type: str,
        message: dict,
    ) -> bytes:
        """Sign EIP-712 typed data for x402 payment authorization."""
        from eth_account import Account
        from eth_account.messages import encode_typed_data

        # Convert domain to dict if it's a dataclass/pydantic object
        domain_dict = domain if isinstance(domain, dict) else {
            k: v for k, v in (
                ("name", getattr(domain, "name", None)),
                ("version", getattr(domain, "version", None)),
                ("chainId", getattr(domain, "chain_id", None) or getattr(domain, "chainId", None)),
                ("verifyingContract", getattr(domain, "verifying_contract", None) or getattr(domain, "verifyingContract", None)),
                ("salt", getattr(domain, "salt", None)),
            ) if v is not None
        }

        # Convert types to plain dict format
        types_dict = {}
        for type_name, fields in types.items():
            types_dict[type_name] = [
                {"name": f.name if hasattr(f, "name") else f["name"],
                 "type": f.type if hasattr(f, "type") else f["type"]}
                for f in fields
            ]

        # eth-account encode_typed_data: (domain_data, message_types, message_data)
        # or full_message dict with keys: types, primaryType, domain, message
        signable = encode_typed_data(
            full_message={
                "types": types_dict,
                "primaryType": primary_type,
                "domain": domain_dict,
                "message": message,
            }
        )
        signed = Account.sign_message(signable, self._account.key)
        return signed.signature


class X402PaymentClient:
    """Client-side x402 payment handler.

    Creates signed payment payloads from 402 responses using the x402 SDK.
    """

    def __init__(self, private_key: str):
        self._signer = EvmWalletSigner(private_key)
        self._x402_client = None

    @property
    def address(self) -> str:
        """The wallet address used for payments."""
        return self._signer.address

    def _get_client(self):
        """Lazy-init the x402 client."""
        if self._x402_client is not None:
            return self._x402_client
        try:
            from x402.client import x402ClientSync
            from x402.mechanisms.evm.exact import ExactEvmScheme

            self._x402_client = x402ClientSync()
            self._x402_client.register(NETWORK, ExactEvmScheme(signer=self._signer))
            return self._x402_client
        except ImportError:
            raise ImportError(
                "x402 SDK is required for wallet payments. "
                "Install it: pip install x402"
            )

    def create_payment_header(self, response_data: dict) -> str:
        """Create a PAYMENT-SIGNATURE header from a 402 response.

        Args:
            response_data: The JSON body from the 402 response.

        Returns:
            Base64-encoded payment signature header value.

        Raises:
            X402PaymentError: If payment creation fails.
        """
        try:
            from x402 import parse_payment_required
            from x402.http import encode_payment_signature_header

            client = self._get_client()
            payment_required = parse_payment_required(response_data)
            payload = client.create_payment_payload(payment_required)
            return encode_payment_signature_header(payload)
        except ImportError:
            raise
        except Exception as e:
            raise X402PaymentError(
                f"Failed to create payment: {e}",
                requirements=response_data.get("accepts", [{}])[0] if response_data.get("accepts") else {},
            )
