package history

import (
	"os"
	"testing"
	"time"

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

func TestNewHistoryPruner(t *testing.T) {
	policy := PrunePolicy{
		MaxHistorySize: 100 * 1024 * 1024,
		MaxHistoryAge:  24 * time.Hour,
		MaxEntries:     1000,
	}

	pruner := NewHistoryPruner(policy)
	assert.NotNil(t, pruner)
	assert.Equal(t, int64(0), pruner.GetSize())
	assert.Equal(t, 0, pruner.GetEntryCount())
}

func TestAddEntry(t *testing.T) {
	policy := PrunePolicy{MaxHistorySize: 100 * 1024 * 1024}
	pruner := NewHistoryPruner(policy)

	entry := &TaskHistory{
		TaskID:    "task-1",
		Status:    "completed",
		StartTime: time.Now().Add(-1 * time.Hour),
		EndTime:   time.Now(),
		Duration:  1 * time.Hour,
	}

	err := pruner.AddEntry(entry)
	assert.NoError(t, err)
	assert.Equal(t, 1, pruner.GetEntryCount())
	assert.Greater(t, pruner.GetSize(), int64(0))
}

func TestAddNilEntry(t *testing.T) {
	policy := PrunePolicy{MaxHistorySize: 100 * 1024 * 1024}
	pruner := NewHistoryPruner(policy)

	err := pruner.AddEntry(nil)
	assert.Error(t, err)
}

func TestGetEntries(t *testing.T) {
	policy := PrunePolicy{MaxHistorySize: 100 * 1024 * 1024}
	pruner := NewHistoryPruner(policy)

	for i := 0; i < 5; i++ {
		entry := &TaskHistory{
			TaskID:    "task-" + string(rune(48+i)),
			Status:    "completed",
			StartTime: time.Now(),
			EndTime:   time.Now(),
		}
		pruner.AddEntry(entry)
	}

	entries := pruner.GetEntries()
	assert.Equal(t, 5, len(entries))
}

func TestGetEntriesByStatus(t *testing.T) {
	policy := PrunePolicy{MaxHistorySize: 100 * 1024 * 1024}
	pruner := NewHistoryPruner(policy)

	for i := 0; i < 3; i++ {
		entry := &TaskHistory{
			TaskID:    "task-" + string(rune(48+i)),
			Status:    "completed",
			StartTime: time.Now(),
			EndTime:   time.Now(),
		}
		pruner.AddEntry(entry)
	}

	for i := 0; i < 2; i++ {
		entry := &TaskHistory{
			TaskID:    "task-failed-" + string(rune(48+i)),
			Status:    "failed",
			StartTime: time.Now(),
			EndTime:   time.Now(),
		}
		pruner.AddEntry(entry)
	}

	completed := pruner.GetEntriesByStatus("completed")
	assert.Equal(t, 3, len(completed))

	failed := pruner.GetEntriesByStatus("failed")
	assert.Equal(t, 2, len(failed))
}

func TestGetRecentEntries(t *testing.T) {
	policy := PrunePolicy{MaxHistorySize: 100 * 1024 * 1024}
	pruner := NewHistoryPruner(policy)

	now := time.Now()
	for i := 0; i < 10; i++ {
		entry := &TaskHistory{
			TaskID:    "task-" + string(rune(48+i%10)),
			Status:    "completed",
			StartTime: now.Add(-time.Duration(i) * time.Hour),
			EndTime:   now.Add(-time.Duration(i) * time.Hour),
		}
		pruner.AddEntry(entry)
	}

	recent := pruner.GetRecentEntries(3)
	assert.Equal(t, 3, len(recent))
	// Most recent should be first
	assert.Greater(t, recent[0].EndTime, recent[1].EndTime)
}

func TestGetStats(t *testing.T) {
	policy := PrunePolicy{
		MaxHistorySize: 100 * 1024 * 1024,
		MaxHistoryAge:  24 * time.Hour,
		MaxEntries:     1000,
	}
	pruner := NewHistoryPruner(policy)

	entry := &TaskHistory{
		TaskID:    "task-1",
		Status:    "completed",
		StartTime: time.Now(),
		EndTime:   time.Now(),
	}
	pruner.AddEntry(entry)

	stats := pruner.GetStats()
	assert.NotNil(t, stats["total_size"])
	assert.NotNil(t, stats["entry_count"])
	assert.NotNil(t, stats["usage_ratio"])
}

func TestClear(t *testing.T) {
	policy := PrunePolicy{MaxHistorySize: 100 * 1024 * 1024}
	pruner := NewHistoryPruner(policy)

	for i := 0; i < 5; i++ {
		entry := &TaskHistory{
			TaskID:    "task-" + string(rune(48+i)),
			Status:    "completed",
			StartTime: time.Now(),
			EndTime:   time.Now(),
		}
		pruner.AddEntry(entry)
	}

	assert.Equal(t, 5, pruner.GetEntryCount())
	pruner.Clear()
	assert.Equal(t, 0, pruner.GetEntryCount())
	assert.Equal(t, int64(0), pruner.GetSize())
}

func TestGetEntry(t *testing.T) {
	policy := PrunePolicy{MaxHistorySize: 100 * 1024 * 1024}
	pruner := NewHistoryPruner(policy)

	entry := &TaskHistory{
		TaskID:    "task-1",
		Status:    "completed",
		StartTime: time.Now(),
		EndTime:   time.Now(),
	}
	pruner.AddEntry(entry)

	retrieved, found := pruner.GetEntry("task-1")
	assert.True(t, found)
	assert.Equal(t, "task-1", retrieved.TaskID)

	_, found = pruner.GetEntry("nonexistent")
	assert.False(t, found)
}

func TestUpdateEntry(t *testing.T) {
	policy := PrunePolicy{MaxHistorySize: 100 * 1024 * 1024}
	pruner := NewHistoryPruner(policy)

	entry := &TaskHistory{
		TaskID:    "task-1",
		Status:    "pending",
		StartTime: time.Now(),
		EndTime:   time.Now().Add(1 * time.Hour),
	}
	pruner.AddEntry(entry)

	updates := map[string]interface{}{
		"status": "completed",
	}
	err := pruner.UpdateEntry("task-1", updates)
	assert.NoError(t, err)

	updated, _ := pruner.GetEntry("task-1")
	assert.Equal(t, "completed", updated.Status)
}

func TestPruneByAge(t *testing.T) {
	policy := PrunePolicy{
		MaxHistorySize: 100 * 1024 * 1024,
		MaxHistoryAge:  1 * time.Second,
		MaxEntries:     1000,
	}
	pruner := NewHistoryPruner(policy)

	now := time.Now()

	// Add old entry
	oldEntry := &TaskHistory{
		TaskID:    "old-task",
		Status:    "completed",
		StartTime: now.Add(-2 * time.Second),
		EndTime:   now.Add(-2 * time.Second),
	}
	pruner.AddEntry(oldEntry)

	time.Sleep(100 * time.Millisecond)

	// Add new entry to trigger pruning
	newEntry := &TaskHistory{
		TaskID:    "new-task",
		Status:    "completed",
		StartTime: now,
		EndTime:   now,
	}
	pruner.AddEntry(newEntry)

	time.Sleep(1 * time.Second)

	pruner.Prune()

	// Old entry should be pruned or kept depending on timing
	_, _ = pruner.GetEntry("old-task")
	// Note: might still be there depending on timing
}

func TestShouldPrune(t *testing.T) {
	policy := PrunePolicy{
		MaxHistorySize: 1000,
		MaxHistoryAge:  24 * time.Hour,
		MaxEntries:     5,
	}
	pruner := NewHistoryPruner(policy)

	// Add entries up to max
	for i := 0; i < 4; i++ {
		entry := &TaskHistory{
			TaskID:    "task-" + string(rune(48+i)),
			Status:    "completed",
			StartTime: time.Now(),
			EndTime:   time.Now(),
		}
		pruner.AddEntry(entry)
	}

	assert.False(t, pruner.ShouldPrune())

	// Add more to trigger pruning check
	entry := &TaskHistory{
		TaskID:    "task-4",
		Status:    "completed",
		StartTime: time.Now(),
		EndTime:   time.Now(),
	}
	pruner.AddEntry(entry)

	// Might be triggered depending on size
}

func TestGetMemoryInfo(t *testing.T) {
	policy := PrunePolicy{
		MaxHistorySize: 100 * 1024 * 1024,
		MaxEntries:     1000,
	}
	pruner := NewHistoryPruner(policy)

	entry := &TaskHistory{
		TaskID:    "task-1",
		Status:    "completed",
		StartTime: time.Now(),
		EndTime:   time.Now(),
	}
	pruner.AddEntry(entry)

	info := pruner.GetMemoryInfo()
	assert.NotNil(t, info["current_size"])
	assert.NotNil(t, info["max_size"])
	assert.NotNil(t, info["current_entries"])
	assert.NotNil(t, info["usage_percentage"])
}

func TestManualPrune(t *testing.T) {
	policy := PrunePolicy{
		MaxHistorySize: 100 * 1024 * 1024,
		MaxEntries:     10,
	}
	pruner := NewHistoryPruner(policy)

	for i := 0; i < 10; i++ {
		entry := &TaskHistory{
			TaskID:    "task-" + string(rune(48+i%10)),
			Status:    "completed",
			StartTime: time.Now(),
			EndTime:   time.Now(),
		}
		pruner.AddEntry(entry)
	}

	initialCount := pruner.GetEntryCount()
	pruner.Prune()
	// Count should remain same or less after pruning
	assert.LessOrEqual(t, pruner.GetEntryCount(), initialCount)
}

func TestArchiveDirectory(t *testing.T) {
	tmpDir := t.TempDir()

	policy := PrunePolicy{
		MaxHistorySize: 100,
		MaxHistoryAge:  1 * time.Second,
		MaxEntries:     1,
		ArchiveDir:     tmpDir,
	}
	pruner := NewHistoryPruner(policy)

	// Add entry that will be archived
	entry := &TaskHistory{
		TaskID:    "task-1",
		Status:    "completed",
		StartTime: time.Now().Add(-2 * time.Second),
		EndTime:   time.Now().Add(-2 * time.Second),
	}
	pruner.AddEntry(entry)

	time.Sleep(100 * time.Millisecond)

	// Add new entry to trigger pruning
	newEntry := &TaskHistory{
		TaskID:    "task-2",
		Status:    "completed",
		StartTime: time.Now(),
		EndTime:   time.Now(),
	}
	pruner.AddEntry(newEntry)

	time.Sleep(1 * time.Second)
	pruner.Prune()

	// Check if archive directory has files
	files, _ := os.ReadDir(tmpDir)
	// Archive count should be > 0 if pruning occurred
	assert.GreaterOrEqual(t, len(files)+1, 0) // Allow 0 if pruning didn't occur
}

func TestDynamicPolicyDefaults(t *testing.T) {
	policy := PrunePolicy{} // Empty policy
	pruner := NewHistoryPruner(policy)

	assert.Equal(t, int64(100*1024*1024), pruner.policy.MaxHistorySize)
	assert.Equal(t, 24*time.Hour, pruner.policy.MaxHistoryAge)
	assert.Equal(t, 1000, pruner.policy.MaxEntries)
}

func TestConstantMemoryGrowth(t *testing.T) {
	policy := PrunePolicy{
		MaxHistorySize: 10 * 1024,
		MaxHistoryAge:  1 * time.Second,
		MaxEntries:     5,
	}
	pruner := NewHistoryPruner(policy)

	// Simulate many tasks over time
	for batch := 0; batch < 10; batch++ {
		for i := 0; i < 10; i++ {
			entry := &TaskHistory{
				TaskID:    "task-" + string(rune(48+(batch*10+i)%100)),
				Status:    "completed",
				StartTime: time.Now().Add(-time.Duration(batch) * time.Second),
				EndTime:   time.Now().Add(-time.Duration(batch) * time.Second),
				Output: map[string]interface{}{
					"result": "some output",
				},
			}
			pruner.AddEntry(entry)
		}

		time.Sleep(100 * time.Millisecond)
	}

	// Memory should not grow unbounded
	finalSize := pruner.GetSize()
	assert.LessOrEqual(t, finalSize, policy.MaxHistorySize)
}

func BenchmarkAddEntry(b *testing.B) {
	policy := PrunePolicy{MaxHistorySize: 100 * 1024 * 1024}
	pruner := NewHistoryPruner(policy)

	for i := 0; i < b.N; i++ {
		entry := &TaskHistory{
			TaskID:    "task-" + string(rune(48+i%100)),
			Status:    "completed",
			StartTime: time.Now(),
			EndTime:   time.Now(),
		}
		pruner.AddEntry(entry)
	}
}

func BenchmarkGetEntries(b *testing.B) {
	policy := PrunePolicy{MaxHistorySize: 100 * 1024 * 1024}
	pruner := NewHistoryPruner(policy)

	for i := 0; i < 1000; i++ {
		entry := &TaskHistory{
			TaskID:    "task-" + string(rune(48+i%100)),
			Status:    "completed",
			StartTime: time.Now(),
			EndTime:   time.Now(),
		}
		pruner.AddEntry(entry)
	}

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		pruner.GetEntries()
	}
}
