package util

import (
	"testing"
	"time"
)

func TestGetExchangeRate_SameCurrency(t *testing.T) {
	rate, err := GetExchangeRate("USD", "USD")
	if err != nil {
		t.Errorf("Expected no error for same currency, got %v", err)
	}
	if rate != 1.0 {
		t.Errorf("Expected rate 1.0 for same currency, got %f", rate)
	}
}

func TestConvertCurrency_SameCurrency(t *testing.T) {
	amount := 100.0
	converted, err := ConvertCurrency(amount, "EUR", "EUR")
	if err != nil {
		t.Errorf("Expected no error for same currency conversion, got %v", err)
	}
	if converted != amount {
		t.Errorf("Expected %f for same currency conversion, got %f", amount, converted)
	}
}

func TestFormatCurrencyAmount(t *testing.T) {
	tests := []struct {
		amount   float64
		currency string
		expected string
	}{
		{100.5, "USD", "100.50 USD"},
		{1234.567, "EUR", "1234.57 EUR"},
		{0.0, "GBP", "0.00 GBP"},
	}

	for _, test := range tests {
		result := FormatCurrencyAmount(test.amount, test.currency)
		if result != test.expected {
			t.Errorf("FormatCurrencyAmount(%.2f, %s) = %s, expected %s",
				test.amount, test.currency, result, test.expected)
		}
	}
}

func TestGetCurrencySymbol(t *testing.T) {
	tests := []struct {
		currency string
		expected string
	}{
		{"USD", "$"},
		{"EUR", "€"},
		{"GBP", "£"},
		{"JPY", "¥"},
		{"CHF", "CHF"},
		{"UNKNOWN", "UNKNOWN"}, // Should return currency code if symbol not found
	}

	for _, test := range tests {
		result := GetCurrencySymbol(test.currency)
		if result != test.expected {
			t.Errorf("GetCurrencySymbol(%s) = %s, expected %s",
				test.currency, result, test.expected)
		}
	}
}

func TestFormatCurrencyWithSymbol(t *testing.T) {
	tests := []struct {
		amount   float64
		currency string
		expected string
	}{
		{100.5, "USD", "$100.50"},
		{1234.56, "EUR", "1234.56 €"},
		{999.99, "GBP", "£999.99"},
		{500.0, "UNKNOWN", "500.00 UNKNOWN"},
	}

	for _, test := range tests {
		result := FormatCurrencyWithSymbol(test.amount, test.currency)
		if result != test.expected {
			t.Errorf("FormatCurrencyWithSymbol(%.2f, %s) = %s, expected %s",
				test.amount, test.currency, result, test.expected)
		}
	}
}

func TestConvertAndFormat_SameCurrency(t *testing.T) {
	amount := 100.0
	currency := "USD"
	result := ConvertAndFormat(amount, currency, currency)
	expected := FormatCurrencyWithSymbol(amount, currency)

	if result != expected {
		t.Errorf("ConvertAndFormat(%f, %s, %s) = %s, expected %s",
			amount, currency, currency, result, expected)
	}
}

func TestBatchConvertCurrency_SameCurrency(t *testing.T) {
	amounts := []float64{100.0, 200.0, 300.0}
	currency := "EUR"

	converted, err := BatchConvertCurrency(amounts, currency, currency)
	if err != nil {
		t.Errorf("Expected no error for same currency batch conversion, got %v", err)
	}

	if len(converted) != len(amounts) {
		t.Errorf("Expected %d converted amounts, got %d", len(amounts), len(converted))
	}

	for i, amount := range amounts {
		if converted[i] != amount {
			t.Errorf("Expected converted[%d] = %f, got %f", i, amount, converted[i])
		}
	}
}

func TestClearCache(t *testing.T) {
	// Add something to cache first
	cacheMutex.Lock()
	exchangeRateCache["TEST_USD"] = CachedRate{
		Rate:      1.5,
		Timestamp: time.Now(),
	}
	cacheMutex.Unlock()

	// Verify cache has content
	info := GetCacheInfo()
	if len(info) == 0 {
		t.Error("Expected cache to have content before clearing")
	}

	// Clear cache
	ClearCache()

	// Verify cache is empty
	info = GetCacheInfo()
	if len(info) != 0 {
		t.Errorf("Expected empty cache after clearing, got %d items", len(info))
	}
}

func TestGetCacheInfo(t *testing.T) {
	// Clear cache first
	ClearCache()

	// Add test data to cache
	testTime := time.Now()
	cacheMutex.Lock()
	exchangeRateCache["EUR_USD_2024-01-01"] = CachedRate{
		Rate:      1.2,
		Timestamp: testTime,
		Date:      "2024-01-01",
	}
	exchangeRateCache["GBP_EUR_2024-01-01"] = CachedRate{
		Rate:      1.15,
		Timestamp: testTime,
		Date:      "2024-01-01",
	}
	cacheMutex.Unlock()

	info := GetCacheInfo()

	if len(info) != 2 {
		t.Errorf("Expected 2 cache entries, got %d", len(info))
	}

	if timestamp, exists := info["EUR_USD_2024-01-01"]; !exists {
		t.Error("Expected EUR_USD_2024-01-01 entry in cache info")
	} else if !timestamp.Equal(testTime) {
		t.Error("Expected timestamp to match test time")
	}

	if timestamp, exists := info["GBP_EUR_2024-01-01"]; !exists {
		t.Error("Expected GBP_EUR_2024-01-01 entry in cache info")
	} else if !timestamp.Equal(testTime) {
		t.Error("Expected timestamp to match test time")
	}
}

// Test cache expiration logic
func TestCacheExpiration(t *testing.T) {
	ClearCache()

	// Add expired entry to cache with a specific rate
	expiredTime := time.Now().Add(-25 * time.Hour) // 25 hours ago (older than 24h cache)
	testDate := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
	cacheKey := "USD_EUR_2024-01-01"
	expiredRate := 999.999 // Unrealistic rate to detect if cache is used

	cacheMutex.Lock()
	exchangeRateCache[cacheKey] = CachedRate{
		Rate:      expiredRate,
		Timestamp: expiredTime,
		Date:      "2024-01-01",
	}
	cacheMutex.Unlock()

	// Verify the expired entry exists
	cacheMutex.RLock()
	_, exists := exchangeRateCache[cacheKey]
	cacheMutex.RUnlock()

	if !exists {
		t.Error("Expected expired cache entry to exist before test")
		return
	}

	// This should not use the expired cache, it should try to fetch from API
	// Even if API fails, it should not return the expired cached rate
	rate, err := GetHistoricalExchangeRate("USD", "EUR", testDate)

	// Either:
	// 1. We get a reasonable rate from API (not the cached unrealistic rate)
	// 2. We get an error (which is fine, means cache wasn't used)
	if err == nil {
		// If we got a rate, it should not be the expired cached rate
		if rate == expiredRate {
			t.Errorf("Function returned expired cached rate %f, should have fetched from API", expiredRate)
		}
		// A reasonable EUR/USD rate should be between 0.5 and 2.0
		if rate < 0.5 || rate > 2.0 {
			t.Errorf("Got unreasonable exchange rate %f, might be using expired cache", rate)
		}
	}
	// If err != nil, that's fine - it means the API call was attempted and failed
	// The important thing is that the expired cache was not used
}

// Test historical exchange rate functionality
func TestGetHistoricalExchangeRate_SameCurrency(t *testing.T) {
	testDate := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
	rate, err := GetHistoricalExchangeRate("USD", "USD", testDate)
	if err != nil {
		t.Errorf("Expected no error for same currency, got %v", err)
	}
	if rate != 1.0 {
		t.Errorf("Expected rate 1.0 for same currency, got %f", rate)
	}
}

func TestConvertCurrencyHistorical_SameCurrency(t *testing.T) {
	amount := 100.0
	testDate := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
	converted, err := ConvertCurrencyHistorical(amount, "EUR", "EUR", testDate)
	if err != nil {
		t.Errorf("Expected no error for same currency conversion, got %v", err)
	}
	if converted != amount {
		t.Errorf("Expected %f for same currency conversion, got %f", amount, converted)
	}
}

func TestBatchConvertCurrencyHistorical_SameCurrency(t *testing.T) {
	amounts := []float64{100.0, 200.0, 300.0}
	currency := "EUR"
	testDate := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)

	converted, err := BatchConvertCurrencyHistorical(amounts, currency, currency, testDate)
	if err != nil {
		t.Errorf("Expected no error for same currency batch conversion, got %v", err)
	}

	if len(converted) != len(amounts) {
		t.Errorf("Expected %d converted amounts, got %d", len(amounts), len(converted))
	}

	for i, amount := range amounts {
		if converted[i] != amount {
			t.Errorf("Expected converted[%d] = %f, got %f", i, amount, converted[i])
		}
	}
}

func TestConvertAndFormatHistorical_SameCurrency(t *testing.T) {
	amount := 100.0
	currency := "USD"
	testDate := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
	result := ConvertAndFormatHistorical(amount, currency, currency, testDate)
	expected := FormatCurrencyWithSymbol(amount, currency)

	if result != expected {
		t.Errorf("ConvertAndFormatHistorical(%f, %s, %s, %v) = %s, expected %s",
			amount, currency, currency, testDate, result, expected)
	}
}

func TestGetExchangeRateWithFallback_SameCurrency(t *testing.T) {
	testDate := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
	rate, isHistorical, err := GetExchangeRateWithFallback("USD", "USD", testDate)
	if err != nil {
		t.Errorf("Expected no error for same currency, got %v", err)
	}
	if rate != 1.0 {
		t.Errorf("Expected rate 1.0 for same currency, got %f", rate)
	}
	if !isHistorical {
		t.Error("Expected isHistorical to be true for same currency")
	}
}

func TestCleanExpiredCache(t *testing.T) {
	// Clear cache first
	ClearCache()

	// Add some test data to cache
	now := time.Now()
	expiredTime := now.Add(-25 * time.Hour)  // Older than 24 hours
	recentTime := now.Add(-30 * time.Minute) // Recent

	cacheMutex.Lock()
	exchangeRateCache["OLD_RATE_2024-01-01"] = CachedRate{
		Rate:      1.0,
		Timestamp: expiredTime,
		Date:      "2024-01-01",
	}
	exchangeRateCache["NEW_RATE_"+now.Format("2006-01-02")] = CachedRate{
		Rate:      1.1,
		Timestamp: recentTime,
		Date:      now.Format("2006-01-02"),
	}
	cacheMutex.Unlock()

	// Clean expired cache
	removed := CleanExpiredCache()

	// Should have removed the old entry
	if removed != 1 {
		t.Errorf("Expected 1 removed entry, got %d", removed)
	}

	// Verify the recent entry is still there
	info := GetCacheInfo()
	if len(info) != 1 {
		t.Errorf("Expected 1 remaining cache entry, got %d", len(info))
	}
}

func TestGetDetailedCacheInfo(t *testing.T) {
	// Clear cache first
	ClearCache()

	// Add test data to cache
	testTime := time.Now()
	cacheMutex.Lock()
	exchangeRateCache["EUR_USD_2024-01-01"] = CachedRate{
		Rate:      1.2,
		Timestamp: testTime,
		Date:      "2024-01-01",
	}
	cacheMutex.Unlock()

	info := GetDetailedCacheInfo()

	if len(info) != 1 {
		t.Errorf("Expected 1 cache entry, got %d", len(info))
	}

	if cached, exists := info["EUR_USD_2024-01-01"]; !exists {
		t.Error("Expected EUR_USD_2024-01-01 entry in detailed cache info")
	} else {
		if cached.Rate != 1.2 {
			t.Errorf("Expected rate 1.2, got %f", cached.Rate)
		}
		if cached.Date != "2024-01-01" {
			t.Errorf("Expected date 2024-01-01, got %s", cached.Date)
		}
		if !cached.Timestamp.Equal(testTime) {
			t.Error("Expected timestamp to match test time")
		}
	}
}

func TestIsToday(t *testing.T) {
	now := time.Now()
	yesterday := now.AddDate(0, 0, -1)
	tomorrow := now.AddDate(0, 0, 1)

	if !isToday(now) {
		t.Error("Expected isToday(now) to be true")
	}

	if isToday(yesterday) {
		t.Error("Expected isToday(yesterday) to be false")
	}

	if isToday(tomorrow) {
		t.Error("Expected isToday(tomorrow) to be false")
	}
}

func TestParseDate(t *testing.T) {
	testDate := "2024-01-01"
	expected := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)

	result := parseDate(testDate)

	if !result.Equal(expected) {
		t.Errorf("Expected parsed date %v, got %v", expected, result)
	}

	// Test invalid date
	invalidResult := parseDate("invalid-date")
	if !invalidResult.IsZero() {
		t.Error("Expected zero time for invalid date")
	}
}

// Benchmark tests
func BenchmarkGetCurrencySymbol(b *testing.B) {
	currencies := []string{"USD", "EUR", "GBP", "JPY", "CHF", "UNKNOWN"}
	for i := 0; i < b.N; i++ {
		currency := currencies[i%len(currencies)]
		GetCurrencySymbol(currency)
	}
}

func BenchmarkFormatCurrencyWithSymbol(b *testing.B) {
	for i := 0; i < b.N; i++ {
		FormatCurrencyWithSymbol(1234.56, "USD")
	}
}
