package test

import (
	"context"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"go.uber.org/zap"

	"github.com/kms/api-key-service/internal/cache"
)


func TestMemoryCache_SetAndGet(t *testing.T) {
	config := NewMockConfig()
	logger := zap.NewNop()
	memCache := cache.NewMemoryCache(config, logger)
	defer memCache.Close()

	ctx := context.Background()
	key := "test-key"
	value := []byte("test-value")
	ttl := time.Hour

	// Set value
	err := memCache.Set(ctx, key, value, ttl)
	require.NoError(t, err)

	// Get value
	retrieved, err := memCache.Get(ctx, key)
	require.NoError(t, err)
	assert.Equal(t, value, retrieved)
}

func TestMemoryCache_GetNonExistent(t *testing.T) {
	config := NewMockConfig()
	logger := zap.NewNop()
	memCache := cache.NewMemoryCache(config, logger)
	defer memCache.Close()

	ctx := context.Background()
	key := "non-existent-key"

	// Try to get non-existent key
	_, err := memCache.Get(ctx, key)
	assert.Error(t, err)
}

func TestMemoryCache_Expiration(t *testing.T) {
	config := NewMockConfig()
	logger := zap.NewNop()
	memCache := cache.NewMemoryCache(config, logger)
	defer memCache.Close()

	ctx := context.Background()
	key := "expiring-key"
	value := []byte("expiring-value")
	ttl := 100 * time.Millisecond

	// Set value with short TTL
	err := memCache.Set(ctx, key, value, ttl)
	require.NoError(t, err)

	// Get value immediately (should work)
	retrieved, err := memCache.Get(ctx, key)
	require.NoError(t, err)
	assert.Equal(t, value, retrieved)

	// Wait for expiration
	time.Sleep(150 * time.Millisecond)

	// Try to get expired value
	_, err = memCache.Get(ctx, key)
	assert.Error(t, err)
}

func TestMemoryCache_Delete(t *testing.T) {
	config := NewMockConfig()
	logger := zap.NewNop()
	memCache := cache.NewMemoryCache(config, logger)
	defer memCache.Close()

	ctx := context.Background()
	key := "delete-key"
	value := []byte("delete-value")
	ttl := time.Hour

	// Set value
	err := memCache.Set(ctx, key, value, ttl)
	require.NoError(t, err)

	// Verify it exists
	exists, err := memCache.Exists(ctx, key)
	require.NoError(t, err)
	assert.True(t, exists)

	// Delete value
	err = memCache.Delete(ctx, key)
	require.NoError(t, err)

	// Verify it no longer exists
	exists, err = memCache.Exists(ctx, key)
	require.NoError(t, err)
	assert.False(t, exists)
}

func TestMemoryCache_Exists(t *testing.T) {
	config := NewMockConfig()
	logger := zap.NewNop()
	memCache := cache.NewMemoryCache(config, logger)
	defer memCache.Close()

	ctx := context.Background()
	key := "exists-key"
	value := []byte("exists-value")
	ttl := time.Hour

	// Check non-existent key
	exists, err := memCache.Exists(ctx, key)
	require.NoError(t, err)
	assert.False(t, exists)

	// Set value
	err = memCache.Set(ctx, key, value, ttl)
	require.NoError(t, err)

	// Check existing key
	exists, err = memCache.Exists(ctx, key)
	require.NoError(t, err)
	assert.True(t, exists)
}

func TestMemoryCache_Clear(t *testing.T) {
	config := NewMockConfig()
	logger := zap.NewNop()
	memCache := cache.NewMemoryCache(config, logger)
	defer memCache.Close()

	ctx := context.Background()
	ttl := time.Hour

	// Set multiple values
	err := memCache.Set(ctx, "key1", []byte("value1"), ttl)
	require.NoError(t, err)
	err = memCache.Set(ctx, "key2", []byte("value2"), ttl)
	require.NoError(t, err)

	// Verify they exist
	exists, err := memCache.Exists(ctx, "key1")
	require.NoError(t, err)
	assert.True(t, exists)
	exists, err = memCache.Exists(ctx, "key2")
	require.NoError(t, err)
	assert.True(t, exists)

	// Clear cache
	err = memCache.Clear(ctx)
	require.NoError(t, err)

	// Verify they no longer exist
	exists, err = memCache.Exists(ctx, "key1")
	require.NoError(t, err)
	assert.False(t, exists)
	exists, err = memCache.Exists(ctx, "key2")
	require.NoError(t, err)
	assert.False(t, exists)
}

func TestCacheManager_SetAndGetJSON(t *testing.T) {
	config := NewMockConfig()
	logger := zap.NewNop()
	cacheManager := cache.NewCacheManager(config, logger)
	defer cacheManager.Close()

	ctx := context.Background()
	key := "json-key"
	ttl := time.Hour

	// Test data
	originalData := map[string]interface{}{
		"name":  "test",
		"value": 42,
		"items": []string{"a", "b", "c"},
	}

	// Set JSON
	err := cacheManager.SetJSON(ctx, key, originalData, ttl)
	require.NoError(t, err)

	// Get JSON
	var retrievedData map[string]interface{}
	err = cacheManager.GetJSON(ctx, key, &retrievedData)
	require.NoError(t, err)

	// Compare data
	assert.Equal(t, originalData["name"], retrievedData["name"])
	assert.Equal(t, float64(42), retrievedData["value"]) // JSON numbers are float64
	
	// JSON arrays become []interface{}, so we need to compare differently
	retrievedItems := retrievedData["items"].([]interface{})
	expectedItems := []interface{}{"a", "b", "c"}
	assert.Equal(t, expectedItems, retrievedItems)
}

func TestCacheManager_GetJSONNonExistent(t *testing.T) {
	config := NewMockConfig()
	logger := zap.NewNop()
	cacheManager := cache.NewCacheManager(config, logger)
	defer cacheManager.Close()

	ctx := context.Background()
	key := "non-existent-json-key"

	var data map[string]interface{}
	err := cacheManager.GetJSON(ctx, key, &data)
	assert.Error(t, err)
}

func TestCacheManager_RawBytesOperations(t *testing.T) {
	config := NewMockConfig()
	logger := zap.NewNop()
	cacheManager := cache.NewCacheManager(config, logger)
	defer cacheManager.Close()

	ctx := context.Background()
	key := "raw-key"
	value := []byte("raw-value")
	ttl := time.Hour

	// Set raw bytes
	err := cacheManager.Set(ctx, key, value, ttl)
	require.NoError(t, err)

	// Get raw bytes
	retrieved, err := cacheManager.Get(ctx, key)
	require.NoError(t, err)
	assert.Equal(t, value, retrieved)

	// Check exists
	exists, err := cacheManager.Exists(ctx, key)
	require.NoError(t, err)
	assert.True(t, exists)

	// Delete
	err = cacheManager.Delete(ctx, key)
	require.NoError(t, err)

	// Verify deleted
	exists, err = cacheManager.Exists(ctx, key)
	require.NoError(t, err)
	assert.False(t, exists)
}

func TestCacheKey(t *testing.T) {
	prefix := "test"
	key := "key123"
	expected := "test:key123"

	result := cache.CacheKey(prefix, key)
	assert.Equal(t, expected, result)
}

func TestCacheKeyPrefixes(t *testing.T) {
	// Test that constants are defined
	assert.Equal(t, "perm", cache.KeyPrefixPermission)
	assert.Equal(t, "app", cache.KeyPrefixApplication)
	assert.Equal(t, "token", cache.KeyPrefixToken)
	assert.Equal(t, "user_claims", cache.KeyPrefixUserClaims)
	assert.Equal(t, "token_revoked", cache.KeyPrefixTokenRevoked)
}

func TestCacheManager_ConfigMethods(t *testing.T) {
	// Create mock config with cache settings
	config := NewMockConfig()
	config.values["CACHE_ENABLED"] = "true"
	config.values["CACHE_TTL"] = "1h"
	logger := zap.NewNop()
	cacheManager := cache.NewCacheManager(config, logger)
	defer cacheManager.Close()

	// Test IsEnabled
	assert.True(t, cacheManager.IsEnabled())

	// Test GetDefaultTTL
	ttl := cacheManager.GetDefaultTTL()
	assert.Equal(t, time.Hour, ttl)
}

func TestCacheManager_InvalidJSON(t *testing.T) {
	config := NewMockConfig()
	logger := zap.NewNop()
	cacheManager := cache.NewCacheManager(config, logger)
	defer cacheManager.Close()

	ctx := context.Background()
	key := "invalid-json-key"

	// Set invalid JSON data manually
	invalidJSON := []byte("{invalid json}")
	err := cacheManager.Set(ctx, key, invalidJSON, time.Hour)
	require.NoError(t, err)

	// Try to get as JSON (should fail)
	var data map[string]interface{}
	err = cacheManager.GetJSON(ctx, key, &data)
	assert.Error(t, err)
}

func TestCacheManager_SetJSONMarshalError(t *testing.T) {
	config := NewMockConfig()
	logger := zap.NewNop()
	cacheManager := cache.NewCacheManager(config, logger)
	defer cacheManager.Close()

	ctx := context.Background()
	key := "marshal-error-key"

	// Try to set data that can't be marshaled (function)
	invalidData := func() {}
	err := cacheManager.SetJSON(ctx, key, invalidData, time.Hour)
	assert.Error(t, err)
}

// Benchmark tests
func BenchmarkMemoryCache_Set(b *testing.B) {
	config := NewMockConfig()
	logger := zap.NewNop()
	memCache := cache.NewMemoryCache(config, logger)
	defer memCache.Close()

	ctx := context.Background()
	value := []byte("benchmark-value")
	ttl := time.Hour

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		key := "benchmark-key-" + string(rune(i))
		memCache.Set(ctx, key, value, ttl)
	}
}

func BenchmarkMemoryCache_Get(b *testing.B) {
	config := NewMockConfig()
	logger := zap.NewNop()
	memCache := cache.NewMemoryCache(config, logger)
	defer memCache.Close()

	ctx := context.Background()
	key := "benchmark-get-key"
	value := []byte("benchmark-value")
	ttl := time.Hour

	// Pre-populate cache
	memCache.Set(ctx, key, value, ttl)

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		memCache.Get(ctx, key)
	}
}

func BenchmarkCacheManager_SetJSON(b *testing.B) {
	config := NewMockConfig()
	logger := zap.NewNop()
	cacheManager := cache.NewCacheManager(config, logger)
	defer cacheManager.Close()

	ctx := context.Background()
	data := map[string]interface{}{
		"name":  "benchmark",
		"value": 42,
		"items": []string{"a", "b", "c"},
	}
	ttl := time.Hour

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		key := "benchmark-json-key-" + string(rune(i))
		cacheManager.SetJSON(ctx, key, data, ttl)
	}
}

func BenchmarkCacheManager_GetJSON(b *testing.B) {
	config := NewMockConfig()
	logger := zap.NewNop()
	cacheManager := cache.NewCacheManager(config, logger)
	defer cacheManager.Close()

	ctx := context.Background()
	key := "benchmark-json-get-key"
	data := map[string]interface{}{
		"name":  "benchmark",
		"value": 42,
		"items": []string{"a", "b", "c"},
	}
	ttl := time.Hour

	// Pre-populate cache
	cacheManager.SetJSON(ctx, key, data, ttl)

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		var retrieved map[string]interface{}
		cacheManager.GetJSON(ctx, key, &retrieved)
	}
}
