package util

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"sync"
	"time"
)

// ExchangeRateResponse represents the response from the exchange rate API
type ExchangeRateResponse struct {
	Success bool               `json:"success"`
	Base    string             `json:"base"`
	Date    string             `json:"date"`
	Rates   map[string]float64 `json:"rates"`
}

// HistoricalExchangeRateResponse represents historical rate response
type HistoricalExchangeRateResponse struct {
	Success        bool               `json:"success"`
	Historical     bool               `json:"historical"`
	Base           string             `json:"base"`
	Date           string             `json:"date"`
	Rates          map[string]float64 `json:"rates"`
	TimeLastUpdate int64              `json:"time_last_update_unix"`
}

// CachedRate stores exchange rate with timestamp
type CachedRate struct {
	Rate      float64
	Timestamp time.Time
	Date      string // The date this rate is for (YYYY-MM-DD format)
}

// Currency conversion cache
var (
	exchangeRateCache  = make(map[string]CachedRate)
	cacheMutex         sync.RWMutex
	cacheValidDuration = 24 * time.Hour // Cache historical rates for 24 hours
	currentRateCache   = 1 * time.Hour  // Cache current rates for 1 hour
)

// GetExchangeRate fetches the current exchange rate from fromCurrency to toCurrency
func GetExchangeRate(fromCurrency, toCurrency string) (float64, error) {
	return GetHistoricalExchangeRate(fromCurrency, toCurrency, time.Now())
}

// GetHistoricalExchangeRate fetches the exchange rate for a specific date
func GetHistoricalExchangeRate(fromCurrency, toCurrency string, date time.Time) (float64, error) {
	// If currencies are the same, return 1.0
	if fromCurrency == toCurrency {
		return 1.0, nil
	}

	dateStr := date.Format("2006-01-02")
	cacheKey := fmt.Sprintf("%s_%s_%s", fromCurrency, toCurrency, dateStr)

	// Check cache first
	cacheMutex.RLock()
	if cached, exists := exchangeRateCache[cacheKey]; exists {
		// Determine cache validity based on whether it's historical or current
		validDuration := cacheValidDuration
		if isToday(date) {
			validDuration = currentRateCache
		}

		if time.Since(cached.Timestamp) < validDuration {
			cacheMutex.RUnlock()
			return cached.Rate, nil
		}
	}
	cacheMutex.RUnlock()

	// Fetch from API if not in cache or cache is expired
	rate, err := fetchHistoricalExchangeRateFromAPI(fromCurrency, toCurrency, date)
	if err != nil {
		return 0, err
	}

	// Cache the result
	cacheMutex.Lock()
	exchangeRateCache[cacheKey] = CachedRate{
		Rate:      rate,
		Timestamp: time.Now(),
		Date:      dateStr,
	}
	cacheMutex.Unlock()

	return rate, nil
}

// fetchHistoricalExchangeRateFromAPI fetches exchange rate for a specific date
func fetchHistoricalExchangeRateFromAPI(fromCurrency, toCurrency string, date time.Time) (float64, error) {
	var url string
	dateStr := date.Format("2006-01-02")
	today := time.Now().Format("2006-01-02")

	// Use different endpoints for historical vs current rates
	if dateStr == today {
		// Current rates
		url = fmt.Sprintf("https://api.exchangerate-api.com/v4/latest/%s", fromCurrency)
	} else {
		// Historical rates - try exchangerate-api.com historical endpoint
		url = fmt.Sprintf("https://api.exchangerate-api.com/v4/historical/%s/%s", fromCurrency, dateStr)
	}

	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return 0, fmt.Errorf("error creating request: %v", err)
	}

	req.Header.Set("User-Agent", "Portfolio-Tracker/1.0")

	client := &http.Client{Timeout: 15 * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		return 0, fmt.Errorf("error fetching exchange rate: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		// If historical API fails, try fallback strategy
		if dateStr != today {
			return fetchHistoricalRateFallback(fromCurrency, toCurrency, date)
		}
		return 0, fmt.Errorf("API returned status %d", resp.StatusCode)
	}

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return 0, fmt.Errorf("error reading response: %v", err)
	}

	// Try parsing as standard response first
	var apiResp struct {
		Base  string             `json:"base"`
		Date  string             `json:"date"`
		Rates map[string]float64 `json:"rates"`
	}

	if err := json.Unmarshal(body, &apiResp); err != nil {
		return 0, fmt.Errorf("error parsing JSON: %v", err)
	}

	rate, exists := apiResp.Rates[toCurrency]
	if !exists {
		return 0, fmt.Errorf("currency %s not found in response", toCurrency)
	}

	return rate, nil
}

// fetchHistoricalRateFallback tries alternative methods for historical rates
func fetchHistoricalRateFallback(fromCurrency, toCurrency string, date time.Time) (float64, error) {
	// Try frankfurter.app as fallback for historical rates (free and reliable)
	dateStr := date.Format("2006-01-02")
	url := fmt.Sprintf("https://api.frankfurter.app/%s?from=%s&to=%s", dateStr, fromCurrency, toCurrency)

	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return 0, fmt.Errorf("error creating fallback request: %v", err)
	}

	req.Header.Set("User-Agent", "Portfolio-Tracker/1.0")

	client := &http.Client{Timeout: 15 * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		// If historical rate fails, use current rate as last resort
		return getCurrentRateAsFallback(fromCurrency, toCurrency)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return getCurrentRateAsFallback(fromCurrency, toCurrency)
	}

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return getCurrentRateAsFallback(fromCurrency, toCurrency)
	}

	var fallbackResp struct {
		Amount float64            `json:"amount"`
		Base   string             `json:"base"`
		Date   string             `json:"date"`
		Rates  map[string]float64 `json:"rates"`
	}

	if err := json.Unmarshal(body, &fallbackResp); err != nil {
		return getCurrentRateAsFallback(fromCurrency, toCurrency)
	}

	if rate, exists := fallbackResp.Rates[toCurrency]; exists {
		return rate, nil
	}

	return getCurrentRateAsFallback(fromCurrency, toCurrency)
}

// getCurrentRateAsFallback gets current rate when historical rate is unavailable
func getCurrentRateAsFallback(fromCurrency, toCurrency string) (float64, error) {
	url := fmt.Sprintf("https://api.exchangerate-api.com/v4/latest/%s", fromCurrency)

	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return 0, fmt.Errorf("error creating fallback request: %v", err)
	}

	req.Header.Set("User-Agent", "Portfolio-Tracker/1.0")

	client := &http.Client{Timeout: 10 * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		return 0, fmt.Errorf("error fetching fallback rate: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return 0, fmt.Errorf("fallback API returned status %d", resp.StatusCode)
	}

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return 0, fmt.Errorf("error reading fallback response: %v", err)
	}

	var apiResp struct {
		Base  string             `json:"base"`
		Date  string             `json:"date"`
		Rates map[string]float64 `json:"rates"`
	}

	if err := json.Unmarshal(body, &apiResp); err != nil {
		return 0, fmt.Errorf("error parsing fallback JSON: %v", err)
	}

	rate, exists := apiResp.Rates[toCurrency]
	if !exists {
		return 0, fmt.Errorf("currency %s not found in fallback response", toCurrency)
	}

	return rate, nil
}

// ConvertCurrency converts an amount from one currency to another using current rates
func ConvertCurrency(amount float64, fromCurrency, toCurrency string) (float64, error) {
	return ConvertCurrencyHistorical(amount, fromCurrency, toCurrency, time.Now())
}

// ConvertCurrencyHistorical converts an amount using historical exchange rate for a specific date
func ConvertCurrencyHistorical(amount float64, fromCurrency, toCurrency string, date time.Time) (float64, error) {
	if fromCurrency == toCurrency {
		return amount, nil
	}

	rate, err := GetHistoricalExchangeRate(fromCurrency, toCurrency, date)
	if err != nil {
		return 0, err
	}

	return amount * rate, nil
}

// FormatCurrencyAmount formats a currency amount with the currency symbol
func FormatCurrencyAmount(amount float64, currency string) string {
	return fmt.Sprintf("%.2f %s", amount, currency)
}

// GetCurrencySymbol returns the symbol for common currencies
func GetCurrencySymbol(currency string) string {
	symbols := map[string]string{
		"USD": "$",
		"EUR": "€",
		"GBP": "£",
		"JPY": "¥",
		"CHF": "CHF",
		"CAD": "C$",
		"AUD": "A$",
		"CNY": "¥",
		"INR": "₹",
		"KRW": "₩",
		"SEK": "kr",
		"NOK": "kr",
		"DKK": "kr",
		"PLN": "zł",
		"CZK": "Kč",
		"HUF": "Ft",
	}

	if symbol, exists := symbols[currency]; exists {
		return symbol
	}
	return currency // Return currency code if symbol not found
}

// FormatCurrencyWithSymbol formats amount with currency symbol
func FormatCurrencyWithSymbol(amount float64, currency string) string {
	symbol := GetCurrencySymbol(currency)
	if symbol == currency {
		// If no symbol found, put currency after amount
		return fmt.Sprintf("%.2f %s", amount, currency)
	}

	// For most currencies, put symbol before amount
	switch currency {
	case "EUR":
		return fmt.Sprintf("%.2f %s", amount, symbol)
	default:
		return fmt.Sprintf("%s%.2f", symbol, amount)
	}
}

// ConvertAndFormat converts currency and formats it nicely
func ConvertAndFormat(amount float64, fromCurrency, toCurrency string) string {
	return ConvertAndFormatHistorical(amount, fromCurrency, toCurrency, time.Now())
}

// ConvertAndFormatHistorical converts currency using historical rate and formats it
func ConvertAndFormatHistorical(amount float64, fromCurrency, toCurrency string, date time.Time) string {
	if fromCurrency == toCurrency {
		return FormatCurrencyWithSymbol(amount, toCurrency)
	}

	convertedAmount, err := ConvertCurrencyHistorical(amount, fromCurrency, toCurrency, date)
	if err != nil {
		// If conversion fails, return original amount with note
		return fmt.Sprintf("%.2f %s (conv. error)", amount, fromCurrency)
	}

	return FormatCurrencyWithSymbol(convertedAmount, toCurrency)
}

// BatchConvertCurrency converts multiple amounts in one API call for efficiency using current rates
func BatchConvertCurrency(amounts []float64, fromCurrency, toCurrency string) ([]float64, error) {
	return BatchConvertCurrencyHistorical(amounts, fromCurrency, toCurrency, time.Now())
}

// BatchConvertCurrencyHistorical converts multiple amounts using historical rate for a specific date
func BatchConvertCurrencyHistorical(amounts []float64, fromCurrency, toCurrency string, date time.Time) ([]float64, error) {
	if fromCurrency == toCurrency {
		return amounts, nil
	}

	rate, err := GetHistoricalExchangeRate(fromCurrency, toCurrency, date)
	if err != nil {
		return nil, err
	}

	converted := make([]float64, len(amounts))
	for i, amount := range amounts {
		converted[i] = amount * rate
	}

	return converted, nil
}

// ClearCache clears the exchange rate cache (useful for testing or manual refresh)
func ClearCache() {
	cacheMutex.Lock()
	defer cacheMutex.Unlock()
	exchangeRateCache = make(map[string]CachedRate)
}

// GetCacheInfo returns information about cached exchange rates
func GetCacheInfo() map[string]time.Time {
	cacheMutex.RLock()
	defer cacheMutex.RUnlock()

	info := make(map[string]time.Time)
	for key, cached := range exchangeRateCache {
		info[key] = cached.Timestamp
	}
	return info
}

// GetDetailedCacheInfo returns detailed information about cached exchange rates
func GetDetailedCacheInfo() map[string]CachedRate {
	cacheMutex.RLock()
	defer cacheMutex.RUnlock()

	info := make(map[string]CachedRate)
	for key, cached := range exchangeRateCache {
		info[key] = cached
	}
	return info
}

// CleanExpiredCache removes expired cache entries
func CleanExpiredCache() int {
	cacheMutex.Lock()
	defer cacheMutex.Unlock()

	removed := 0
	now := time.Now()

	for key, cached := range exchangeRateCache {
		// Determine if this is a current or historical rate
		validDuration := cacheValidDuration
		if isToday(parseDate(cached.Date)) {
			validDuration = currentRateCache
		}

		if now.Sub(cached.Timestamp) > validDuration {
			delete(exchangeRateCache, key)
			removed++
		}
	}

	return removed
}

// Helper functions

// isToday checks if a date is today
func isToday(date time.Time) bool {
	now := time.Now()
	return date.Year() == now.Year() && date.Month() == now.Month() && date.Day() == now.Day()
}

// parseDate parses a date string in YYYY-MM-DD format
func parseDate(dateStr string) time.Time {
	date, err := time.Parse("2006-01-02", dateStr)
	if err != nil {
		return time.Time{}
	}
	return date
}

// GetExchangeRateWithFallback gets exchange rate with multiple fallback strategies
func GetExchangeRateWithFallback(fromCurrency, toCurrency string, date time.Time) (float64, bool, error) {
	rate, err := GetHistoricalExchangeRate(fromCurrency, toCurrency, date)
	if err != nil {
		// Try with current date as fallback
		currentRate, currentErr := GetExchangeRate(fromCurrency, toCurrency)
		if currentErr != nil {
			return 0, false, fmt.Errorf("both historical (%v) and current (%v) rate fetching failed", err, currentErr)
		}
		return currentRate, false, nil // false indicates fallback was used
	}
	return rate, true, nil // true indicates historical rate was used
}
