"""
Agent Wallet – Abstraktionsschicht über Coinbase CDP SDK.
Versteckt die gesamte Blockchain-Komplexität.
Der Entwickler sieht nur: wallet.pay() und wallet.balance()

Supports: ETH (native) and USDC (ERC-20) on Base.
"""
from __future__ import annotations
import asyncio
import inspect
import re
from dataclasses import dataclass
from decimal import Decimal

from swarmpay.fee_engine import FeeEngine, FeeConfig
from swarmpay.constants import (
    USDC_CONTRACTS,
    TOKEN_DECIMALS,
    SUPPORTED_NETWORKS,
    ERC20_TRANSFER_SELECTOR,
    ERC20_APPROVE_SELECTOR,
)

# Valid Ethereum address pattern
_ETH_ADDRESS_RE = re.compile(r'^0x[0-9a-fA-F]{40}$')
_ZERO_ADDRESS = "0x" + "0" * 40


@dataclass
class PaymentResult:
    """Ergebnis einer Zahlung."""
    success: bool
    transaction_id: str
    amount_usd: float
    fee_usd: float
    service_amount_usd: float
    recipient: str
    explorer_url: str
    currency: str = "USDC"
    error: str = ""


def _to_atomic_units(amount: float, decimals: int) -> int:
    """Convert human-readable amount to atomic units (wei/micro-USDC).

    Uses Decimal to avoid floating-point precision errors.
    """
    return int(Decimal(str(amount)) * Decimal(10 ** decimals))


def _encode_erc20_transfer(recipient: str, amount_atomic: int) -> str:
    """Encode ERC-20 transfer(address, uint256) calldata."""
    if amount_atomic < 0:
        raise ValueError("Transfer amount cannot be negative")
    addr = recipient.lower().replace("0x", "").zfill(64)
    value = hex(amount_atomic)[2:].zfill(64)
    return ERC20_TRANSFER_SELECTOR + addr + value


def _encode_erc20_approve(spender: str, amount_atomic: int) -> str:
    """Encode ERC-20 approve(address, uint256) calldata."""
    if amount_atomic < 0:
        raise ValueError("Approval amount cannot be negative")
    addr = spender.lower().replace("0x", "").zfill(64)
    value = hex(amount_atomic)[2:].zfill(64)
    return ERC20_APPROVE_SELECTOR + addr + value


def _run_async(coro):
    """Run async coroutine in sync context."""
    try:
        loop = asyncio.get_running_loop()
        import concurrent.futures
        with concurrent.futures.ThreadPoolExecutor() as pool:
            return loop.run_in_executor(pool, asyncio.run, coro)
    except RuntimeError:
        return asyncio.run(coro)


class AgentWallet:
    """
    Wallet für einen KI-Agenten.
    Nutzt Coinbase CDP SDK unter der Haube.

    Usage:
        wallet = AgentWallet(agent_id="my-bot", provider=cdp_account)
        result = wallet.pay("0xRecipient...", amount=0.50, currency="USDC")
    """

    def __init__(self, agent_id: str, wallet_provider=None,
                 fee_engine: FeeEngine | None = None, network: str = "base-sepolia",
                 permission_engine=None):
        self.agent_id = agent_id
        self.network = network
        self._provider = wallet_provider
        self._fee_engine = fee_engine or FeeEngine()
        self._permission_engine = permission_engine
        self._address: str = ""

    @property
    def address(self) -> str:
        """Wallet-Adresse des Agenten."""
        if self._address:
            return self._address
        if self._provider:
            try:
                self._address = self._provider.get_address()
            except AttributeError:
                # CDP SDK EvmServerAccount uses .address property
                self._address = getattr(self._provider, 'address', 'UNKNOWN')
            return self._address
        return "NOT_CONNECTED"

    def balance(self, currency: str = "USDC") -> float:
        """Gibt den aktuellen Kontostand zurück."""
        if not self._provider:
            return 0.0
        try:
            return float(self._provider.get_balance(currency))
        except Exception:
            return 0.0

    def pay(self, recipient: str, amount: float,
            currency: str = "USDC") -> PaymentResult:
        """
        Zahlt an einen Empfänger. Automatische Fee-Berechnung.

        Args:
            recipient: Wallet-Adresse des Empfängers
            amount: Betrag (z.B. 0.50 für $0.50 USDC)
            currency: Währung (Standard: USDC, auch ETH möglich)

        Returns:
            PaymentResult mit TX-Details
        """
        # Adresse validieren
        if not recipient or not _ETH_ADDRESS_RE.match(recipient):
            return PaymentResult(
                success=False, transaction_id="", amount_usd=amount,
                fee_usd=0, service_amount_usd=0, recipient=recipient or "",
                explorer_url="", currency=currency,
                error="Invalid recipient address",
            )

        if recipient == _ZERO_ADDRESS:
            return PaymentResult(
                success=False, transaction_id="", amount_usd=amount,
                fee_usd=0, service_amount_usd=0, recipient=recipient,
                explorer_url="", currency=currency,
                error="Cannot send to zero address",
            )

        if amount <= 0:
            return PaymentResult(
                success=False, transaction_id="", amount_usd=amount,
                fee_usd=0, service_amount_usd=0, recipient=recipient,
                explorer_url="", currency=currency,
                error="Amount must be greater than 0",
            )

        # Permission check
        if self._permission_engine:
            perm_result = self._permission_engine.check_permission(
                sender=self.address, recipient=recipient, amount=amount,
            )
            if not perm_result.allowed:
                return PaymentResult(
                    success=False, transaction_id="", amount_usd=amount,
                    fee_usd=0, service_amount_usd=0, recipient=recipient,
                    explorer_url="", currency=currency,
                    error=f"Permission denied: {perm_result.reason}",
                )

        # Fee berechnen
        fee_info = self._fee_engine.calculate_fee(amount, self.address)

        # Check if amount is too small to cover minimum fee
        if fee_info["service_amount"] <= 0:
            return PaymentResult(
                success=False, transaction_id="", amount_usd=amount,
                fee_usd=fee_info["fee_amount"],
                service_amount_usd=fee_info["service_amount"],
                recipient=recipient, explorer_url="", currency=currency,
                error="Amount too small to cover minimum fee",
            )

        if not self._provider:
            return PaymentResult(
                success=False, transaction_id="", amount_usd=amount,
                fee_usd=fee_info["fee_amount"],
                service_amount_usd=fee_info["service_amount"],
                recipient=recipient, explorer_url="", currency=currency,
                error="No wallet provider connected. Set CDP API Key.",
            )

        try:
            # Transfer 1: Service-Betrag an Empfänger
            tx1 = self._send_transfer(
                to=recipient,
                amount=fee_info["service_amount"],
                currency=currency,
            )

            # Transfer 2: Fee an Admin-Wallet
            admin = fee_info["admin_wallet"]
            fee_paid = False
            fee_error = ""
            if admin and fee_info["fee_amount"] > 0:
                try:
                    self._send_transfer(
                        to=admin,
                        amount=fee_info["fee_amount"],
                        currency=currency,
                    )
                    fee_paid = True
                except Exception:
                    fee_error = "Payment sent but platform fee transfer failed"

            # Volumen tracken
            self._fee_engine.record_volume(self.address, amount)
            if self._permission_engine:
                self._permission_engine.record_spending(self.address, amount)

            tx_id = str(tx1) if tx1 else "pending"
            explorer = self._get_explorer_url(tx_id)

            return PaymentResult(
                success=True,
                transaction_id=tx_id,
                amount_usd=amount,
                fee_usd=fee_info["fee_amount"] if fee_paid else 0,
                service_amount_usd=fee_info["service_amount"],
                recipient=recipient,
                explorer_url=explorer,
                currency=currency,
                error=fee_error,
            )

        except Exception:
            return PaymentResult(
                success=False, transaction_id="", amount_usd=amount,
                fee_usd=fee_info["fee_amount"],
                service_amount_usd=fee_info["service_amount"],
                recipient=recipient, explorer_url="", currency=currency,
                error="Payment failed. Please try again.",
            )

    def _send_transfer(self, to: str, amount: float, currency: str) -> str:
        """
        Sendet einen Transfer – ETH nativ oder USDC als ERC-20.

        Unterstützt verschiedene Provider-Interfaces:
        - CDP SDK EvmServerAccount.transfer() (async, native USDC support)
        - CdpEvmWalletProvider.send_transaction() (sync)
        - Mock providers for testing
        """
        currency_lower = currency.lower()

        # Check if provider has native transfer() method (CDP SDK)
        if hasattr(self._provider, 'transfer') and callable(getattr(self._provider, 'transfer')):
            transfer_method = getattr(self._provider, 'transfer')
            decimals = TOKEN_DECIMALS.get(currency_lower, 18)
            amount_atomic = _to_atomic_units(amount, decimals)

            # Handle async transfer (CDP SDK)
            if inspect.iscoroutinefunction(transfer_method):
                return _run_async(transfer_method(
                    to=to,
                    amount=amount_atomic,
                    token=currency_lower,
                    network=self.network,
                ))

            # Sync transfer
            return transfer_method(
                to=to,
                amount=amount_atomic,
                token=currency_lower,
                network=self.network,
            )

        # Fallback: send_transaction (CdpEvmWalletProvider or mock)
        if hasattr(self._provider, 'send_transaction'):
            if currency_lower == "usdc":
                return self._send_usdc_via_raw_tx(to, amount)
            else:
                return self._send_eth_via_raw_tx(to, amount)

        raise RuntimeError(f"Provider {type(self._provider)} hat keine transfer/send_transaction Methode")

    def send_transaction(self, to: str, data: str, value: int = 0) -> str:
        """Send a raw contract transaction through the configured provider."""
        if not self._provider:
            raise RuntimeError("No wallet provider connected. Set CDP API Key.")
        if not to or not _ETH_ADDRESS_RE.match(to):
            raise ValueError("Invalid contract address")
        if value < 0:
            raise ValueError("Transaction value cannot be negative")
        if not isinstance(data, str) or not data.startswith("0x"):
            raise ValueError("Transaction data must be a hex string")

        tx = {"to": to, "value": value, "data": data}
        send_fn = getattr(self._provider, "send_transaction", None)
        if not callable(send_fn):
            raise RuntimeError(f"Provider {type(self._provider)} hat keine send_transaction Methode")
        if inspect.iscoroutinefunction(send_fn):
            return _run_async(send_fn(tx, self.network))
        return send_fn(tx)

    def approve_erc20(self, token_address: str, spender: str, amount_atomic: int) -> str:
        """Approve an ERC-20 spender with a raw contract call."""
        if not token_address or not _ETH_ADDRESS_RE.match(token_address):
            raise ValueError("Invalid token contract address")
        if not spender or not _ETH_ADDRESS_RE.match(spender):
            raise ValueError("Invalid spender address")
        calldata = _encode_erc20_approve(spender, amount_atomic)
        return self.send_transaction(to=token_address, data=calldata, value=0)

    def _send_usdc_via_raw_tx(self, to: str, amount: float) -> str:
        """Send USDC via raw ERC-20 transfer calldata."""
        usdc_address = USDC_CONTRACTS.get(self.network)
        if not usdc_address:
            raise ValueError(f"USDC nicht verfügbar auf Netzwerk: {self.network}")

        amount_atomic = _to_atomic_units(amount, 6)  # USDC = 6 decimals
        calldata = _encode_erc20_transfer(to, amount_atomic)

        tx = {"to": usdc_address, "value": 0, "data": calldata}

        send_fn = self._provider.send_transaction
        if inspect.iscoroutinefunction(send_fn):
            return _run_async(send_fn(tx, self.network))
        return send_fn(tx)

    def _send_eth_via_raw_tx(self, to: str, amount: float) -> str:
        """Send ETH via native transfer."""
        amount_wei = _to_atomic_units(amount, 18)

        # Use native_transfer if available (CdpEvmWalletProvider)
        if hasattr(self._provider, 'native_transfer'):
            return self._provider.native_transfer(to, str(amount_wei))

        tx = {"to": to, "value": amount_wei}
        send_fn = self._provider.send_transaction
        if asyncio.iscoroutinefunction(send_fn):
            return _run_async(send_fn(tx, self.network))
        return send_fn(tx)

    def _get_explorer_url(self, tx_id: str) -> str:
        """Get block explorer URL for transaction."""
        network_info = SUPPORTED_NETWORKS.get(self.network)
        if network_info:
            return f"{network_info['explorer']}/tx/{tx_id}"
        return f"https://sepolia.basescan.org/tx/{tx_id}"
