"""Unified pricing data service with multi-provider support and automatic failover."""

import time
from typing import Dict, List, Optional, Any, Callable
from datetime import datetime
from decimal import Decimal

from .providers.base_provider import BasePricingProvider
from .providers.ccxt_provider import CCXTProvider
from .providers.coingecko_provider import CoinGeckoProvider
from .cache_manager import CacheManager
from .health_monitor import HealthMonitor, HealthStatus
from src.core.config import get_config
from src.core.logger import get_logger

logger = get_logger(__name__)


class PricingService:
    """Unified pricing data service with multi-provider support.
    
    Manages multiple pricing providers with automatic failover, caching,
    and health monitoring. Provides a single consistent API for accessing
    market data regardless of the underlying provider.
    """
    
    def __init__(self):
        """Initialize pricing service."""
        self.config = get_config()
        self.logger = get_logger(__name__)
        
        # Initialize components
        self.cache = CacheManager(
            default_ttl=self.config.get("data_providers.caching.ohlcv_ttl", 60),
            max_size=self.config.get("data_providers.caching.max_cache_size", 1000),
            ticker_ttl=self.config.get("data_providers.caching.ticker_ttl", 2),
            ohlcv_ttl=self.config.get("data_providers.caching.ohlcv_ttl", 60),
        )
        
        self.health_monitor = HealthMonitor()
        
        # Provider instances
        self._providers: Dict[str, BasePricingProvider] = {}
        self._active_provider: Optional[str] = None
        self._provider_priority: List[str] = []
        
        # Subscriptions
        self._subscriptions: Dict[str, List[Callable]] = {}
        
        # Initialize providers
        self._initialize_providers()
    
    def _initialize_providers(self):
        """Initialize providers from configuration."""
        # Get primary providers from config
        primary_config = self.config.get("data_providers.primary", [])
        if not primary_config:
            # Default configuration
            primary_config = [
                {'name': 'kraken', 'enabled': True, 'priority': 1},
                {'name': 'coinbase', 'enabled': True, 'priority': 2},
                {'name': 'binance', 'enabled': True, 'priority': 3},
            ]
        
        # Sort by priority
        primary_config = sorted(
            [p for p in primary_config if p.get('enabled', True)],
            key=lambda x: x.get('priority', 999)
        )
        
        # Create CCXT providers for each exchange
        for provider_config in primary_config:
            exchange_name = provider_config.get('name')
            try:
                provider = CCXTProvider(exchange_name=exchange_name)
                provider_name = provider.name
                
                if provider.connect():
                    self._providers[provider_name] = provider
                    self._provider_priority.append(provider_name)
                    self.logger.info(f"Initialized provider: {provider_name}")
                else:
                    self.logger.warning(f"Failed to connect provider: {provider_name}")
            except Exception as e:
                self.logger.error(f"Error initializing provider {exchange_name}: {e}")
        
        # Add fallback provider (CoinGecko)
        fallback_config = self.config.get("data_providers.fallback", {})
        if fallback_config.get('enabled', True):
            try:
                coingecko = CoinGeckoProvider(api_key=fallback_config.get('api_key'))
                if coingecko.connect():
                    self._providers[coingecko.name] = coingecko
                    self._provider_priority.append(coingecko.name)
                    self.logger.info(f"Initialized fallback provider: {coingecko.name}")
                else:
                    self.logger.warning("Failed to connect CoinGecko fallback provider")
            except Exception as e:
                self.logger.error(f"Error initializing CoinGecko provider: {e}")
        
        # Select initial active provider
        self._select_active_provider()
    
    def _select_active_provider(self) -> Optional[str]:
        """Select the best available provider.
        
        Returns:
            Name of selected provider or None
        """
        # Filter to healthy providers
        healthy_providers = [
            name for name in self._provider_priority
            if name in self._providers
            and self.health_monitor.is_healthy(name)
        ]
        
        if not healthy_providers:
            # Fall back to any available provider if none are healthy
            healthy_providers = list(self._providers.keys())
        
        if not healthy_providers:
            self.logger.error("No providers available")
            self._active_provider = None
            return None
        
        # Select first healthy provider (already sorted by priority)
        self._active_provider = healthy_providers[0]
        self.logger.info(f"Selected active provider: {self._active_provider}")
        return self._active_provider
    
    def _get_provider(self, provider_name: Optional[str] = None) -> Optional[BasePricingProvider]:
        """Get a provider instance.
        
        Args:
            provider_name: Name of provider, or None to use active provider
            
        Returns:
            Provider instance or None
        """
        if provider_name:
            return self._providers.get(provider_name)
        
        # Use active provider, or select one if none active
        if not self._active_provider:
            self._select_active_provider()
        
        return self._providers.get(self._active_provider) if self._active_provider else None
    
    def _execute_with_failover(
        self,
        operation: Callable[[BasePricingProvider], Any],
        operation_name: str
    ) -> Any:
        """Execute an operation with automatic failover.
        
        Args:
            operation: Function that takes a provider and returns a result
            operation_name: Name of operation for logging
            
        Returns:
            Operation result or None if all providers fail
        """
        # Try active provider first
        providers_to_try = [self._active_provider] if self._active_provider else []
        
        # Add other providers in priority order
        for provider_name in self._provider_priority:
            if provider_name != self._active_provider and provider_name in self._providers:
                providers_to_try.append(provider_name)
        
        last_error = None
        
        for provider_name in providers_to_try:
            provider = self._providers.get(provider_name)
            if not provider:
                continue
            
            # Check health
            if not self.health_monitor.is_healthy(provider_name):
                self.logger.debug(f"Skipping unhealthy provider: {provider_name}")
                continue
            
            try:
                start_time = time.time()
                result = operation(provider)
                response_time = time.time() - start_time
                
                # Record success
                self.health_monitor.record_success(provider_name, response_time)
                
                # Update active provider if we used a different one
                if provider_name != self._active_provider:
                    self.logger.info(f"Switched to provider: {provider_name}")
                    self._active_provider = provider_name
                
                return result
                
            except Exception as e:
                last_error = e
                self.logger.warning(f"{operation_name} failed on {provider_name}: {e}")
                self.health_monitor.record_failure(provider_name)
                
                # Try next provider
                continue
        
        # All providers failed
        self.logger.error(f"{operation_name} failed on all providers")
        if last_error:
            raise last_error
        
        return None
    
    def get_ticker(self, symbol: str, use_cache: bool = True) -> Dict[str, Any]:
        """Get current ticker data for a symbol.
        
        Args:
            symbol: Trading pair symbol (e.g., 'BTC/USD')
            use_cache: Whether to use cache
            
        Returns:
            Ticker data dictionary
        """
        cache_key = f"ticker:{symbol}"
        
        # Check cache
        if use_cache:
            cached = self.cache.get(cache_key)
            if cached:
                return cached
        
        # Fetch from provider
        def fetch_ticker(provider: BasePricingProvider):
            return provider.get_ticker(symbol)
        
        ticker_data = self._execute_with_failover(fetch_ticker, f"get_ticker({symbol})")
        
        if ticker_data:
            # Cache the result
            if use_cache:
                self.cache.set(cache_key, ticker_data, cache_type='ticker')
            
            return ticker_data
        
        return {}
    
    def get_ohlcv(
        self,
        symbol: str,
        timeframe: str = '1h',
        since: Optional[datetime] = None,
        limit: int = 100,
        use_cache: bool = True
    ) -> List[List]:
        """Get OHLCV candlestick data.
        
        Args:
            symbol: Trading pair symbol
            timeframe: Timeframe (1m, 5m, 15m, 1h, 1d, etc.)
            since: Start datetime
            limit: Number of candles
            use_cache: Whether to use cache
            
        Returns:
            List of [timestamp_ms, open, high, low, close, volume]
        """
        cache_key = f"ohlcv:{symbol}:{timeframe}:{limit}"
        
        # Check cache (only if no 'since' parameter, as it changes the result)
        if use_cache and not since:
            cached = self.cache.get(cache_key)
            if cached:
                return cached
        
        # Fetch from provider
        def fetch_ohlcv(provider: BasePricingProvider):
            return provider.get_ohlcv(symbol, timeframe, since, limit)
        
        ohlcv_data = self._execute_with_failover(
            fetch_ohlcv,
            f"get_ohlcv({symbol}, {timeframe})"
        )
        
        if ohlcv_data:
            # Cache the result (only if no 'since' parameter)
            if use_cache and not since:
                self.cache.set(cache_key, ohlcv_data, cache_type='ohlcv')
            
            return ohlcv_data
        
        return []
    
    def subscribe_ticker(self, symbol: str, callback: Callable) -> bool:
        """Subscribe to ticker updates.
        
        Args:
            symbol: Trading pair symbol
            callback: Callback function(data) called on price updates
            
        Returns:
            True if subscription successful
        """
        key = f"ticker:{symbol}"
        
        # Add callback
        if key not in self._subscriptions:
            self._subscriptions[key] = []
        if callback not in self._subscriptions[key]:
            self._subscriptions[key].append(callback)
        
        # Wrap callback to handle failover
        def wrapped_callback(data):
            for cb in self._subscriptions.get(key, []):
                try:
                    cb(data)
                except Exception as e:
                    self.logger.error(f"Callback error for {symbol}: {e}")
        
        # Subscribe via active provider
        provider = self._get_provider()
        if provider:
            try:
                success = provider.subscribe_ticker(symbol, wrapped_callback)
                if success:
                    self.logger.info(f"Subscribed to ticker updates for {symbol}")
                    return True
            except Exception as e:
                self.logger.error(f"Failed to subscribe to ticker for {symbol}: {e}")
        
        return False
    
    def unsubscribe_ticker(self, symbol: str, callback: Optional[Callable] = None):
        """Unsubscribe from ticker updates.
        
        Args:
            symbol: Trading pair symbol
            callback: Specific callback to remove, or None to remove all
        """
        key = f"ticker:{symbol}"
        
        # Remove callback
        if key in self._subscriptions:
            if callback:
                if callback in self._subscriptions[key]:
                    self._subscriptions[key].remove(callback)
                if not self._subscriptions[key]:
                    del self._subscriptions[key]
            else:
                del self._subscriptions[key]
        
        # Unsubscribe from all providers
        for provider in self._providers.values():
            try:
                provider.unsubscribe_ticker(symbol, callback)
            except Exception:
                pass
        
        self.logger.info(f"Unsubscribed from ticker updates for {symbol}")
    
    def get_active_provider(self) -> Optional[str]:
        """Get name of active provider.
        
        Returns:
            Provider name or None
        """
        return self._active_provider
    
    def get_provider_health(self, provider_name: Optional[str] = None) -> Dict[str, Any]:
        """Get health status for a provider or all providers.
        
        Args:
            provider_name: Provider name, or None for all providers
            
        Returns:
            Health status dictionary
        """
        if provider_name:
            metrics = self.health_monitor.get_metrics(provider_name)
            if metrics:
                return metrics.to_dict()
            return {}
        
        return self.health_monitor.get_all_metrics()
    
    def get_cache_stats(self) -> Dict[str, Any]:
        """Get cache statistics.
        
        Returns:
            Cache statistics dictionary
        """
        return self.cache.get_stats()


# Global pricing service instance
_pricing_service: Optional[PricingService] = None


def get_pricing_service() -> PricingService:
    """Get global pricing service instance.
    
    Returns:
        PricingService instance
    """
    global _pricing_service
    if _pricing_service is None:
        _pricing_service = PricingService()
    return _pricing_service
