package cache

import (
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
)

func TestCacheKeyString(t *testing.T) {
	key := &CacheKey{
		ActivityType: "implementer",
		TaskID:       "T1.1",
		InputHash:    "abc123",
		ModelID:      "claude-opus",
	}

	keyStr := key.String()
	assert.Contains(t, keyStr, "implementer")
	assert.Contains(t, keyStr, "T1.1")
	assert.Contains(t, keyStr, "abc123")
	assert.Contains(t, keyStr, "claude-opus")
}

func TestComputeHash(t *testing.T) {
	data := map[string]interface{}{
		"task": "T1.1",
		"code": "package main",
	}

	hash1, err := ComputeHash(data)
	assert.NoError(t, err)
	assert.NotEmpty(t, hash1)

	hash2, err := ComputeHash(data)
	assert.NoError(t, err)
	assert.Equal(t, hash1, hash2)
}

func TestSetAndGet(t *testing.T) {
	tmpDir := t.TempDir()
	cache := NewResultCache(tmpDir, 100, 0)

	key := &CacheKey{
		ActivityType: "implementer",
		TaskID:       "T1.1",
		InputHash:    "abc123",
		ModelID:      "claude-opus",
	}

	result := map[string]interface{}{
		"output": "implementation code",
		"files": []string{"file1.go", "file2.go"},
	}

	err := cache.Set(key, result)
	assert.NoError(t, err)

	retrieved, found, err := cache.Get(key)
	assert.NoError(t, err)
	assert.True(t, found)
	assert.Equal(t, "implementation code", retrieved["output"])
}

func TestCacheMiss(t *testing.T) {
	tmpDir := t.TempDir()
	cache := NewResultCache(tmpDir, 100, 0)

	key := &CacheKey{
		ActivityType: "implementer",
		TaskID:       "T1.1",
		InputHash:    "abc123",
		ModelID:      "claude-opus",
	}

	retrieved, found, err := cache.Get(key)
	assert.NoError(t, err)
	assert.False(t, found)
	assert.Nil(t, retrieved)
}

func TestInvalidate(t *testing.T) {
	tmpDir := t.TempDir()
	cache := NewResultCache(tmpDir, 100, 0)

	key := &CacheKey{
		ActivityType: "implementer",
		TaskID:       "T1.1",
		InputHash:    "abc123",
		ModelID:      "claude-opus",
	}

	cache.Set(key, map[string]interface{}{"output": "code"})
	assert.Equal(t, 1, cache.GetSize())

	cache.Invalidate(key)
	assert.Equal(t, 0, cache.GetSize())

	_, found, _ := cache.Get(key)
	assert.False(t, found)
}

func TestClear(t *testing.T) {
	tmpDir := t.TempDir()
	cache := NewResultCache(tmpDir, 100, 0)

	for i := 0; i < 10; i++ {
		key := &CacheKey{
			ActivityType: "implementer",
			TaskID:       "T1.1",
			InputHash:    string(rune(48 + i)),
			ModelID:      "claude-opus",
		}
		cache.Set(key, map[string]interface{}{"output": "code"})
	}

	assert.Equal(t, 10, cache.GetSize())

	cache.Clear()
	assert.Equal(t, 0, cache.GetSize())
}

func TestGetStats(t *testing.T) {
	tmpDir := t.TempDir()
	cache := NewResultCache(tmpDir, 100, 0)

	key := &CacheKey{
		ActivityType: "implementer",
		TaskID:       "T1.1",
		InputHash:    "abc123",
		ModelID:      "claude-opus",
	}

	cache.Set(key, map[string]interface{}{"output": "code"})
	cache.Get(key) // Hit

	stats := cache.GetStats()
	assert.Equal(t, 1, stats["size"])
	assert.Equal(t, 100, stats["max_size"])
	assert.Equal(t, 1, stats["total_hits"])
}

func TestTTLExpiration(t *testing.T) {
	tmpDir := t.TempDir()
	cache := NewResultCache(tmpDir, 100, 100*time.Millisecond)

	key := &CacheKey{
		ActivityType: "implementer",
		TaskID:       "T1.1",
		InputHash:    "abc123",
		ModelID:      "claude-opus",
	}

	cache.Set(key, map[string]interface{}{"output": "code"})

	// Should find immediately
	_, found, _ := cache.Get(key)
	assert.True(t, found)

	// Wait for TTL to expire
	time.Sleep(150 * time.Millisecond)

	// Should not find after TTL
	_, found, _ = cache.Get(key)
	assert.False(t, found)
}

func TestMaxSizeEviction(t *testing.T) {
	tmpDir := t.TempDir()
	cache := NewResultCache(tmpDir, 3, 0)

	// Add 3 entries
	for i := 0; i < 3; i++ {
		key := &CacheKey{
			ActivityType: "implementer",
			TaskID:       "T1.1",
			InputHash:    string(rune(48 + i)),
			ModelID:      "claude-opus",
		}
		cache.Set(key, map[string]interface{}{"output": "code"})
	}

	assert.Equal(t, 3, cache.GetSize())

	// Add 4th entry (should evict oldest)
	key4 := &CacheKey{
		ActivityType: "implementer",
		TaskID:       "T1.1",
		InputHash:    "3",
		ModelID:      "claude-opus",
	}
	cache.Set(key4, map[string]interface{}{"output": "code"})

	// Size should still be 3
	assert.Equal(t, 3, cache.GetSize())
}

func TestInvalidateByActivity(t *testing.T) {
	tmpDir := t.TempDir()
	cache := NewResultCache(tmpDir, 100, 0)

	// Add implementer entries
	for i := 0; i < 2; i++ {
		key := &CacheKey{
			ActivityType: "implementer",
			TaskID:       "T1.1",
			InputHash:    string(rune(48 + i)),
			ModelID:      "claude-opus",
		}
		cache.Set(key, map[string]interface{}{"output": "code"})
	}

	// Add judge entries
	for i := 0; i < 2; i++ {
		key := &CacheKey{
			ActivityType: "judge",
			TaskID:       "T1.1",
			InputHash:    string(rune(48 + i)),
			ModelID:      "claude-opus",
		}
		cache.Set(key, map[string]interface{}{"output": "verdict"})
	}

	assert.Equal(t, 4, cache.GetSize())

	// Invalidate implementer entries
	cache.InvalidateByActivity("implementer")

	assert.Equal(t, 2, cache.GetSize())
}

func TestInvalidateByTask(t *testing.T) {
	tmpDir := t.TempDir()
	cache := NewResultCache(tmpDir, 100, 0)

	// Add entries for T1.1
	for i := 0; i < 2; i++ {
		key := &CacheKey{
			ActivityType: "implementer",
			TaskID:       "T1.1",
			InputHash:    string(rune(48 + i)),
			ModelID:      "claude-opus",
		}
		cache.Set(key, map[string]interface{}{"output": "code"})
	}

	// Add entries for T1.2
	for i := 0; i < 2; i++ {
		key := &CacheKey{
			ActivityType: "implementer",
			TaskID:       "T1.2",
			InputHash:    string(rune(48 + i)),
			ModelID:      "claude-opus",
		}
		cache.Set(key, map[string]interface{}{"output": "code"})
	}

	assert.Equal(t, 4, cache.GetSize())

	// Invalidate T1.1 entries
	cache.InvalidateByTask("T1.1")

	assert.Equal(t, 2, cache.GetSize())
}

func TestGetHitRate(t *testing.T) {
	tmpDir := t.TempDir()
	cache := NewResultCache(tmpDir, 100, 0)

	key1 := &CacheKey{
		ActivityType: "implementer",
		TaskID:       "T1.1",
		InputHash:    "1",
		ModelID:      "claude-opus",
	}

	key2 := &CacheKey{
		ActivityType: "implementer",
		TaskID:       "T1.1",
		InputHash:    "2",
		ModelID:      "claude-opus",
	}

	cache.Set(key1, map[string]interface{}{"output": "code"})
	cache.Set(key2, map[string]interface{}{"output": "code"})

	cache.Get(key1)
	cache.Get(key1)
	cache.Get(key2)

	hitRate, count := cache.GetHitRate()
	assert.Equal(t, 2, count)
	assert.GreaterOrEqual(t, hitRate, 1.0)
}

func TestPersistence(t *testing.T) {
	tmpDir := t.TempDir()
	cache1 := NewResultCache(tmpDir, 100, 0)

	key := &CacheKey{
		ActivityType: "implementer",
		TaskID:       "T1.1",
		InputHash:    "abc123",
		ModelID:      "claude-opus",
	}

	cache1.Set(key, map[string]interface{}{"output": "code"})

	// Create new cache and load
	cache2 := NewResultCache(tmpDir, 100, 0)
	cache2.Load()

	retrieved, found, _ := cache2.Get(key)
	assert.True(t, found)
	assert.Equal(t, "code", retrieved["output"])
}
