from typing import Dict, Optional
import requests
from datetime import datetime, date, timedelta
import json

class CurrencyService:
    """Service for handling currency conversions and exchange rates"""

    def __init__(self):
        self.cache = {}
        self.cache_expiry = {}
        self.cache_duration_hours = 24  # Cache rates for 24 hours

        # Fallback exchange rates (for offline mode or API failures)
        self.fallback_rates = {
            'USD': 1.0,
            'EUR': 0.85,
            'GBP': 0.73,
            'JPY': 110.0,
            'CNY': 6.45,
            'INR': 74.0,
            'AED': 3.67,
            'CAD': 1.25,
            'AUD': 1.35,
            'CHF': 0.92,
            'SEK': 8.6,
            'NOK': 8.5,
            'DKK': 6.2,
            'SGD': 1.35,
            'HKD': 7.8,
            'NZD': 1.45,
            'ZAR': 15.0,
            'MXN': 20.0,
            'BRL': 5.2
        }

    def _is_cache_valid(self, currency_pair: str) -> bool:
        """Check if cached rate is still valid"""
        if currency_pair not in self.cache_expiry:
            return False

        expiry_time = self.cache_expiry[currency_pair]
        return datetime.now() < expiry_time

    def _cache_rate(self, currency_pair: str, rate: float):
        """Cache an exchange rate with expiry time"""
        self.cache[currency_pair] = rate
        expiry_time = datetime.now().replace(microsecond=0) + \
                     timedelta(hours=self.cache_duration_hours)
        self.cache_expiry[currency_pair] = expiry_time

    def get_exchange_rate(self, from_currency: str, to_currency: str, date: Optional[date] = None) -> float:
        """
        Get exchange rate from one currency to another

        Args:
            from_currency: Source currency code (e.g., 'USD')
            to_currency: Target currency code (e.g., 'EUR')
            date: Historical date for the rate (optional, defaults to current rate)

        Returns:
            Exchange rate (how many units of to_currency equal 1 unit of from_currency)
        """
        if from_currency == to_currency:
            return 1.0

        # Check cache first
        cache_key = f"{from_currency}_{to_currency}"
        reverse_cache_key = f"{to_currency}_{from_currency}"

        if self._is_cache_valid(cache_key):
            return self.cache[cache_key]

        if self._is_cache_valid(reverse_cache_key):
            # We have the reverse rate, calculate the forward rate
            reverse_rate = self.cache[reverse_cache_key]
            if reverse_rate > 0:
                forward_rate = 1.0 / reverse_rate
                self._cache_rate(cache_key, forward_rate)
                return forward_rate

        # Try to fetch from API
        try:
            rate = self._fetch_rate_from_api(from_currency, to_currency)
            if rate:
                self._cache_rate(cache_key, rate)
                return rate
        except Exception as e:
            print(f"Failed to fetch exchange rate from API: {e}")

        # Fallback to static rates
        # Fallback rates are stored as "how many units = 1 USD"
        # So to convert from_currency to to_currency:
        # 1 from_currency = (1/from_rate) USD
        # 1 USD = to_rate to_currency
        # Therefore: 1 from_currency = (to_rate / from_rate) to_currency
        from_rate = self.fallback_rates.get(from_currency, 1.0)
        to_rate = self.fallback_rates.get(to_currency, 1.0)

        if from_rate > 0:
            rate = to_rate / from_rate
            self._cache_rate(cache_key, rate)
            return rate

        return 1.0

    def _fetch_rate_from_api(self, from_currency: str, to_currency: str) -> Optional[float]:
        """
        Fetch exchange rate from external API
        Using a free API for demonstration - in production, consider using a paid service
        """
        try:
            # Using exchangerate-api.com free tier
            url = f"https://v6.exchangerate-api.com/v6/latest/{from_currency}"
            response = requests.get(url, timeout=10)
            response.raise_for_status()

            data = response.json()
            if 'conversion_rates' in data and to_currency in data['conversion_rates']:
                return data['conversion_rates'][to_currency]

        except requests.exceptions.RequestException as e:
            print(f"API request failed: {e}")
        except (KeyError, ValueError) as e:
            print(f"Invalid API response: {e}")

        return None

    def convert_amount(self, amount: float, from_currency: str, to_currency: str) -> float:
        """
        Convert amount from one currency to another

        Args:
            amount: Amount in source currency
            from_currency: Source currency code
            to_currency: Target currency code

        Returns:
            Converted amount in target currency
        """
        if amount <= 0:
            return 0.0

        rate = self.get_exchange_rate(from_currency, to_currency)
        return amount * rate

    def get_supported_currencies(self) -> Dict[str, str]:
        """Get dictionary of supported currencies with their names"""
        return {
            'USD': 'US Dollar',
            'EUR': 'Euro',
            'GBP': 'British Pound',
            'JPY': 'Japanese Yen',
            'CNY': 'Chinese Yuan',
            'INR': 'Indian Rupee',
            'AED': 'UAE Dirham',
            'CAD': 'Canadian Dollar',
            'AUD': 'Australian Dollar',
            'CHF': 'Swiss Franc',
            'SEK': 'Swedish Krona',
            'NOK': 'Norwegian Krone',
            'DKK': 'Danish Krone',
            'SGD': 'Singapore Dollar',
            'HKD': 'Hong Kong Dollar',
            'NZD': 'New Zealand Dollar',
            'ZAR': 'South African Rand',
            'MXN': 'Mexican Peso',
            'BRL': 'Brazilian Real'
        }

    def format_currency(self, amount: float, currency_code: str) -> str:
        """Format amount with appropriate currency symbol and formatting"""
        symbols = {
            'USD': '$',
            'EUR': '€',
            'GBP': '£',
            'JPY': '¥',
            'CNY': '¥',
            'INR': '₹',
            'AED': 'د.إ',
            'CAD': 'C$',
            'AUD': 'A$',
            'CHF': 'CHF',
            'SEK': 'SEK',
            'NOK': 'NOK',
            'DKK': 'DKK',
            'SGD': 'S$',
            'HKD': 'HK$',
            'NZD': 'NZ$',
            'ZAR': 'R',
            'MXN': 'MX$',
            'BRL': 'R$'
        }

        symbol = symbols.get(currency_code, currency_code)

        # Format based on currency conventions
        if currency_code in ['JPY', 'CNY']:
            # No decimal places for these currencies
            return f"{symbol}{int(round(amount))}"
        else:
            # Two decimal places for most currencies
            return f"{symbol}{amount:.2f}"

# Singleton instance
currency_service = CurrencyService()