"""CoinGecko data provider.

Cryptocurrency market data with broad coverage.
Free tier available. Pro tier for higher rate limits.

Docs: https://docs.coingecko.com/reference/introduction
"""

from __future__ import annotations

from datetime import datetime
from typing import Any, Dict, List, Optional

import requests

from ..models import Bar, Quote, TimeFrame


class CoinGeckoProvider:
    """CoinGecko data provider.
    
    Supports cryptocurrency market data, trending coins, and search.
    
    Usage:
        provider = CoinGeckoProvider(api_key="your_key")
        bars = provider.get_bars("bitcoin", start=datetime(2023, 1, 1))
    """
    
    name = "coingecko"
    supported_assets = ["crypto"]
    
    FREE_BASE_URL = "https://api.coingecko.com/api/v3"
    PRO_BASE_URL = "https://pro-api.coingecko.com/api/v3"
    
    def __init__(self, api_key: Optional[str] = None) -> None:
        """Initialize CoinGecko provider.
        
        Args:
            api_key: CoinGecko API key. Free demo keys available at coingecko.com
        """
        self._api_key = api_key or ""
        
        if not self._api_key:
            try:
                from ...common.cpz_ai import CPZAIClient
                cpz_client = CPZAIClient.from_env()
                creds = cpz_client.get_data_credentials(provider="coingecko")
                self._api_key = creds.get("coingecko_api_key", "")
            except Exception:
                pass
        
        if not self._api_key:
            print("[CPZ SDK] CoinGecko API key not found. Get a free key at coingecko.com")
        
        self._coin_list_cache: Optional[List[Dict[str, Any]]] = None
    
    def _get_base_url(self) -> str:
        """Return Pro or Free base URL based on key length."""
        if self._api_key and len(self._api_key) > 10:
            return self.PRO_BASE_URL
        return self.FREE_BASE_URL
    
    def _get_headers(self) -> Dict[str, str]:
        """Return auth headers based on key type."""
        if not self._api_key:
            return {}
        if len(self._api_key) > 10:
            return {"x-cg-pro-api-key": self._api_key}
        return {"x-cg-demo-api-key": self._api_key}
    
    def _request(self, path: str, params: Optional[Dict[str, Any]] = None) -> Any:
        """Make API request."""
        url = f"{self._get_base_url()}{path}"
        response = requests.get(url, headers=self._get_headers(), params=params, timeout=30)
        response.raise_for_status()
        return response.json()
    
    def _resolve_coin_id(self, symbol: str) -> str:
        """Resolve a symbol/name to a CoinGecko coin ID.
        
        Caches the coin list after the first call.
        """
        normalized = symbol.lower().strip()
        
        if self._coin_list_cache is None:
            try:
                self._coin_list_cache = self._request("/coins/list")
            except Exception:
                self._coin_list_cache = []
                return normalized
        
        for coin in self._coin_list_cache:
            if coin.get("id", "").lower() == normalized:
                return coin["id"]
        
        for coin in self._coin_list_cache:
            if coin.get("symbol", "").lower() == normalized:
                return coin["id"]
        
        for coin in self._coin_list_cache:
            if coin.get("name", "").lower() == normalized:
                return coin["id"]
        
        return normalized
    
    def get_bars(
        self,
        symbol: str,
        timeframe: TimeFrame | str = TimeFrame.DAY,
        start: Optional[datetime] = None,
        end: Optional[datetime] = None,
        limit: Optional[int] = None,
    ) -> List[Bar]:
        """Get historical price data from CoinGecko.
        
        Args:
            symbol: Coin symbol or ID (e.g., "btc" or "bitcoin")
            timeframe: Bar timeframe (granularity auto-selected by CoinGecko)
            start: Start datetime
            end: End datetime
            limit: Maximum bars
            
        Returns:
            List of Bar objects (OHLC all set to price)
        """
        if isinstance(timeframe, str):
            timeframe = TimeFrame.from_string(timeframe)
        
        coin_id = self._resolve_coin_id(symbol)
        
        if not start:
            start = datetime(2020, 1, 1)
        if not end:
            end = datetime.utcnow()
        
        from_ts = int(start.timestamp())
        to_ts = int(end.timestamp())
        
        try:
            data = self._request(
                f"/coins/{coin_id}/market_chart/range",
                params={"vs_currency": "usd", "from": from_ts, "to": to_ts},
            )
            
            prices = data.get("prices", [])
            volumes = data.get("total_volumes", [])
            
            volume_map: Dict[int, float] = {}
            for entry in volumes:
                ts_ms = entry[0]
                volume_map[ts_ms] = entry[1]
            
            bars: List[Bar] = []
            for entry in prices:
                ts_ms = entry[0]
                price = entry[1]
                timestamp = datetime.utcfromtimestamp(ts_ms / 1000.0)
                vol = volume_map.get(ts_ms, 0.0)
                
                bars.append(Bar(
                    symbol=symbol,
                    timestamp=timestamp,
                    open=price,
                    high=price,
                    low=price,
                    close=price,
                    volume=vol,
                    vwap=None,
                ))
            
            bars.sort(key=lambda x: x.timestamp)
            
            if limit and len(bars) > limit:
                bars = bars[-limit:]
            
            return bars
            
        except Exception as e:
            print(f"[CPZ SDK] Error fetching CoinGecko data for {symbol}: {e}")
            return []
    
    def get_quote(self, symbol: str) -> Optional[Quote]:
        """Get latest price for a coin."""
        coin_id = self._resolve_coin_id(symbol)
        
        try:
            data = self._request(
                "/simple/price",
                params={"ids": coin_id, "vs_currencies": "usd"},
            )
            
            coin_data = data.get(coin_id, {})
            price = coin_data.get("usd")
            if price is None:
                return None
            
            return Quote(
                symbol=symbol,
                timestamp=datetime.utcnow(),
                bid=float(price),
                ask=float(price),
                bid_size=0,
                ask_size=0,
            )
            
        except Exception as e:
            print(f"[CPZ SDK] Error fetching CoinGecko quote for {symbol}: {e}")
            return None
    
    def get_market_data(self, symbol: str) -> Dict[str, Any]:
        """Get detailed market data for a coin.
        
        Args:
            symbol: Coin symbol or ID
            
        Returns:
            Full coin data dict from CoinGecko
        """
        coin_id = self._resolve_coin_id(symbol)
        
        try:
            return self._request(f"/coins/{coin_id}")
        except Exception as e:
            print(f"[CPZ SDK] Error fetching CoinGecko market data for {symbol}: {e}")
            return {}
    
    def get_trending(self) -> List[Dict[str, Any]]:
        """Get trending coins on CoinGecko.
        
        Returns:
            List of trending coin dicts
        """
        try:
            data = self._request("/search/trending")
            return data.get("coins", [])
        except Exception as e:
            print(f"[CPZ SDK] Error fetching CoinGecko trending: {e}")
            return []
    
    def search(self, query: str) -> List[Dict[str, Any]]:
        """Search for coins by query string.
        
        Args:
            query: Search query
            
        Returns:
            List of matching coin dicts
        """
        try:
            data = self._request("/search", params={"query": query})
            return data.get("coins", [])
        except Exception as e:
            print(f"[CPZ SDK] Error searching CoinGecko for '{query}': {e}")
            return []
