"""
Exchange rate update engine for automatic rate fetching
"""

import logging
import requests
import threading
from datetime import datetime, date
from typing import Dict, Any, Optional, List
from decimal import Decimal as Dec

logger = logging.getLogger(__name__)


class ExchangeRateUpdater:
    """
    Handles automatic exchange rate updates from external API providers.
    
    Features:
    - Multiple API provider support with failover
    - Rate limiting per provider
    - Health monitoring for providers
    - Historical rate tracking
    - Separate update intervals for fiat and crypto
    """
    
    # Update intervals in seconds
    FIAT_UPDATE_INTERVAL = 86400  # 24 hours
    CRYPTO_UPDATE_INTERVAL = 3600  # 1 hour
    
    # Provider types
    TYPE_FIAT = 'fiat'
    TYPE_CRYPTO = 'crypto'
    TYPE_BOTH = 'both'
    
    def __init__(self, db_manager, message_bus=None):
        """
        Initialize exchange rate updater.
        
        Args:
            db_manager: Database manager instance
            message_bus: Optional message bus for notifications
        """
        self.db_manager = db_manager
        self.message_bus = message_bus
        self._update_thread: Optional[threading.Thread] = None
        self._stop_event = threading.Event()
        self._last_fiat_update: Optional[datetime] = None
        self._last_crypto_update: Optional[datetime] = None
        logger.info("ExchangeRateUpdater initialized")
    
    def update_all_rates(self, force: bool = False) -> Dict[str, Any]:
        """
        Update exchange rates for all currencies.
        
        Args:
            force: Force update even if interval hasn't elapsed
            
        Returns:
            Dict with update results
        """
        results = {
            'fiat': {'success': 0, 'failed': 0, 'rates': {}},
            'crypto': {'success': 0, 'failed': 0, 'rates': {}},
            'errors': []
        }
        
        try:
            # Update fiat rates
            fiat_result = self._update_fiat_rates(force)
            results['fiat'] = fiat_result
            
            # Update crypto rates
            crypto_result = self._update_crypto_rates(force)
            results['crypto'] = crypto_result
            
            logger.info(f"Exchange rate update completed: "
                       f"fiat={results['fiat']['success']}/{results['fiat']['failed']}, "
                       f"crypto={results['crypto']['success']}/{results['crypto']['failed']}")
            
        except Exception as e:
            logger.error(f"Failed to update all rates: {e}")
            results['errors'].append(str(e))
        
        return results
    
    def update_currency_rate(self, currency_code: str, 
                              force: bool = False) -> Dict[str, Any]:
        """
        Update exchange rate for a specific currency.
        
        Args:
            currency_code: Currency code to update
            force: Force update even if recently updated
            
        Returns:
            Dict with update result
        """
        try:
            session = self.db_manager.get_session()
            try:
                from ..database.models import CurrencyModel
                
                currency = session.query(CurrencyModel).filter_by(
                    code=currency_code.upper(),
                    is_active=True
                ).first()
                
                if not currency:
                    return {
                        'success': False,
                        'error': f'Currency {currency_code} not found'
                    }
                
                # Determine provider type based on currency type
                provider_type = currency.currency_type
                
                # Get best provider for this currency type
                provider = self._get_best_provider(provider_type)
                
                if not provider:
                    return {
                        'success': False,
                        'error': f'No active provider for {provider_type} rates'
                    }
                
                # Fetch rate from provider
                rate = self._fetch_rate_from_provider(provider, currency_code)
                
                if rate is not None:
                    # Update currency rate
                    old_rate = float(currency.exchange_rate)
                    currency.exchange_rate = rate
                    currency.last_rate_update = datetime.utcnow()
                    currency.exchange_rate_source = provider['provider_name']
                    
                    # Record historical rate
                    self._record_historical_rate(
                        currency.id, 
                        currency.base_currency,
                        currency_code, 
                        rate, 
                        provider['provider_name']
                    )
                    
                    session.commit()
                    
                    logger.info(f"Updated {currency_code} rate: {old_rate} -> {rate}")
                    
                    return {
                        'success': True,
                        'currency': currency_code,
                        'old_rate': old_rate,
                        'new_rate': rate,
                        'provider': provider['provider_name']
                    }
                else:
                    return {
                        'success': False,
                        'currency': currency_code,
                        'error': 'Failed to fetch rate from provider'
                    }
                    
            finally:
                session.close()
                
        except Exception as e:
            logger.error(f"Failed to update rate for {currency_code}: {e}")
            return {
                'success': False,
                'currency': currency_code,
                'error': str(e)
            }
    
    def _update_fiat_rates(self, force: bool = False) -> Dict[str, Any]:
        """Update all fiat currency rates."""
        result = {'success': 0, 'failed': 0, 'rates': {}}
        
        try:
            # Check if update is needed
            if not force and not self._should_update(self.TYPE_FIAT):
                logger.debug("Fiat rates recently updated, skipping")
                return result
            
            session = self.db_manager.get_session()
            try:
                from ..database.models import CurrencyModel
                
                currencies = session.query(CurrencyModel).filter_by(
                    currency_type='fiat',
                    is_active=True
                ).all()
                
                provider = self._get_best_provider(self.TYPE_FIAT)
                
                if not provider:
                    logger.warning("No active fiat rate provider")
                    return result
                
                for currency in currencies:
                    if currency.code == 'USD':  # Skip base currency
                        result['rates'][currency.code] = 1.0
                        result['success'] += 1
                        continue
                    
                    rate = self._fetch_rate_from_provider(provider, currency.code)
                    
                    if rate is not None:
                        old_rate = float(currency.exchange_rate)
                        currency.exchange_rate = rate
                        currency.last_rate_update = datetime.utcnow()
                        currency.exchange_rate_source = provider['provider_name']
                        
                        self._record_historical_rate(
                            currency.id, 'USD', currency.code, 
                            rate, provider['provider_name']
                        )
                        
                        result['rates'][currency.code] = rate
                        result['success'] += 1
                        logger.debug(f"Updated {currency.code}: {old_rate} -> {rate}")
                    else:
                        result['failed'] += 1
                        logger.warning(f"Failed to update {currency.code}")
                
                session.commit()
                self._last_fiat_update = datetime.utcnow()
                
            finally:
                session.close()
                
        except Exception as e:
            logger.error(f"Failed to update fiat rates: {e}")
            result['failed'] += 1
        
        return result
    
    def _update_crypto_rates(self, force: bool = False) -> Dict[str, Any]:
        """Update all cryptocurrency rates."""
        result = {'success': 0, 'failed': 0, 'rates': {}}
        
        try:
            # Check if update is needed
            if not force and not self._should_update(self.TYPE_CRYPTO):
                logger.debug("Crypto rates recently updated, skipping")
                return result
            
            session = self.db_manager.get_session()
            try:
                from ..database.models import CurrencyModel
                
                currencies = session.query(CurrencyModel).filter_by(
                    currency_type='crypto',
                    is_active=True
                ).all()
                
                provider = self._get_best_provider(self.TYPE_CRYPTO)
                
                if not provider:
                    logger.warning("No active crypto rate provider")
                    return result
                
                for currency in currencies:
                    rate = self._fetch_rate_from_provider(provider, currency.code)
                    
                    if rate is not None:
                        old_rate = float(currency.exchange_rate)
                        currency.exchange_rate = rate
                        currency.last_rate_update = datetime.utcnow()
                        currency.exchange_rate_source = provider['provider_name']
                        
                        self._record_historical_rate(
                            currency.id, 'USD', currency.code, 
                            rate, provider['provider_name']
                        )
                        
                        result['rates'][currency.code] = rate
                        result['success'] += 1
                        logger.debug(f"Updated {currency.code}: {old_rate} -> {rate}")
                    else:
                        result['failed'] += 1
                        logger.warning(f"Failed to update {currency.code}")
                
                session.commit()
                self._last_crypto_update = datetime.utcnow()
                
            finally:
                session.close()
                
        except Exception as e:
            logger.error(f"Failed to update crypto rates: {e}")
            result['failed'] += 1
        
        return result
    
    def _should_update(self, provider_type: str) -> bool:
        """Check if rates should be updated based on interval."""
        if provider_type == self.TYPE_FIAT:
            if self._last_fiat_update is None:
                return True
            elapsed = (datetime.utcnow() - self._last_fiat_update).total_seconds()
            return elapsed >= self.FIAT_UPDATE_INTERVAL
        else:
            if self._last_crypto_update is None:
                return True
            elapsed = (datetime.utcnow() - self._last_crypto_update).total_seconds()
            return elapsed >= self.CRYPTO_UPDATE_INTERVAL
    
    def _get_best_provider(self, provider_type: str) -> Optional[Dict[str, Any]]:
        """
        Get the best available provider for a currency type.
        
        Args:
            provider_type: 'fiat', 'crypto', or 'both'
            
        Returns:
            Provider dict or None
        """
        try:
            session = self.db_manager.get_session()
            try:
                from ..database.models import ExchangeRateProviderModel
                
                query = session.query(ExchangeRateProviderModel).filter_by(
                    is_active=True
                )
                
                # Filter by provider type
                if provider_type == self.TYPE_FIAT:
                    query = query.filter(
                        ExchangeRateProviderModel.provider_type.in_(
                            [self.TYPE_FIAT, self.TYPE_BOTH]
                        )
                    )
                elif provider_type == self.TYPE_CRYPTO:
                    query = query.filter(
                        ExchangeRateProviderModel.provider_type.in_(
                            [self.TYPE_CRYPTO, self.TYPE_BOTH]
                        )
                    )
                
                # Order by priority (lower = higher priority) and health
                providers = query.order_by(
                    ExchangeRateProviderModel.priority.asc()
                ).all()
                
                for provider in providers:
                    # Check if provider is healthy (not too many failures)
                    if provider.failure_count < 5:
                        return {
                            'id': provider.id,
                            'provider_name': provider.provider_name,
                            'provider_type': provider.provider_type,
                            'api_url': provider.api_url,
                            'api_key': provider.api_key,
                            'api_secret': provider.api_secret,
                            'rate_limit_per_hour': provider.rate_limit_per_hour,
                            'rate_limit_per_day': provider.rate_limit_per_day
                        }
                
                return None
                
            finally:
                session.close()
                
        except Exception as e:
            logger.error(f"Failed to get best provider: {e}")
            return None
    
    def _fetch_rate_from_provider(self, provider: Dict[str, Any], 
                                    currency_code: str) -> Optional[float]:
        """
        Fetch exchange rate from a provider.
        
        Args:
            provider: Provider configuration dict
            currency_code: Currency code to fetch
            
        Returns:
            Exchange rate or None
        """
        try:
            provider_name = provider['provider_name'].lower()
            
            if 'coingecko' in provider_name:
                return self._fetch_from_coingecko(provider, currency_code)
            elif 'coinmarketcap' in provider_name:
                return self._fetch_from_coinmarketcap(provider, currency_code)
            elif 'openexchangerates' in provider_name:
                return self._fetch_from_openexchangerates(provider, currency_code)
            elif 'fixer' in provider_name:
                return self._fetch_from_fixer(provider, currency_code)
            else:
                # Generic API call
                return self._fetch_from_generic(provider, currency_code)
                
        except Exception as e:
            logger.error(f"Failed to fetch rate from {provider['provider_name']}: {e}")
            self._record_provider_failure(provider['id'])
            return None
    
    def _fetch_from_coingecko(self, provider: Dict[str, Any], 
                               currency_code: str) -> Optional[float]:
        """Fetch rate from CoinGecko API."""
        try:
            # CoinGecko uses lowercase IDs
            coin_ids = {
                'BTC': 'bitcoin',
                'ETH': 'ethereum',
                'USDT': 'tether',
                'USDC': 'usd-coin',
                'BNB': 'binancecoin',
                'SOL': 'solana',
                'MATIC': 'matic-network',
                'DOGE': 'dogecoin',
                'SHIB': 'shiba-inu',
                'ADA': 'cardano',
                'XRP': 'ripple',
                'DOT': 'polkadot',
                'AVAX': 'avalanche-2',
                'LINK': 'chainlink',
                'UNI': 'uniswap'
            }
            
            coin_id = coin_ids.get(currency_code.upper())
            if not coin_id:
                logger.warning(f"Unknown coin ID for {currency_code}")
                return None
            
            url = f"{provider['api_url']}/simple/price"
            params = {
                'ids': coin_id,
                'vs_currencies': 'usd'
            }
            
            if provider.get('api_key'):
                params['x_cg_demo_api_key'] = provider['api_key']
            
            response = requests.get(url, params=params, timeout=10)
            
            if response.status_code == 200:
                data = response.json()
                if coin_id in data and 'usd' in data[coin_id]:
                    rate = float(data[coin_id]['usd'])
                    self._record_provider_success(provider['id'])
                    return rate
            
            logger.warning(f"CoinGecko returned status {response.status_code}")
            self._record_provider_failure(provider['id'])
            return None
            
        except Exception as e:
            logger.error(f"CoinGecko fetch error: {e}")
            self._record_provider_failure(provider['id'])
            return None
    
    def _fetch_from_coinmarketcap(self, provider: Dict[str, Any], 
                                    currency_code: str) -> Optional[float]:
        """Fetch rate from CoinMarketCap API."""
        try:
            url = f"{provider['api_url']}/v1/cryptocurrency/quotes/latest"
            headers = {
                'X-CMC_PRO_API_KEY': provider.get('api_key', '')
            }
            params = {
                'symbol': currency_code.upper(),
                'convert': 'USD'
            }
            
            response = requests.get(url, headers=headers, params=params, timeout=10)
            
            if response.status_code == 200:
                data = response.json()
                symbol = currency_code.upper()
                if 'data' in data and symbol in data['data']:
                    quote = data['data'][symbol]['quote']['USD']
                    rate = float(quote['price'])
                    self._record_provider_success(provider['id'])
                    return rate
            
            logger.warning(f"CoinMarketCap returned status {response.status_code}")
            self._record_provider_failure(provider['id'])
            return None
            
        except Exception as e:
            logger.error(f"CoinMarketCap fetch error: {e}")
            self._record_provider_failure(provider['id'])
            return None
    
    def _fetch_from_openexchangerates(self, provider: Dict[str, Any], 
                                        currency_code: str) -> Optional[float]:
        """Fetch rate from Open Exchange Rates API."""
        try:
            url = f"{provider['api_url']}/latest.json"
            params = {
                'app_id': provider.get('api_key', ''),
                'base': 'USD'
            }
            
            response = requests.get(url, params=params, timeout=10)
            
            if response.status_code == 200:
                data = response.json()
                if 'rates' in data:
                    # Open Exchange Rates returns rates relative to USD
                    # To convert USD to target currency, we need 1/rate
                    rate = data['rates'].get(currency_code.upper())
                    if rate:
                        self._record_provider_success(provider['id'])
                        return float(rate)
            
            logger.warning(f"OpenExchangeRates returned status {response.status_code}")
            self._record_provider_failure(provider['id'])
            return None
            
        except Exception as e:
            logger.error(f"OpenExchangeRates fetch error: {e}")
            self._record_provider_failure(provider['id'])
            return None
    
    def _fetch_from_fixer(self, provider: Dict[str, Any], 
                           currency_code: str) -> Optional[float]:
        """Fetch rate from Fixer.io API."""
        try:
            url = f"{provider['api_url']}/latest"
            params = {
                'access_key': provider.get('api_key', ''),
                'base': 'USD',
                'symbols': currency_code.upper()
            }
            
            response = requests.get(url, params=params, timeout=10)
            
            if response.status_code == 200:
                data = response.json()
                if 'rates' in data and data.get('success'):
                    rate = data['rates'].get(currency_code.upper())
                    if rate:
                        self._record_provider_success(provider['id'])
                        return float(rate)
            
            logger.warning(f"Fixer returned status {response.status_code}")
            self._record_provider_failure(provider['id'])
            return None
            
        except Exception as e:
            logger.error(f"Fixer fetch error: {e}")
            self._record_provider_failure(provider['id'])
            return None
    
    def _fetch_from_generic(self, provider: Dict[str, Any], 
                             currency_code: str) -> Optional[float]:
        """Fetch rate from generic API endpoint."""
        try:
            url = provider['api_url'].replace('{currency}', currency_code.upper())
            headers = {}
            
            if provider.get('api_key'):
                headers['Authorization'] = f"Bearer {provider['api_key']}"
            
            response = requests.get(url, headers=headers, timeout=10)
            
            if response.status_code == 200:
                data = response.json()
                # Try common response structures
                if 'rate' in data:
                    rate = float(data['rate'])
                elif 'price' in data:
                    rate = float(data['price'])
                elif 'rates' in data and currency_code.upper() in data['rates']:
                    rate = float(data['rates'][currency_code.upper()])
                else:
                    logger.warning(f"Unknown response structure from {provider['provider_name']}")
                    return None
                
                self._record_provider_success(provider['id'])
                return rate
            
            logger.warning(f"Generic API returned status {response.status_code}")
            self._record_provider_failure(provider['id'])
            return None
            
        except Exception as e:
            logger.error(f"Generic API fetch error: {e}")
            self._record_provider_failure(provider['id'])
            return None
    
    def _record_provider_success(self, provider_id: int):
        """Record successful API call for provider."""
        try:
            session = self.db_manager.get_session()
            try:
                from ..database.models import ExchangeRateProviderModel
                
                provider = session.query(ExchangeRateProviderModel).get(provider_id)
                if provider:
                    provider.last_successful_call = datetime.utcnow()
                    provider.failure_count = 0
                    session.commit()
                    
            finally:
                session.close()
                
        except Exception as e:
            logger.error(f"Failed to record provider success: {e}")
    
    def _record_provider_failure(self, provider_id: int):
        """Record failed API call for provider."""
        try:
            session = self.db_manager.get_session()
            try:
                from ..database.models import ExchangeRateProviderModel
                
                provider = session.query(ExchangeRateProviderModel).get(provider_id)
                if provider:
                    provider.last_failed_call = datetime.utcnow()
                    provider.failure_count = (provider.failure_count or 0) + 1
                    session.commit()
                    
            finally:
                session.close()
                
        except Exception as e:
            logger.error(f"Failed to record provider failure: {e}")
    
    def _record_historical_rate(self, currency_id: int, from_currency: str, 
                                 to_currency: str, rate: float, source: str):
        """Record exchange rate in history table."""
        try:
            session = self.db_manager.get_session()
            try:
                from ..database.models import ExchangeRateHistoryModel
                
                history = ExchangeRateHistoryModel(
                    currency_id=currency_id,
                    from_currency=from_currency,
                    to_currency=to_currency,
                    exchange_rate=rate,
                    rate_source=source,
                    recorded_at=datetime.utcnow()
                )
                session.add(history)
                session.commit()
                
            finally:
                session.close()
                
        except Exception as e:
            logger.error(f"Failed to record historical rate: {e}")
    
    def start_background_updates(self):
        """Start background thread for automatic rate updates."""
        if self._update_thread and self._update_thread.is_alive():
            logger.warning("Background update thread already running")
            return
        
        self._stop_event.clear()
        self._update_thread = threading.Thread(target=self._background_update_loop, daemon=True)
        self._update_thread.start()
        logger.info("Started background exchange rate updates")
    
    def stop_background_updates(self):
        """Stop background update thread."""
        if self._update_thread:
            self._stop_event.set()
            self._update_thread.join(timeout=5)
            logger.info("Stopped background exchange rate updates")
    
    def _background_update_loop(self):
        """Background loop for automatic rate updates."""
        import time
        
        while not self._stop_event.is_set():
            try:
                # Check and update rates
                self.update_all_rates(force=False)
                
                # Sleep for 1 minute before checking again
                for _ in range(60):
                    if self._stop_event.is_set():
                        break
                    time.sleep(1)
                    
            except Exception as e:
                logger.error(f"Error in background update loop: {e}")
                time.sleep(60)