"""CoinGecko data provider with coin validation."""

import re
from typing import Any

from pydantic import BaseModel

from .base import Provider, ProviderData, CoinNotFoundError, ProviderError


class CoinGeckoData(ProviderData):
    """CoinGecko specific data."""

    provider: str = "coingecko"
    current_price: float | None = None
    market_cap: float | None = None
    total_volume: float | None = None
    price_change_24h: float | None = None
    price_change_7d: float | None = None
    price_change_30d: float | None = None
    ath: float | None = None
    ath_change_percentage: float | None = None
    ath_date: str | None = None
    atl: float | None = None
    high_24h: float | None = None
    low_24h: float | None = None
    circulating_supply: float | None = None
    total_supply: float | None = None
    sparkline_7d: list[float] | None = None


# Common symbol to CoinGecko ID mapping
SYMBOL_TO_ID = {
    "BTC": "bitcoin",
    "ETH": "ethereum",
    "SOL": "solana",
    "BNB": "binancecoin",
    "XRP": "ripple",
    "ADA": "cardano",
    "DOGE": "dogecoin",
    "DOT": "polkadot",
    "MATIC": "matic-network",
    "POL": "matic-network",  # Polygon rebranding
    "SHIB": "shiba-inu",
    "LTC": "litecoin",
    "AVAX": "avalanche-2",
    "LINK": "chainlink",
    "UNI": "uniswap",
    "ATOM": "cosmos",
    "XLM": "stellar",
    "ALGO": "algorand",
    "VET": "vechain",
    "FIL": "filecoin",
    "AAVE": "aave",
    "SAND": "the-sandbox",
    "MANA": "decentraland",
    "AXS": "axie-infinity",
    "APE": "apecoin",
    "CRO": "crypto-com-chain",
    "NEAR": "near",
    "FTM": "fantom",
    "EGLD": "elrond-erd-2",
    "HBAR": "hedera-hashgraph",
    "ICP": "internet-computer",
    "ARB": "arbitrum",
    "OP": "optimism",
    "SUI": "sui",
    "SEI": "sei-network",
    "TIA": "celestia",
    "INJ": "injective-protocol",
    "PEPE": "pepe",
    "WIF": "dogwifcoin",
    "BONK": "bonk",
    "FLOKI": "floki",
    "RENDER": "render-token",
    "FET": "fetch-ai",
    "GRT": "the-graph",
    "IMX": "immutable-x",
    "STX": "blockstack",
    "MKR": "maker",
    "RUNE": "thorchain",
    "KAS": "kaspa",
    "TON": "the-open-network",
    "TRX": "tron",
    "BCH": "bitcoin-cash",
    "ETC": "ethereum-classic",
    "XMR": "monero",
    "OKB": "okb",
    "APT": "aptos",
}

# Valid symbol pattern
SYMBOL_PATTERN = re.compile(r"^[A-Za-z0-9]{1,10}$")


def validate_symbol(symbol: str) -> str:
    """Validate and normalize a symbol."""
    if not symbol or not isinstance(symbol, str):
        raise ValueError("Symbol must be a non-empty string")

    symbol = symbol.strip().upper()

    if not SYMBOL_PATTERN.match(symbol):
        raise ValueError(f"Invalid symbol format: {symbol}")

    return symbol


class CoinGeckoProvider(Provider):
    """CoinGecko API provider for price and market data."""

    name = "coingecko"
    base_url = "https://api.coingecko.com/api/v3"
    requires_auth = False  # Free tier available

    # CoinGecko has stricter rate limits on free tier
    max_retries = 3
    base_delay = 2.0  # Longer base delay for CoinGecko

    def __init__(self, api_key: str | None = None, timeout: float = 30.0, cache_ttl: int = 5):
        """Initialize CoinGecko provider."""
        super().__init__(api_key, timeout, cache_ttl)
        # Use Pro API if key provided
        if api_key:
            self.base_url = "https://pro-api.coingecko.com/api/v3"
            self.base_delay = 0.5  # Pro tier has more generous limits

        # Cache for coin ID lookups
        self._coin_id_cache: dict[str, str] = {}

    def _get_auth_headers(self) -> dict[str, str]:
        """Get authentication headers for Pro API."""
        if self.api_key:
            return {"x-cg-pro-api-key": self.api_key}
        return {}

    async def _resolve_coin_id(self, symbol: str) -> str:
        """Resolve symbol to CoinGecko coin ID with validation."""
        symbol = validate_symbol(symbol)

        # Check known mappings first
        if symbol in SYMBOL_TO_ID:
            return SYMBOL_TO_ID[symbol]

        # Check cache
        if symbol in self._coin_id_cache:
            return self._coin_id_cache[symbol]

        # Search CoinGecko for the symbol
        try:
            search_results = await self.search_coin(symbol)

            # Find exact symbol match
            for coin in search_results:
                if coin.get("symbol", "").upper() == symbol:
                    coin_id = coin.get("id")
                    if coin_id:
                        self._coin_id_cache[symbol] = coin_id
                        return coin_id

            # If no exact match, try the first result if symbol is close
            if search_results:
                first_result = search_results[0]
                if first_result.get("symbol", "").upper() == symbol:
                    coin_id = first_result.get("id")
                    if coin_id:
                        self._coin_id_cache[symbol] = coin_id
                        return coin_id

        except ProviderError:
            pass  # Search failed, will try lowercase fallback

        # Last resort: try lowercase symbol as ID
        # This works for some coins like "bitcoin", "ethereum"
        return symbol.lower()

    async def fetch_data(self, symbol: str) -> CoinGeckoData:
        """Fetch market data for a coin."""
        try:
            coin_id = await self._resolve_coin_id(symbol)
        except ValueError as e:
            raise CoinNotFoundError(str(e))

        # Fetch coin market data
        try:
            data = await self.get(
                "/coins/markets",
                params={
                    "vs_currency": "usd",
                    "ids": coin_id,
                    "sparkline": "true",
                    "price_change_percentage": "24h,7d,30d",
                },
            )
        except CoinNotFoundError:
            raise CoinNotFoundError(f"Coin '{symbol}' not found on CoinGecko")

        if not data or len(data) == 0:
            raise CoinNotFoundError(f"No data returned for '{symbol}' (tried ID: {coin_id})")

        coin = data[0]
        sparkline = coin.get("sparkline_in_7d", {}).get("price", [])

        return CoinGeckoData(
            symbol=symbol.upper(),
            current_price=coin.get("current_price"),
            market_cap=coin.get("market_cap"),
            total_volume=coin.get("total_volume"),
            price_change_24h=coin.get("price_change_percentage_24h"),
            price_change_7d=coin.get("price_change_percentage_7d_in_currency"),
            price_change_30d=coin.get("price_change_percentage_30d_in_currency"),
            ath=coin.get("ath"),
            ath_change_percentage=coin.get("ath_change_percentage"),
            ath_date=coin.get("ath_date"),
            atl=coin.get("atl"),
            high_24h=coin.get("high_24h"),
            low_24h=coin.get("low_24h"),
            circulating_supply=coin.get("circulating_supply"),
            total_supply=coin.get("total_supply"),
            sparkline_7d=sparkline if sparkline else None,
            raw_data=coin,
        )

    async def fetch_coin_info(self, symbol: str) -> dict[str, Any]:
        """Fetch detailed coin information."""
        coin_id = await self._resolve_coin_id(symbol)
        return await self.get(
            f"/coins/{coin_id}",
            params={
                "localization": "false",
                "tickers": "false",
                "community_data": "true",
                "developer_data": "false",
            },
        )

    async def fetch_ohlc(
        self, symbol: str, days: int = 30
    ) -> list[list[float]]:
        """Fetch OHLC data for RSI calculation."""
        coin_id = await self._resolve_coin_id(symbol)
        return await self.get(
            f"/coins/{coin_id}/ohlc",
            params={"vs_currency": "usd", "days": str(days)},
        )

    async def search_coin(self, query: str) -> list[dict[str, Any]]:
        """Search for coins by name or symbol."""
        data = await self.get("/search", params={"query": query})
        return data.get("coins", [])

    async def validate_coin_exists(self, symbol: str) -> bool:
        """Check if a coin exists on CoinGecko."""
        try:
            await self._resolve_coin_id(symbol)
            return True
        except (CoinNotFoundError, ValueError):
            return False
