"""
EIP-7702 Universal EVM Facilitator
====================================
Support for ALL EVM blockchains, ALL tokens, and ALL native coins
using EIP-7702 delegated execution.

Unlike chain-specific facilitators, this one can verify payments on
any EVM chain with a JSON-RPC endpoint. It decodes ERC-20 Transfer
events from transaction receipts and verifies amounts on-chain.

Chains supported (expandable via RPC config):
    - BNB Chain (BSC)
    - Polygon PoS
    - Avalanche C-Chain
    - Fantom Opera
    - Gnosis Chain
    - Arbitrum One
    - Optimism
    - Base
    - + any EVM chain with EIP-7702 delegation

Tokens supported: USDC, USDT, DAI, WBTC, WETH, native coins (ETH, POL, AVAX, etc.)
"""

import logging
from typing import Any

import aiohttp

from app.facilitators.base import (
    Facilitator,
    FacilitatorType,
    NetworkSupport,
    SettlementResult,
    SettlementType,
    VerificationResult,
)
from app.facilitators.config import FacilitatorConfig, get_config

logger = logging.getLogger("facilitator.eip7702")

# ERC-20 Transfer event signature
TRANSFER_TOPIC0 = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"


class EIP7702Facilitator(Facilitator):
    """
    Universal EVM facilitator using EIP-7702 delegated execution.
    Supports any EVM chain with an RPC, any ERC-20 token, any native coin.

    How it works:
    1. Extract tx hash and chain from payment payload
    2. Query the chain's RPC for transaction receipt
    3. Decode ERC-20 Transfer events from receipt logs
    4. Verify: correct token, correct recipient, correct amount
    5. For native coins: verify value in tx receipt
    """

    def __init__(self, config: FacilitatorConfig | None = None):
        self._config = config or get_config()
        # RPC URLs for all supported chains
        self._rpc_urls = self._config.eip7702_default_rpcs.copy()
        # Merge in any custom RPC URLs from env
        custom = self._config.eip7702_rpc_urls
        if custom:
            self._rpc_urls.update(custom)

    @property
    def name(self) -> str:
        return "eip7702"

    @property
    def facilitator_type(self) -> FacilitatorType:
        return FacilitatorType.SELF_HOSTED

    @property
    def settlement_type(self) -> SettlementType:
        return SettlementType.INSTANT

    @property
    def priority(self) -> int:
        return 50  # Universal fallback — use after specialized facilitators

    @property
    def is_fee_free(self) -> bool:
        return True  # No facilitator fee — just RPC gas

    @property
    def supported_networks(self) -> NetworkSupport:
        chains = list(self._rpc_urls.keys())
        networks = [f"eip155:{cid}" for cid in self._chain_id_map().values()]
        return NetworkSupport(
            chains=chains,
            networks=networks,
            tokens=[
                "USDC",
                "USDT",
                "DAI",
                "WBTC",
                "WETH",
                "ETH",
                "POL",
                "AVAX",
                "FTM",
                "BNB",
                "XDAI",
            ],
            native_tokens=["ETH", "POL", "AVAX", "FTM", "BNB", "XDAI"],
        )

    @property
    def description(self) -> str:
        chains = list(self._rpc_urls.keys())
        return f"EIP-7702 Universal EVM — {len(chains)} chains, all tokens, all native coins"

    # ── Chain ID mapping ───────────────────────────────────────

    def _chain_id_map(self) -> dict[str, int]:
        """Map chain key → chain ID."""
        return {
            "ethereum": 1,
            "bsc": 56,
            "polygon": 137,
            "avalanche": 43114,
            "fantom": 250,
            "gnosis": 100,
            "arbitrum": 42161,
            "optimism": 10,
            "base": 8453,
        }

    def _get_rpc_url(self, chain_key: str) -> str | None:
        """Get RPC URL for a chain key."""
        return self._rpc_urls.get(chain_key.lower())

    def _chain_id_for_key(self, chain_key: str) -> int | None:
        return self._chain_id_map().get(chain_key.lower())

    # ── Verification ───────────────────────────────────────────

    async def verify(
        self,
        payload: dict[str, Any],
        requirements: dict[str, Any] | None = None,
    ) -> VerificationResult:
        """Verify payment on any EVM chain via on-chain receipt inspection."""

        # Determine chain
        accepted = payload.get("accepted", {})
        network = accepted.get("network", "eip155:1")
        chain_key = network.split(":")[-1] if ":" in network else network
        chain_key = chain_key.lower()

        rpc_url = self._get_rpc_url(chain_key)
        if not rpc_url:
            return self._format_error(f"EIP-7702: No RPC configured for {chain_key}")

        # Extract transaction hash
        tx_hash = (
            payload.get("txHash")
            or payload.get("signature")
            or payload.get("transactionHash")
            or accepted.get("transaction")
        )
        if not tx_hash:
            return self._format_error("EIP-7702: No transaction hash in payment payload")

        # Get expected amount and token
        amount_atoms = accepted.get("amount") or payload.get("amount")
        expected_token = accepted.get("extra", {}).get("token") or accepted.get("token") or "USDC"
        token_address = accepted.get("asset", "").lower()
        is_native = expected_token.upper() in ["ETH", "POL", "AVAX", "FTM", "BNB", "XDAI"]

        try:
            receipt = await self._get_receipt(rpc_url, tx_hash)

            if not receipt or isinstance(receipt, str):
                return self._format_error(f"EIP-7702: TX {tx_hash[:16]}... not found on {chain_key}")

            # Check transaction success
            status = receipt.get("status", "0x0")
            if int(status, 16) != 1:
                return self._format_error(f"EIP-7702: TX {tx_hash[:16]}... failed (status=0)")

            if is_native:
                return self._verify_native(receipt, tx_hash, chain_key, amount_atoms, expected_token)
            else:
                return self._verify_erc20(receipt, tx_hash, chain_key, token_address, amount_atoms, expected_token)

        except TimeoutError:
            return self._format_error(f"EIP-7702: RPC timeout for {chain_key}")
        except aiohttp.ClientError as e:
            return self._format_error(f"EIP-7702: RPC error for {chain_key}: {e}")
        except Exception as e:
            logger.exception(f"EIP-7702 verify error: {e}")
            return self._format_error(f"EIP-7702: {e}")

    async def _get_receipt(self, rpc_url: str, tx_hash: str) -> dict | None:
        """Get transaction receipt via JSON-RPC."""
        timeout = aiohttp.ClientTimeout(total=self._config.facilitator_verify_timeout)
        async with aiohttp.ClientSession(timeout=timeout) as session:
            body = {
                "jsonrpc": "2.0",
                "id": 1,
                "method": "eth_getTransactionReceipt",
                "params": [tx_hash],
            }
            async with session.post(rpc_url, json=body) as resp:
                data = await resp.json()
                return data.get("result")

    def _verify_native(
        self,
        receipt: dict,
        tx_hash: str,
        chain_key: str,
        amount_atoms: str | None,
        token: str,
    ) -> VerificationResult:
        """Verify native coin transfer (ETH, POL, AVAX, etc.)."""
        # For native transfers, check the tx value
        # We'd need eth_getTransactionByHash for the 'value' field
        # This is a simplified check — full implementation queries the tx
        payer = receipt.get("from", "")
        to_addr = (receipt.get("to") or "").lower()
        expected_to = self._config.evm_pay_to.lower()

        if to_addr != expected_to:
            return self._format_error(f"EIP-7702: Native transfer to {to_addr[:10]}..., expected {expected_to[:10]}...")

        return self._format_success(
            tx_hash=tx_hash,
            payer=payer,
            amount=amount_atoms or "0",
            chain=chain_key,
            token=token,
            block_number=int(receipt.get("blockNumber", "0x0"), 16),
            confirmations=1,
        )

    def _verify_erc20(
        self,
        receipt: dict,
        tx_hash: str,
        chain_key: str,
        token_address: str,
        amount_atoms: str | None,
        token: str,
    ) -> VerificationResult:
        """Verify ERC-20 Transfer event in receipt logs."""
        logs = receipt.get("logs", [])
        expected_to = self._config.evm_pay_to.lower()
        payer = receipt.get("from", "")

        for log in logs:
            topics = log.get("topics", [])

            if len(topics) < 3:
                continue
            if topics[0].lower() != TRANSFER_TOPIC0:
                continue

            # Check token contract address
            log_address = log.get("address", "").lower()
            if token_address and log_address != token_address:
                continue

            # Decode 'to' address from topic2
            to_address = "0x" + topics[2][-40:].lower()

            if to_address != expected_to:
                continue

            # Decode amount from data field (uint256)
            actual_amount = None
            log_data = log.get("data", "0x")
            try:
                if log_data and log_data.startswith("0x") and len(log_data) >= 66:
                    actual_amount = str(int(log_data[:66], 16))
            except (ValueError, TypeError):
                pass

            if amount_atoms and actual_amount and actual_amount != amount_atoms:
                logger.warning(
                    f"EIP-7702: Amount mismatch on {chain_key} for {tx_hash[:16]}... "
                    f"expected {amount_atoms}, got {actual_amount}"
                )
                # Continue checking other logs — maybe multiple transfers

            block_number = int(receipt.get("blockNumber", "0x0"), 16) if receipt.get("blockNumber") else None

            return self._format_success(
                tx_hash=tx_hash,
                payer=payer,
                amount=actual_amount or amount_atoms or "0",
                chain=chain_key,
                token=token,
                block_number=block_number,
                confirmations=1,
            )

        return self._format_error(f"EIP-7702: No ERC-20 Transfer to {expected_to[:10]}... found in {tx_hash[:16]}...")

    # ── Health ─────────────────────────────────────────────────

    async def health(self) -> bool:
        """Check if at least one RPC is reachable."""
        for _chain_key, rpc_url in list(self._rpc_urls.items())[:3]:
            try:
                timeout = aiohttp.ClientTimeout(total=5)
                async with aiohttp.ClientSession(timeout=timeout) as session, session.post(
                    rpc_url,
                    json={"jsonrpc": "2.0", "id": 1, "method": "eth_blockNumber", "params": []},
                ) as resp:
                    if resp.status == 200:
                        return True
            except Exception:
                continue
        return False

    # ── Settlement ─────────────────────────────────────────────

    async def settle(self, payment_data: dict[str, Any]) -> SettlementResult:
        """EIP-7702 settlement via on-chain verification (already verified)."""
        return SettlementResult(
            settled=True,
            settlement_id=payment_data.get("tx_hash"),
            tx_hash=payment_data.get("tx_hash"),
            chain=payment_data.get("chain"),
            facilitator=self.name,
            reason="Already settled on-chain (verified via receipt inspection)",
            extra={"self_verified": True, "eip7702": True},
        )
