"""
Etherscan Family Connector — EVM Block Explorers.
Single API works for: Ethereum, BSC, Polygon, Avalanche, Fantom, Arbitrum, Optimism, Base.
Free tier: 5 req/sec, 100,000 calls/day.

Supports:
- Account: balance, txlist, tokentx, tokenbalance
- Contract: abi, sourcecode, verify
- Transaction: status, receipt
- Block: blocknumber, blockreward
- Logs: getLogs
"""

import asyncio
import logging
import os
import time
from typing import Any

import httpx

logger = logging.getLogger(__name__)

# ── API Keys ────────────────────────────────────────────────

ETHERSCAN_API_KEY = os.getenv("ETHERSCAN_API_KEY", "")
BSCSCAN_API_KEY = os.getenv("BSCSCAN_API_KEY", "")
POLYGONSCAN_API_KEY = os.getenv("POLYGONSCAN_API_KEY", "")
SNOWTRACE_API_KEY = os.getenv("SNOWTRACE_API_KEY", "")  # Avalanche
FTMSCAN_API_KEY = os.getenv("FTMSCAN_API_KEY", "")  # Fantom

# Network configurations
NETWORKS = {
    "eth": {"name": "Etherscan", "url": "https://api.etherscan.io/api", "key": ETHERSCAN_API_KEY},
    "bsc": {"name": "BscScan", "url": "https://api.bscscan.com/api", "key": BSCSCAN_API_KEY},
    "polygon": {
        "name": "PolygonScan",
        "url": "https://api.polygonscan.com/api",
        "key": POLYGONSCAN_API_KEY,
    },
    "avalanche": {
        "name": "SnowTrace",
        "url": "https://api.snowtrace.io/api",
        "key": SNOWTRACE_API_KEY,
    },
    "fantom": {"name": "FtmScan", "url": "https://api.ftmscan.com/api", "key": FTMSCAN_API_KEY},
    "arbitrum": {
        "name": "Arbiscan",
        "url": "https://api.arbiscan.io/api",
        "key": ETHERSCAN_API_KEY,
    },
    "optimism": {
        "name": "Optimism Etherscan",
        "url": "https://api-optimistic.etherscan.io/api",
        "key": ETHERSCAN_API_KEY,
    },
    "base": {"name": "Basescan", "url": "https://api.basescan.org/api", "key": ETHERSCAN_API_KEY},
}


class EtherscanConnector:
    """Etherscan family API connector with intelligent key rotation."""

    def __init__(self):
        self._cache: dict[str, tuple] = {}
        self._cache_ttl = 300  # 5 min
        self._last_request: dict[str, float] = {}  # per-network rate limiting
        self._min_interval = 0.2  # 5 req/sec per network

    def _get_network(self, network: str) -> dict:
        """Get network config."""
        return NETWORKS.get(network, NETWORKS["eth"])

    def _network_key(self, network: str) -> str:
        """Get API key for network."""
        net = self._get_network(network)
        return net.get("key", ETHERSCAN_API_KEY)

    async def _rate_limit(self, network: str):
        now = time.monotonic()
        last = self._last_request.get(network, 0)
        elapsed = now - last
        if elapsed < self._min_interval:
            await asyncio.sleep(self._min_interval - elapsed)
        self._last_request[network] = time.monotonic()

    def _cached(self, key: str) -> Any | None:
        if key in self._cache:
            data, ts = self._cache[key]
            if time.time() - ts < self._cache_ttl:
                return data
        return None

    def _set_cache(self, key: str, data: Any):
        self._cache[key] = (data, time.time())
        if len(self._cache) > 500:
            oldest = min(self._cache, key=lambda k: self._cache[k][1])
            del self._cache[oldest]

    async def _get(self, network: str, params: dict) -> dict | None:
        """Make API call to Etherscan family."""
        net = self._get_network(network)
        api_key = self._network_key(network)

        params["apikey"] = api_key
        cache_key = f"{network}:{params.get('module', '')}:{params.get('action', '')}:{str(params.get('address', params.get('contract', '')))[:30]}"

        cached = self._cached(cache_key)
        if cached is not None:
            return cached

        await self._rate_limit(network)

        try:
            async with httpx.AsyncClient(timeout=15.0) as client:
                r = await client.get(net["url"], params=params)
                if r.status_code == 200:
                    data = r.json()
                    if data.get("status") == "1":
                        result = data.get("result")
                        self._set_cache(cache_key, result)
                        return result
                    elif data.get("status") == "0":
                        logger.debug(f"Etherscan error: {data.get('message', '')}")
                        return None
                    else:
                        self._set_cache(cache_key, data)
                        return data
                elif r.status_code == 429:
                    logger.warning(f"{net['name']} rate limited")
                    return None
                else:
                    logger.debug(f"{net['name']} {r.status_code}")
                    return None
        except Exception as e:
            logger.debug(f"{net['name']} request failed: {e}")
            return None

    # ── Account API ──────────────────────────────────────────

    async def get_balance(self, address: str, network: str = "eth") -> str:
        """Get native token balance (ETH/BNB/MATIC/etc)."""
        return (
            await self._get(
                network,
                {"module": "account", "action": "balance", "address": address, "tag": "latest"},
            )
            or "0"
        )

    async def get_tx_list(
        self,
        address: str,
        network: str = "eth",
        start_block: int = 0,
        end_block: int = 99999999,
        page: int = 1,
        offset: int = 100,
        sort: str = "desc",
    ) -> list[dict]:
        """Get transaction list for address."""
        result = await self._get(
            network,
            {
                "module": "account",
                "action": "txlist",
                "address": address,
                "startblock": start_block,
                "endblock": end_block,
                "page": page,
                "offset": offset,
                "sort": sort,
            },
        )
        return result if isinstance(result, list) else []

    async def get_token_transfers(
        self,
        address: str,
        network: str = "eth",
        contract_address: str | None = None,
        page: int = 1,
        offset: int = 100,
    ) -> list[dict]:
        """Get ERC-20 token transfer events."""
        params = {
            "module": "account",
            "action": "tokentx",
            "address": address,
            "page": page,
            "offset": offset,
            "sort": "desc",
        }
        if contract_address:
            params["contractaddress"] = contract_address
        result = await self._get(network, params)
        return result if isinstance(result, list) else []

    async def get_token_balance(self, contract_address: str, address: str, network: str = "eth") -> str:
        """Get ERC-20 token balance for address."""
        return (
            await self._get(
                network,
                {
                    "module": "account",
                    "action": "tokenbalance",
                    "contractaddress": contract_address,
                    "address": address,
                    "tag": "latest",
                },
            )
            or "0"
        )

    async def get_erc721_transfers(
        self, address: str, network: str = "eth", page: int = 1, offset: int = 100
    ) -> list[dict]:
        """Get ERC-721 NFT transfer events."""
        result = await self._get(
            network,
            {
                "module": "account",
                "action": "tokennfttx",
                "address": address,
                "page": page,
                "offset": offset,
                "sort": "desc",
            },
        )
        return result if isinstance(result, list) else []

    # ── Contract API ─────────────────────────────────────────

    async def get_contract_abi(self, contract: str, network: str = "eth") -> str:
        """Get contract ABI."""
        return await self._get(network, {"module": "contract", "action": "getabi", "address": contract}) or ""

    async def get_contract_source(self, contract: str, network: str = "eth") -> dict:
        """Get contract source code."""
        result = await self._get(network, {"module": "contract", "action": "getsourcecode", "address": contract})
        if isinstance(result, list) and len(result) > 0:
            return result[0]
        return result or {}

    async def verify_contract(self, contract: str, network: str = "eth") -> dict:
        """Check if contract is verified."""
        source = await self.get_contract_source(contract, network)
        return {
            "verified": bool(source.get("SourceCode")),
            "name": source.get("ContractName"),
            "compiler": source.get("CompilerVersion"),
        }

    # ── Transaction API ──────────────────────────────────────

    async def get_transaction_status(self, tx_hash: str, network: str = "eth") -> dict:
        """Get transaction status (success/fail)."""
        result = await self._get(network, {"module": "transaction", "action": "getstatus", "txhash": tx_hash})
        return result or {}

    async def get_transaction_receipt(self, tx_hash: str, network: str = "eth") -> dict:
        """Get transaction receipt."""
        result = await self._get(network, {"module": "transaction", "action": "gettxreceiptstatus", "txhash": tx_hash})
        return result or {}

    # ── Block API ────────────────────────────────────────────

    async def get_block_number(self, network: str = "eth") -> int:
        """Get latest block number."""
        result = await self._get(network, {"module": "proxy", "action": "eth_blockNumber"})
        try:
            return int(result, 16) if result and result.startswith("0x") else int(result or 0)
        except Exception:
            return 0

    async def get_block_reward(self, block_number: int, network: str = "eth") -> dict:
        """Get block reward data."""
        return await self._get(network, {"module": "block", "action": "getblockreward", "blockno": block_number}) or {}

    # ── Logs API ─────────────────────────────────────────────

    async def get_logs(
        self,
        from_block: int,
        to_block: int,
        address: str | None = None,
        topic0: str | None = None,
        network: str = "eth",
    ) -> list[dict]:
        """Get event logs (filtered)."""
        params = {
            "module": "logs",
            "action": "getLogs",
            "fromBlock": from_block,
            "toBlock": to_block,
        }
        if address:
            params["address"] = address
        if topic0:
            params["topic0"] = topic0
        result = await self._get(network, params)
        return result if isinstance(result, list) else []

    # ── Utility ──────────────────────────────────────────────

    def status(self) -> dict:
        """Return connector status."""
        active = [k for k, v in NETWORKS.items() if v.get("key")]
        return {
            "networks_configured": len(NETWORKS),
            "networks_active": len(active),
            "active_list": active,
            "cache_entries": len(self._cache),
        }


# Singleton
_etherscan: EtherscanConnector | None = None


def get_etherscan_connector() -> EtherscanConnector:
    global _etherscan
    if _etherscan is None:
        _etherscan = EtherscanConnector()
    return _etherscan
