"""x402 payment support for Skytale channels.

Provides :class:`PaymentConfig` for automatic x402 payment handling.
When the API returns HTTP 402 Payment Required, the SDK parses the
``PAYMENT-REQUIRED`` header, signs an EIP-712 authorization, and
retries with a ``PAYMENT-SIGNATURE`` header.

Usage::

    from skytale_sdk.payment import PaymentConfig

    config = PaymentConfig(
        wallet_key="0xprivate_key_hex",
        network="base-sepolia",
        max_per_message="0.01",
    )
"""

from __future__ import annotations

import hashlib
import json
import struct
import time
from dataclasses import dataclass, field
from typing import Optional


@dataclass
class PaymentConfig:
    """Configuration for automatic x402 payment on channel operations.

    Args:
        wallet_key: Hex-encoded private key (with or without ``0x`` prefix).
            Used to sign EIP-712 payment authorizations.
        network: Blockchain network for settlement
            (e.g. ``"base-sepolia"``, ``"avalanche-mainnet"``).
        max_per_message: Maximum USDC to pay per message/operation (e.g. ``"0.01"``).
            Operations exceeding this limit raise ``PaymentLimitExceeded``.
        auto_pay: If ``True`` (default), automatically sign and retry on 402.
            If ``False``, raise ``PaymentRequired`` and let the caller handle it.
    """

    wallet_key: str
    network: str = "base-sepolia"
    max_per_message: str = "0.01"
    auto_pay: bool = True

    def __post_init__(self):
        # Strip 0x prefix for consistent internal handling
        if self.wallet_key.startswith("0x") or self.wallet_key.startswith("0X"):
            self.wallet_key = self.wallet_key[2:]

    @property
    def max_per_message_minor(self) -> int:
        """Convert max_per_message to USDC minor units (6 decimals)."""
        parts = self.max_per_message.split(".")
        whole = int(parts[0]) * 1_000_000
        if len(parts) > 1:
            frac = parts[1][:6].ljust(6, "0")
            whole += int(frac)
        return whole


class PaymentRequired(Exception):
    """Raised when an API request requires x402 payment.

    Attributes:
        amount: Payment amount in minor units.
        network: Settlement network.
        token: Token address (USDC).
        recipient: Recipient wallet address.
        raw: Raw payment requirement dict from the API.
    """

    def __init__(self, raw: dict):
        self.raw = raw
        self.amount = raw.get("maxAmountRequired", 0)
        self.network = raw.get("network", "")
        self.token = raw.get("asset", "")
        self.recipient = raw.get("payTo", "")
        super().__init__(
            f"Payment required: {self.amount} on {self.network} to {self.recipient}"
        )


class PaymentLimitExceeded(Exception):
    """Raised when a payment exceeds the configured max_per_message limit.

    Attributes:
        requested: Amount requested in minor units.
        limit: Configured limit in minor units.
    """

    def __init__(self, requested: int, limit: int):
        self.requested = requested
        self.limit = limit
        super().__init__(
            f"Payment {requested} exceeds limit {limit}"
        )


def parse_payment_required(header_value: str) -> dict:
    """Parse a base64-encoded PAYMENT-REQUIRED header.

    Args:
        header_value: Base64-encoded JSON from the ``PAYMENT-REQUIRED`` header.

    Returns:
        Parsed payment requirement dict with keys:
        ``scheme``, ``network``, ``maxAmountRequired``, ``asset``, ``payTo``, ``extra``.
    """
    import base64

    decoded = base64.b64decode(header_value)
    return json.loads(decoded)


def sign_payment(config: PaymentConfig, requirement: dict) -> str:
    """Sign an x402 payment authorization using EIP-712.

    Requires ``eth_account`` package. Install with::

        pip install skytale-sdk[x402]

    Args:
        config: Payment configuration with wallet key.
        requirement: Parsed payment requirement from the API.

    Returns:
        Base64-encoded payment signature for the ``PAYMENT-SIGNATURE`` header.

    Raises:
        PaymentLimitExceeded: If the required amount exceeds ``config.max_per_message``.
    """
    import base64

    amount = requirement.get("maxAmountRequired", 0)
    if amount > config.max_per_message_minor:
        raise PaymentLimitExceeded(amount, config.max_per_message_minor)

    from eth_account import Account
    from eth_account.messages import encode_typed_data

    # EIP-712 typed data for x402 payment authorization
    domain = {
        "name": "x402",
        "version": "2",
        "chainId": _chain_id_for_network(requirement.get("network", config.network)),
    }

    message = {
        "scheme": requirement.get("scheme", "exact"),
        "network": requirement.get("network", config.network),
        "token": requirement.get("asset", ""),
        "amount": str(amount),
        "payTo": requirement.get("payTo", ""),
        "nonce": str(int(time.time() * 1000)),
        "validUntil": str(int(time.time()) + 300),  # 5 min validity
    }

    types = {
        "EIP712Domain": [
            {"name": "name", "type": "string"},
            {"name": "version", "type": "string"},
            {"name": "chainId", "type": "uint256"},
        ],
        "Payment": [
            {"name": "scheme", "type": "string"},
            {"name": "network", "type": "string"},
            {"name": "token", "type": "address"},
            {"name": "amount", "type": "string"},
            {"name": "payTo", "type": "address"},
            {"name": "nonce", "type": "string"},
            {"name": "validUntil", "type": "string"},
        ],
    }

    acct = Account.from_key(bytes.fromhex(config.wallet_key))
    signable = encode_typed_data(
        domain_data=domain,
        message_types={"Payment": types["Payment"]},
        message_data=message,
    )
    signed = acct.sign_message(signable)

    payload = {
        **message,
        "from": acct.address,
        "signature": signed.signature.hex(),
    }

    return base64.b64encode(json.dumps(payload).encode()).decode()


# Network to chain ID mapping
_CHAIN_IDS = {
    "base-sepolia": 84532,
    "base-mainnet": 8453,
    "avalanche-mainnet": 43114,
    "avalanche-fuji": 43113,
    "ethereum-mainnet": 1,
    "ethereum-sepolia": 11155111,
}


def _chain_id_for_network(network: str) -> int:
    """Resolve a CAIP-2 network name to a chain ID."""
    return _CHAIN_IDS.get(network, 1)
