package queue

import (
	"testing"
	"time"

	"github.com/jfraeys/fetch_ml/internal/queue"
)

func TestCommitDedup_IsDuplicate(t *testing.T) {
	d := queue.NewCommitDedup(5 * time.Minute)

	// First check should not be duplicate
	if d.IsDuplicate("job1", "abc123") {
		t.Error("Expected not duplicate on first check")
	}

	// Mark as queued
	d.MarkQueued("job1", "abc123")

	// Second check should be duplicate
	if !d.IsDuplicate("job1", "abc123") {
		t.Error("Expected duplicate after marking queued")
	}

	// Different job with same commit should not be duplicate
	if d.IsDuplicate("job2", "abc123") {
		t.Error("Different job with same commit should not be duplicate")
	}

	// Same job with different commit should not be duplicate
	if d.IsDuplicate("job1", "def456") {
		t.Error("Same job with different commit should not be duplicate")
	}
}

func TestCommitDedup_TTL(t *testing.T) {
	// Use very short TTL for testing
	d := queue.NewCommitDedup(100 * time.Millisecond)

	// Mark as queued
	d.MarkQueued("job1", "abc123")

	// Should be duplicate immediately
	if !d.IsDuplicate("job1", "abc123") {
		t.Error("Expected duplicate immediately after queueing")
	}

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

	// Should no longer be duplicate after TTL
	if d.IsDuplicate("job1", "abc123") {
		t.Error("Expected not duplicate after TTL expired")
	}
}

func TestCommitDedup_Cleanup(t *testing.T) {
	d := queue.NewCommitDedup(100 * time.Millisecond)

	// Add several entries
	d.MarkQueued("job1", "abc123")
	d.MarkQueued("job2", "def456")
	d.MarkQueued("job3", "ghi789")

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

	// Add one more after expiration
	d.MarkQueued("job4", "jkl012")

	// Before cleanup, size should be 4
	if d.Size() != 4 {
		t.Errorf("Expected size 4 before cleanup, got %d", d.Size())
	}

	// Run cleanup
	d.Cleanup()

	// After cleanup, size should be 1 (only job4 remains)
	if d.Size() != 1 {
		t.Errorf("Expected size 1 after cleanup, got %d", d.Size())
	}

	// job4 should still be tracked
	if !d.IsDuplicate("job4", "jkl012") {
		t.Error("job4 should still be tracked after cleanup")
	}

	// expired entries should be gone
	if d.IsDuplicate("job1", "abc123") {
		t.Error("expired job1 should not be tracked after cleanup")
	}
}

func TestCommitDedup_DefaultTTL(t *testing.T) {
	// Create with zero/negative TTL - should use default (1 hour)
	d := queue.NewCommitDedup(0)

	// Mark as queued
	d.MarkQueued("job1", "abc123")

	// Should be duplicate immediately
	if !d.IsDuplicate("job1", "abc123") {
		t.Error("Expected duplicate with default TTL")
	}

	// Size should be 1
	if d.Size() != 1 {
		t.Errorf("Expected size 1, got %d", d.Size())
	}
}

func TestCommitDedup_ConcurrentAccess(t *testing.T) {
	d := queue.NewCommitDedup(5 * time.Minute)

	// Concurrent writes
	for i := 0; i < 100; i++ {
		go func(n int) {
			d.MarkQueued("job", string(rune(n)))
		}(i)
	}

	// Concurrent reads
	for i := 0; i < 100; i++ {
		go func(n int) {
			d.IsDuplicate("job", string(rune(n)))
		}(i)
	}

	// Small delay to let goroutines complete
	time.Sleep(100 * time.Millisecond)

	// Should not panic and should have some entries
	if d.Size() == 0 {
		t.Error("Expected some entries after concurrent access")
	}
}

func TestCommitDedup_DifferentJobSameCommit(t *testing.T) {
	d := queue.NewCommitDedup(5 * time.Minute)

	// Two different jobs can have the same commit
	d.MarkQueued("train-model", "abc123")

	// Different job with same commit should be allowed
	if d.IsDuplicate("evaluate-model", "abc123") {
		t.Error("Different jobs should not share commit dedup")
	}

	// But same job with same commit should be duplicate
	if !d.IsDuplicate("train-model", "abc123") {
		t.Error("Same job+commit should be duplicate")
	}
}

func BenchmarkCommitDedup_IsDuplicate(b *testing.B) {
	d := queue.NewCommitDedup(5 * time.Minute)
	d.MarkQueued("job", "commit")

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		d.IsDuplicate("job", "commit")
	}
}

func BenchmarkCommitDedup_MarkQueued(b *testing.B) {
	d := queue.NewCommitDedup(5 * time.Minute)

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		d.MarkQueued("job", string(rune(i%256)))
	}
}
