"""CoinGecko pricing provider for fallback market data."""

import httpx
import time
import threading
from decimal import Decimal
from typing import Dict, List, Optional, Any, Callable, Tuple
from datetime import datetime
from .base_provider import BasePricingProvider
from src.core.logger import get_logger

logger = get_logger(__name__)


class CoinGeckoProvider(BasePricingProvider):
    """CoinGecko API pricing provider.
    
    This provider uses CoinGecko's free API tier as a fallback when CCXT
    providers are unavailable. It uses simple REST endpoints that don't
    require authentication.
    """
    
    BASE_URL = "https://api.coingecko.com/api/v3"
    
    # CoinGecko coin ID mapping for common symbols
    COIN_ID_MAP = {
        'BTC': 'bitcoin',
        'ETH': 'ethereum',
        'BNB': 'binancecoin',
        'SOL': 'solana',
        'ADA': 'cardano',
        'XRP': 'ripple',
        'DOGE': 'dogecoin',
        'DOT': 'polkadot',
        'MATIC': 'matic-network',
        'AVAX': 'avalanche-2',
        'LINK': 'chainlink',
        'USDT': 'tether',
        'USDC': 'usd-coin',
        'DAI': 'dai',
    }
    
    # Currency mapping
    CURRENCY_MAP = {
        'USD': 'usd',
        'EUR': 'eur',
        'GBP': 'gbp',
        'JPY': 'jpy',
        'USDT': 'usd',  # CoinGecko uses USD for stablecoins
    }
    
    def __init__(self, api_key: Optional[str] = None):
        """Initialize CoinGecko provider.
        
        Args:
            api_key: Optional API key for higher rate limits (free tier doesn't require it)
        """
        super().__init__()
        self.api_key = api_key
        self._client = None
        self._polling_threads: Dict[str, threading.Thread] = {}
        self._stop_polling: Dict[str, bool] = {}
        self._rate_limit_delay = 1.0  # Free tier: ~30-50 calls/minute
    
    @property
    def name(self) -> str:
        """Provider name."""
        return "CoinGecko"
    
    @property
    def supports_websocket(self) -> bool:
        """CoinGecko free tier doesn't support WebSocket."""
        return False
    
    def connect(self) -> bool:
        """Connect to CoinGecko API.
        
        Returns:
            True if connection successful (just validates API access)
        """
        try:
            self._client = httpx.Client(timeout=10.0)
            
            # Test connection by fetching Bitcoin price
            response = self._client.get(
                f"{self.BASE_URL}/simple/price",
                params={
                    'ids': 'bitcoin',
                    'vs_currencies': 'usd',
                }
            )
            
            if response.status_code == 200:
                data = response.json()
                if 'bitcoin' in data:
                    self._connected = True
                    logger.info("Connected to CoinGecko API")
                    return True
            
            logger.warning("CoinGecko API test failed")
            self._connected = False
            return False
            
        except Exception as e:
            logger.error(f"Failed to connect to CoinGecko: {e}")
            self._connected = False
            return False
    
    def disconnect(self):
        """Disconnect from CoinGecko API."""
        # Stop all polling threads
        for symbol in list(self._stop_polling.keys()):
            self._stop_polling[symbol] = True
        
        # Wait for threads to stop
        for symbol, thread in self._polling_threads.items():
            if thread.is_alive():
                thread.join(timeout=2.0)
        
        self._polling_threads.clear()
        self._stop_polling.clear()
        self._subscribers.clear()
        
        if self._client:
            self._client.close()
            self._client = None
        
        self._connected = False
        logger.info("Disconnected from CoinGecko API")
    
    def _parse_symbol(self, symbol: str) -> Tuple[Optional[str], Optional[str]]:
        """Parse symbol into coin_id and currency.
        
        Args:
            symbol: Trading pair like 'BTC/USD' or 'BTC/USDT'
            
        Returns:
            Tuple of (coin_id, currency) or (None, None) if not found
        """
        parts = symbol.upper().replace('-', '/').split('/')
        if len(parts) != 2:
            return None, None
        
        base, quote = parts
        
        # Get coin ID
        coin_id = self.COIN_ID_MAP.get(base)
        if not coin_id:
            # Try lowercase base as fallback
            coin_id = base.lower()
        
        # Get currency
        currency = self.CURRENCY_MAP.get(quote, quote.lower())
        
        return coin_id, currency
    
    def get_ticker(self, symbol: str) -> Dict[str, Any]:
        """Get current ticker data from CoinGecko."""
        if not self._connected or not self._client:
            logger.error("Provider not connected")
            return {}
        
        try:
            coin_id, currency = self._parse_symbol(symbol)
            if not coin_id:
                logger.error(f"Unknown symbol format: {symbol}")
                return {}
            
            # Fetch current price
            response = self._client.get(
                f"{self.BASE_URL}/simple/price",
                params={
                    'ids': coin_id,
                    'vs_currencies': currency,
                    'include_24hr_change': 'true',
                    'include_24hr_vol': 'true',
                }
            )
            
            if response.status_code != 200:
                logger.error(f"CoinGecko API error: {response.status_code}")
                return {}
            
            data = response.json()
            if coin_id not in data:
                logger.error(f"Coin {coin_id} not found in CoinGecko response")
                return {}
            
            coin_data = data[coin_id]
            price_key = currency.lower()
            
            if price_key not in coin_data:
                logger.error(f"Currency {currency} not found for {coin_id}")
                return {}
            
            price = Decimal(str(coin_data[price_key]))
            
            # Calculate high/low from 24h change if available
            change_key = f"{price_key}_24h_change"
            vol_key = f"{price_key}_24h_vol"
            
            change_24h = coin_data.get(change_key, 0) or 0
            volume_24h = coin_data.get(vol_key, 0) or 0
            
            # Estimate high/low from current price and 24h change
            # This is approximate since CoinGecko free tier doesn't provide exact high/low
            current_price = float(price)
            if change_24h:
                estimated_high = current_price * (1 + abs(change_24h / 100) / 2)
                estimated_low = current_price * (1 - abs(change_24h / 100) / 2)
            else:
                estimated_high = current_price
                estimated_low = current_price
            
            return {
                'symbol': symbol,
                'bid': price,  # CoinGecko doesn't provide bid/ask, use last price
                'ask': price,
                'last': price,
                'high': Decimal(str(estimated_high)),
                'low': Decimal(str(estimated_low)),
                'volume': Decimal(str(volume_24h)),
                'timestamp': int(time.time() * 1000),
            }
            
        except Exception as e:
            logger.error(f"Failed to get ticker for {symbol}: {e}")
            return {}
    
    def get_ohlcv(
        self,
        symbol: str,
        timeframe: str = '1h',
        since: Optional[datetime] = None,
        limit: int = 100
    ) -> List[List]:
        """Get OHLCV data from CoinGecko.
        
        Note: CoinGecko free tier has limited historical data access.
        This method may return empty data for some timeframes.
        """
        if not self._connected or not self._client:
            logger.error("Provider not connected")
            return []
        
        try:
            coin_id, currency = self._parse_symbol(symbol)
            if not coin_id:
                logger.error(f"Unknown symbol format: {symbol}")
                return []
            
            # CoinGecko uses days parameter instead of timeframe
            # Map timeframe to days
            timeframe_days_map = {
                '1m': 1,  # Last 24 hours, minute data (not available in free tier)
                '5m': 1,
                '15m': 1,
                '30m': 1,
                '1h': 1,  # Last 24 hours, hourly data
                '4h': 7,  # Last 7 days
                '1d': 30,  # Last 30 days
                '1w': 90,  # Last 90 days
            }
            
            days = timeframe_days_map.get(timeframe, 7)
            
            # Fetch OHLC data
            response = self._client.get(
                f"{self.BASE_URL}/coins/{coin_id}/ohlc",
                params={
                    'vs_currency': currency,
                    'days': days,
                }
            )
            
            if response.status_code != 200:
                logger.warning(f"CoinGecko OHLC API returned {response.status_code}")
                return []
            
            data = response.json()
            
            # CoinGecko returns: [timestamp_ms, open, high, low, close]
            # We need to convert to: [timestamp_ms, open, high, low, close, volume]
            # Note: CoinGecko OHLC endpoint doesn't include volume
            
            # Filter by since if provided
            if since:
                since_timestamp = int(since.timestamp() * 1000)
                data = [candle for candle in data if candle[0] >= since_timestamp]
            
            # Limit results
            data = data[-limit:] if limit else data
            
            # Add volume as 0 (CoinGecko doesn't provide it in OHLC endpoint)
            ohlcv = [candle + [0] for candle in data]
            
            logger.debug(f"Fetched {len(ohlcv)} candles for {symbol} from CoinGecko")
            return ohlcv
            
        except Exception as e:
            logger.error(f"Failed to get OHLCV for {symbol}: {e}")
            return []
    
    def subscribe_ticker(self, symbol: str, callback: Callable) -> bool:
        """Subscribe to ticker updates via polling."""
        if not self._connected or not self._client:
            logger.error("Provider not connected")
            return False
        
        key = f"ticker_{symbol}"
        
        # Add callback to subscribers
        if key not in self._subscribers:
            self._subscribers[key] = []
        if callback not in self._subscribers[key]:
            self._subscribers[key].append(callback)
        
        # Start polling thread if not already running
        if key not in self._polling_threads or not self._polling_threads[key].is_alive():
            self._stop_polling[key] = False
            
            def poll_ticker():
                """Poll ticker respecting rate limits."""
                while not self._stop_polling.get(key, False):
                    try:
                        ticker_data = self.get_ticker(symbol)
                        if ticker_data and 'last' in ticker_data:
                            # Call all callbacks
                            for cb in self._subscribers.get(key, []):
                                try:
                                    cb({
                                        'symbol': symbol,
                                        'price': ticker_data['last'],
                                        'bid': ticker_data.get('bid', 0),
                                        'ask': ticker_data.get('ask', 0),
                                        'volume': ticker_data.get('volume', 0),
                                        'timestamp': ticker_data.get('timestamp'),
                                    })
                                except Exception as e:
                                    logger.error(f"Callback error for {symbol}: {e}")
                        
                        # Rate limit: wait between requests (free tier: ~30-50 calls/min)
                        time.sleep(self._rate_limit_delay * 2)  # Poll every 2 seconds
                    except Exception as e:
                        logger.error(f"Ticker polling error for {symbol}: {e}")
                        time.sleep(10)  # Wait longer on error
            
            thread = threading.Thread(target=poll_ticker, daemon=True)
            thread.start()
            self._polling_threads[key] = thread
            logger.info(f"Subscribed to ticker updates for {symbol} (CoinGecko polling)")
            return True
        
        return True
    
    def unsubscribe_ticker(self, symbol: str, callback: Optional[Callable] = None):
        """Unsubscribe from ticker updates."""
        key = f"ticker_{symbol}"
        
        # Stop polling
        if key in self._stop_polling:
            self._stop_polling[key] = True
        
        # Remove from subscribers
        super().unsubscribe_ticker(symbol, callback)
        
        # Clean up thread
        if key in self._polling_threads:
            del self._polling_threads[key]
        
        logger.info(f"Unsubscribed from ticker updates for {symbol}")
    
    def normalize_symbol(self, symbol: str) -> str:
        """Normalize symbol format."""
        # CoinGecko uses coin IDs, so we just normalize the format
        return symbol.replace('-', '/').upper()
