"""
Currency conversion engine for multi-currency support
"""

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

logger = logging.getLogger(__name__)


class CurrencyConverter:
    """
    Handles currency conversion for fiat and cryptocurrencies.
    
    Features:
    - Convert between any supported currencies
    - Use historical exchange rates for accurate reporting
    - Cache exchange rates for performance
    - Support for both fiat and crypto currencies
    """
    
    # Base currency for all conversions
    BASE_CURRENCY = 'USD'
    
    # Supported fiat currencies
    FIAT_CURRENCIES = ['USD', 'EUR', 'GBP', 'ZAR', 'KES', 'UGX', 'NGN', 'GHS']
    
    # Supported cryptocurrencies
    CRYPTO_CURRENCIES = [
        'BTC', 'ETH', 'USDT', 'USDC', 'BNB', 'SOL', 'MATIC', 'DOGE', 
        'SHIB', 'ADA', 'XRP', 'DOT', 'AVAX', 'LINK', 'UNI'
    ]
    
    def __init__(self, db_manager):
        """
        Initialize currency converter.
        
        Args:
            db_manager: Database manager instance
        """
        self.db_manager = db_manager
        self._rate_cache: Dict[str, Dict[str, Any]] = {}
        self._cache_timestamp: Optional[datetime] = None
        self._cache_ttl_seconds = 3600  # 1 hour cache
        logger.info("CurrencyConverter initialized")
    
    def convert(self, amount: Decimal, from_currency: str, 
                to_currency: str, use_historical: bool = False,
                historical_date: Optional[date] = None) -> Decimal:
        """
        Convert amount from one currency to another.
        
        Args:
            amount: Amount to convert
            from_currency: Source currency code
            to_currency: Target currency code
            use_historical: Use historical exchange rate
            historical_date: Date for historical rate
            
        Returns:
            Converted amount in target currency
        """
        try:
            # Same currency - no conversion needed
            if from_currency.upper() == to_currency.upper():
                return amount
            
            # Get exchange rate
            if use_historical and historical_date:
                rate = self._get_historical_rate(from_currency, to_currency, historical_date)
            else:
                rate = self._get_current_rate(from_currency, to_currency)
            
            if rate is None:
                logger.warning(f"No exchange rate found for {from_currency} -> {to_currency}")
                return amount
            
            # Convert amount
            converted = amount * Dec(str(rate))
            
            # Round to 2 decimal places for fiat, 8 for crypto
            if to_currency.upper() in self.CRYPTO_CURRENCIES:
                converted = converted.quantize(Dec('0.00000001'), rounding=ROUND_HALF_UP)
            else:
                converted = converted.quantize(Dec('0.01'), rounding=ROUND_HALF_UP)
            
            return converted
            
        except Exception as e:
            logger.error(f"Failed to convert {amount} {from_currency} to {to_currency}: {e}")
            return amount
    
    def _get_current_rate(self, from_currency: str, to_currency: str) -> Optional[float]:
        """
        Get current exchange rate between two currencies.
        
        Args:
            from_currency: Source currency code
            to_currency: Target currency code
            
        Returns:
            Exchange rate or None if not found
        """
        try:
            # Check cache first
            cache_key = f"{from_currency}_{to_currency}"
            if self._is_cache_valid() and cache_key in self._rate_cache:
                return self._rate_cache[cache_key]['rate']
            
            # Get rates from database
            from_rate = self._get_currency_rate(from_currency)
            to_rate = self._get_currency_rate(to_currency)
            
            if from_rate is None or to_rate is None:
                return None
            
            # Calculate cross rate through base currency
            # If base is USD: EUR/USD = 1.1, GBP/USD = 1.3
            # Then EUR/GBP = 1.1 / 1.3 = 0.846
            rate = from_rate / to_rate
            
            # Cache the rate
            if not self._is_cache_valid():
                self._rate_cache = {}
                self._cache_timestamp = datetime.utcnow()
            
            self._rate_cache[cache_key] = {
                'rate': rate,
                'timestamp': datetime.utcnow()
            }
            
            return rate
            
        except Exception as e:
            logger.error(f"Failed to get current rate for {from_currency} -> {to_currency}: {e}")
            return None
    
    def _get_historical_rate(self, from_currency: str, to_currency: str, 
                              target_date: date) -> Optional[float]:
        """
        Get historical exchange rate for a specific date.
        
        Args:
            from_currency: Source currency code
            to_currency: Target currency code
            target_date: Date to get rate for
            
        Returns:
            Historical exchange rate or None if not found
        """
        try:
            session = self.db_manager.get_session()
            try:
                from ..database.models import ExchangeRateHistoryModel, CurrencyModel
                
                # Get currency IDs
                from_currency_obj = session.query(CurrencyModel).filter_by(
                    code=from_currency.upper(),
                    is_active=True
                ).first()
                
                to_currency_obj = session.query(CurrencyModel).filter_by(
                    code=to_currency.upper(),
                    is_active=True
                ).first()
                
                if not from_currency_obj or not to_currency_obj:
                    return None
                
                # Get historical rates for the date (or closest date)
                from_rate = self._get_historical_rate_for_currency(
                    session, from_currency_obj.id, target_date
                )
                to_rate = self._get_historical_rate_for_currency(
                    session, to_currency_obj.id, target_date
                )
                
                if from_rate is None or to_rate is None:
                    # Fall back to current rate
                    return self._get_current_rate(from_currency, to_currency)
                
                return from_rate / to_rate
                
            finally:
                session.close()
                
        except Exception as e:
            logger.error(f"Failed to get historical rate for {from_currency} -> {to_currency} on {target_date}: {e}")
            # Fall back to current rate
            return self._get_current_rate(from_currency, to_currency)
    
    def _get_historical_rate_for_currency(self, session, currency_id: int, 
                                           target_date: date) -> Optional[float]:
        """
        Get historical rate for a currency on a specific date.
        
        Args:
            session: Database session
            currency_id: Currency ID
            target_date: Target date
            
        Returns:
            Exchange rate or None
        """
        try:
            from ..database.models import ExchangeRateHistoryModel
            
            # Get rate for the date (or closest previous date)
            rate_record = session.query(ExchangeRateHistoryModel).filter(
                ExchangeRateHistoryModel.currency_id == currency_id,
                ExchangeRateHistoryModel.recorded_at <= target_date
            ).order_by(ExchangeRateHistoryModel.recorded_at.desc()).first()
            
            if rate_record:
                return float(rate_record.exchange_rate)
            
            return None
            
        except Exception as e:
            logger.error(f"Failed to get historical rate for currency {currency_id} on {target_date}: {e}")
            return None
    
    def _get_currency_rate(self, currency_code: str) -> Optional[float]:
        """
        Get current exchange rate for a currency relative to base currency.
        
        Args:
            currency_code: Currency code
            
        Returns:
            Exchange rate or None if not found
        """
        try:
            # Base currency always has rate of 1
            if currency_code.upper() == self.BASE_CURRENCY:
                return 1.0
            
            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 currency:
                    return float(currency.exchange_rate)
                
                return None
                
            finally:
                session.close()
                
        except Exception as e:
            logger.error(f"Failed to get currency rate for {currency_code}: {e}")
            return None
    
    def _is_cache_valid(self) -> bool:
        """Check if the rate cache is still valid."""
        if self._cache_timestamp is None:
            return False
        
        age = (datetime.utcnow() - self._cache_timestamp).total_seconds()
        return age < self._cache_ttl_seconds
    
    def get_all_rates(self, base_currency: str = 'USD') -> Dict[str, float]:
        """
        Get all exchange rates relative to a base currency.
        
        Args:
            base_currency: Base currency for rates
            
        Returns:
            Dict mapping currency codes to exchange rates
        """
        try:
            session = self.db_manager.get_session()
            try:
                from ..database.models import CurrencyModel
                
                currencies = session.query(CurrencyModel).filter_by(
                    is_active=True
                ).all()
                
                rates = {}
                base_rate = 1.0
                
                # Find base currency rate
                for currency in currencies:
                    if currency.code == base_currency.upper():
                        base_rate = float(currency.exchange_rate)
                        break
                
                # Calculate rates relative to base
                for currency in currencies:
                    if currency.code == base_currency.upper():
                        rates[currency.code] = 1.0
                    else:
                        rates[currency.code] = float(currency.exchange_rate) / base_rate
                
                return rates
                
            finally:
                session.close()
                
        except Exception as e:
            logger.error(f"Failed to get all rates: {e}")
            return {}
    
    def format_amount(self, amount: Decimal, currency: str) -> str:
        """
        Format amount with currency symbol.
        
        Args:
            amount: Amount to format
            currency: Currency code
            
        Returns:
            Formatted amount string
        """
        try:
            session = self.db_manager.get_session()
            try:
                from ..database.models import CurrencyModel
                
                currency_obj = session.query(CurrencyModel).filter_by(
                    code=currency.upper()
                ).first()
                
                symbol = currency_obj.symbol if currency_obj else currency
                
                # Format based on currency type
                if currency.upper() in self.CRYPTO_CURRENCIES:
                    formatted = f"{symbol}{amount:.8f}"
                else:
                    formatted = f"{symbol}{amount:.2f}"
                
                return formatted
                
            finally:
                session.close()
                
        except Exception as e:
            logger.error(f"Failed to format amount: {e}")
            return f"{currency} {amount}"
    
    def get_currency_info(self, currency_code: str) -> Optional[Dict[str, Any]]:
        """
        Get detailed information about a currency.
        
        Args:
            currency_code: Currency code
            
        Returns:
            Dict with currency information or None
        """
        try:
            session = self.db_manager.get_session()
            try:
                from ..database.models import CurrencyModel
                
                currency = session.query(CurrencyModel).filter_by(
                    code=currency_code.upper()
                ).first()
                
                if currency:
                    return {
                        'code': currency.code,
                        'name': currency.name,
                        'symbol': currency.symbol,
                        'currency_type': currency.currency_type,
                        'network': currency.network,
                        'contract_address': currency.contract_address,
                        'decimals': currency.decimals,
                        'exchange_rate': float(currency.exchange_rate),
                        'base_currency': currency.base_currency,
                        'is_active': currency.is_active,
                        'last_rate_update': currency.last_rate_update.isoformat() if currency.last_rate_update else None
                    }
                
                return None
                
            finally:
                session.close()
                
        except Exception as e:
            logger.error(f"Failed to get currency info for {currency_code}: {e}")
            return None
    
    def get_supported_currencies(self, currency_type: Optional[str] = None) -> List[Dict[str, Any]]:
        """
        Get list of supported currencies.
        
        Args:
            currency_type: Filter by type ('fiat', 'crypto', or None for all)
            
        Returns:
            List of currency information dicts
        """
        try:
            session = self.db_manager.get_session()
            try:
                from ..database.models import CurrencyModel
                
                query = session.query(CurrencyModel).filter_by(is_active=True)
                
                if currency_type:
                    query = query.filter_by(currency_type=currency_type)
                
                currencies = query.all()
                
                return [
                    {
                        'code': c.code,
                        'name': c.name,
                        'symbol': c.symbol,
                        'currency_type': c.currency_type,
                        'network': c.network,
                        'exchange_rate': float(c.exchange_rate)
                    }
                    for c in currencies
                ]
                
            finally:
                session.close()
                
        except Exception as e:
            logger.error(f"Failed to get supported currencies: {e}")
            return []