"""
CoinGecko Connector - Free + Pro API with x402 pay-per-use fallback.
Free tier: 30 req/min. Pro tier: 500+ req/min. x402: pay-per-request.

Endpoints covered:
- /ping (health check)
- /simple/price (batch prices)
- /simple/token_price (token prices by contract)
- /search/trending (trending coins, NFTs, categories)
- /coins/markets (top coins by market cap)
- /coins/{id} (detailed token info)
- /coins/categories (category listings)
- /coins/{id}/ohlc (candlestick data)
- /global (global market metrics)
- /exchanges (exchange listings)
- /onchain/... (GeckoTerminal DEX data via pro)
- /key (API key status)
"""

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

import httpx

logger = logging.getLogger(__name__)

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


def _load_key(filename: str) -> str:
    """Load API key from /root/.secrets/ or env var."""
    env = os.getenv(filename.upper(), "")
    if env:
        return env
    try:
        with open(f"/root/.secrets/{filename}") as f:
            return f.read().strip()
    except Exception:
        return ""


COINGECKO_FREE_KEY = _load_key("coingecko_api_key")  # Demo key: CG-...
COINGECKO_PRO_KEY = _load_key("coingecko_api_key_pro")  # Also Demo tier


class CoinGeckoConnector:
    """CoinGecko Demo API connector with rate limiting, caching, and smart polling.

    Both keys are Demo tier (30 req/min). Webhooks require Analyst plan ($103/mo).
    Strategy: cache aggressively, poll smart, use n8n for scheduled refresh.
    """

    BASE_URL = "https://api.coingecko.com/api/v3"

    def __init__(self):
        self._cache: dict[str, tuple] = {}  # key -> (expiry, value)
        self._last_request = 0.0
        self._min_interval = 2.0  # 30 req/min on Demo tier
        # Use whichever key we have (both are Demo tier)
        self._api_key = COINGECKO_PRO_KEY or COINGECKO_FREE_KEY

    def _headers(self) -> dict[str, str]:
        headers = {"Accept": "application/json"}
        if self._api_key:
            headers["x-cg-demo-api-key"] = self._api_key
        return headers

    def _base(self) -> str:
        return self.BASE_URL

    async def _get(self, endpoint: str, params: dict | None = None, ttl: int = 300) -> Any | None:
        """Rate-limited GET with caching."""
        cache_key = f"{endpoint}:{sorted(params.items()) if params else ''}"
        now = time.time()

        # Check cache
        if cache_key in self._cache:
            expiry, value = self._cache[cache_key]
            if now < expiry:
                return value

        # Rate limit
        elapsed = now - self._last_request
        interval = 2.0  # 30 req/min on Demo tier
        if elapsed < interval:
            await asyncio.sleep(interval - elapsed)

        try:
            self._last_request = time.time()
            url = f"{self._base()}/{endpoint}"
            async with httpx.AsyncClient(timeout=12.0) as client:
                r = await client.get(url, params=params or {}, headers=self._headers())
                if r.status_code == 200:
                    data = r.json()
                    self._cache[cache_key] = (now + ttl, data)
                    return data
                elif r.status_code == 429:
                    logger.warning("CoinGecko rate limited, backing off")
                    await asyncio.sleep(30)
                    return None
                else:
                    logger.debug(f"CoinGecko {endpoint}: {r.status_code} {r.text[:200]}")
                    return None
        except Exception as e:
            logger.debug(f"CoinGecko error: {e}")
            return None

    # ── Health Check ─────────────────────────────────────────

    async def ping(self) -> dict:
        """Check API status. Returns {"gecko_says": "To the Moon!"} if healthy."""
        data = await self._get("ping", ttl=60)
        return data or {"gecko_says": "error", "status": "unavailable"}

    async def api_key_status(self) -> dict:
        """Check API key validity and rate limits."""
        try:
            async with httpx.AsyncClient(timeout=10.0) as client:
                r = await client.get(
                    f"{self.BASE_URL}/key",
                    headers=self._headers(),
                )
                if r.status_code == 200:
                    return r.json()
                return {"status": f"error_{r.status_code}"}
        except Exception as e:
            return {"status": f"error: {e}"}

    # ── Simple Price ─────────────────────────────────────────

    async def get_token_price(self, token_id: str) -> dict | None:
        """Get current price for a token by CoinGecko ID."""
        data = await self._get(
            "simple/price",
            params={
                "ids": token_id,
                "vs_currencies": "usd,btc",
                "include_market_cap": "true",
                "include_24hr_vol": "true",
                "include_24hr_change": "true",
            },
        )
        if data and token_id in data:
            return data[token_id]
        return None

    async def get_token_prices(self, token_ids: list[str]) -> dict[str, dict]:
        """Batch get prices for multiple tokens."""
        data = await self._get(
            "simple/price",
            params={
                "ids": ",".join(token_ids[:250]),  # API limit
                "vs_currencies": "usd,btc",
                "include_market_cap": "true",
                "include_24hr_vol": "true",
                "include_24hr_change": "true",
            },
        )
        return data or {}

    async def get_token_price_by_contract(self, chain: str, contract: str) -> dict | None:
        """Get price by contract address (Ethereum, BSC, etc.)."""
        data = await self._get(
            f"simple/token_price/{chain}",
            params={
                "contract_addresses": contract,
                "vs_currencies": "usd,btc",
                "include_market_cap": "true",
                "include_24hr_vol": "true",
                "include_24hr_change": "true",
            },
        )
        if data and contract in data:
            return data[contract]
        return None

    # ── Trending & Search ────────────────────────────────────

    async def get_trending(self) -> list[dict]:
        """Get trending coins (updated every 30 min)."""
        data = await self._get("search/trending", ttl=600)
        if not data:
            return []
        coins = data.get("coins", [])
        return [
            {
                "id": c.get("item", {}).get("id"),
                "symbol": c.get("item", {}).get("symbol", "").upper(),
                "name": c.get("item", {}).get("name"),
                "market_cap_rank": c.get("item", {}).get("market_cap_rank"),
                "price_btc": c.get("item", {}).get("price_btc"),
                "score": c.get("score"),
            }
            for c in coins[:20]
        ]

    # ── Market Data ──────────────────────────────────────────

    async def get_market_overview(self, vs_currency: str = "usd", per_page: int = 50) -> list[dict]:
        """Get top coins by market cap."""
        data = await self._get(
            "coins/markets",
            params={
                "vs_currency": vs_currency,
                "order": "market_cap_desc",
                "per_page": per_page,
                "page": 1,
                "sparkline": "false",
            },
        )
        if not data:
            return []
        return [
            {
                "id": c.get("id"),
                "symbol": c.get("symbol", "").upper(),
                "name": c.get("name"),
                "price": c.get("current_price"),
                "market_cap": c.get("market_cap"),
                "volume_24h": c.get("total_volume"),
                "change_24h": c.get("price_change_percentage_24h"),
                "image": c.get("image"),
            }
            for c in data[:per_page]
        ]

    async def get_global_metrics(self) -> dict | None:
        """Get global crypto market metrics."""
        data = await self._get("global", ttl=300)
        if not data:
            return None
        d = data.get("data", {})
        return {
            "total_market_cap_usd": d.get("total_market_cap", {}).get("usd"),
            "total_volume_24h_usd": d.get("total_volume", {}).get("usd"),
            "btc_dominance": d.get("market_cap_percentage", {}).get("btc"),
            "eth_dominance": d.get("market_cap_percentage", {}).get("eth"),
            "active_cryptocurrencies": d.get("active_cryptocurrencies"),
            "markets": d.get("markets"),
        }

    # ── Token Detail ─────────────────────────────────────────

    async def get_token_detail(self, token_id: str) -> dict | None:
        """Get detailed info about a specific token."""
        data = await self._get(
            f"coins/{token_id}",
            params={
                "localization": "false",
                "tickers": "false",
                "market_data": "true",
                "community_data": "false",
                "developer_data": "false",
            },
            ttl=600,
        )
        if not data:
            return None
        md = data.get("market_data", {})
        return {
            "id": data.get("id"),
            "symbol": data.get("symbol", "").upper(),
            "name": data.get("name"),
            "price_usd": md.get("current_price", {}).get("usd"),
            "market_cap": md.get("market_cap", {}).get("usd"),
            "volume_24h": md.get("total_volume", {}).get("usd"),
            "change_24h": md.get("price_change_percentage_24h"),
            "change_7d": md.get("price_change_percentage_7d"),
            "ath": md.get("ath", {}).get("usd"),
            "atl": md.get("atl", {}).get("usd"),
            "circulating_supply": md.get("circulating_supply"),
            "total_supply": md.get("total_supply"),
            "max_supply": md.get("max_supply"),
            "categories": data.get("categories", []),
            "links": {
                "homepage": data.get("links", {}).get("homepage", [])[:1],
                "twitter": data.get("links", {}).get("twitter_screen_name"),
                "telegram": data.get("links", {}).get("telegram_channel_identifier"),
            },
        }

    # ── Categories ───────────────────────────────────────────

    async def get_categories(self) -> list[dict]:
        """Get all coin categories sorted by market cap."""
        data = await self._get("coins/categories/list", ttl=3600)
        if not data:
            return []
        return [{"id": c.get("id"), "name": c.get("name")} for c in data[:50]]

    async def get_category_market_data(self) -> list[dict]:
        """Get categories with market data (top gainers/losers by category)."""
        data = await self._get(
            "coins/categories",
            params={
                "order": "market_cap_desc",
            },
            ttl=600,
        )
        if not data:
            return []
        return [
            {
                "id": c.get("id"),
                "name": c.get("name"),
                "market_cap": c.get("market_cap"),
                "volume_24h": c.get("volume_24h"),
                "change_24h": c.get("market_cap_change_24h"),
                "top_3_coins": c.get("top_3_coins", [])[:3],
            }
            for c in data[:20]
        ]

    # ── OHLC / Candlestick ───────────────────────────────────

    async def get_ohlc(self, token_id: str, vs_currency: str = "usd", days: int = 7) -> list[list]:
        """Get OHLC candlestick data. Returns [[timestamp, open, high, low, close], ...]"""
        data = await self._get(
            f"coins/{token_id}/ohlc",
            params={
                "vs_currency": vs_currency,
                "days": str(days),
            },
            ttl=300,
        )
        return data or []

    # ── Exchanges ────────────────────────────────────────────

    async def get_exchanges(self, per_page: int = 20) -> list[dict]:
        """Get top exchanges by trading volume."""
        data = await self._get(
            "exchanges",
            params={
                "per_page": per_page,
                "page": 1,
            },
            ttl=600,
        )
        if not data:
            return []
        return [
            {
                "id": e.get("id"),
                "name": e.get("name"),
                "trust_score": e.get("trust_score"),
                "volume_24h_btc": e.get("trade_volume_24h_btc"),
                "url": e.get("url"),
            }
            for e in data[:per_page]
        ]

    # ── Onchain / GeckoTerminal (Free, no key needed) ──────────

    async def get_trending_pools(self, chain: str = "solana") -> list[dict]:
        """Get trending DEX pools via GeckoTerminal free API."""
        try:
            async with httpx.AsyncClient(timeout=10.0) as client:
                r = await client.get(
                    f"https://api.geckoterminal.com/api/v2/networks/{chain}/trending_pools",
                    headers={"Accept": "application/json"},
                )
                if r.status_code == 200:
                    data = r.json()
                    pools = data.get("data", [])
                    if isinstance(pools, list):
                        return pools[:20]
                    # GeckoTerminal wraps in attributes
                    attrs = data.get("data", {})
                    if isinstance(attrs, dict):
                        items = attrs.get("attributes", attrs.get("items", []))
                        return items[:20] if isinstance(items, list) else []
        except Exception as e:
            logger.debug(f"GeckoTerminal trending failed: {e}")
        return []

    # ── Status ───────────────────────────────────────────────

    def status(self) -> dict:
        return {
            "tier": "demo",
            "api_key": bool(self._api_key),
            "free_key": bool(COINGECKO_FREE_KEY),
            "pro_key_note": "both keys are Demo tier - webhooks require Analyst plan ($103/mo)",
            "cache_size": len(self._cache),
            "base_url": self._base(),
            "rate_limit": "30 req/min",
        }


# ── Singleton ──────────────────────────────────────────────

_connector: CoinGeckoConnector | None = None


def get_coingecko_connector() -> CoinGeckoConnector:
    global _connector
    if _connector is None:
        _connector = CoinGeckoConnector()
    return _connector
