package engine

import (
	"context"
	"fmt"
	"sync"
	"testing"
	"time"
)

func TestGenerateValidHash(t *testing.T) {
	generator, err := NewDefaultGenerator()
	if err != nil {
		t.Fatalf("NewDefaultGenerator failed: %v", err)
	}
	hash := "abcdef123456789"
	size := 64.0

	icon, err := generator.Generate(context.Background(), hash, size)

	if err != nil {
		t.Fatalf("Generate failed with error: %v", err)
	}

	if icon == nil {
		t.Fatal("Generate returned nil icon")
	}

	if icon.Hash != hash {
		t.Errorf("Expected hash %s, got %s", hash, icon.Hash)
	}

	if icon.Size != size {
		t.Errorf("Expected size %f, got %f", size, icon.Size)
	}

	if len(icon.Shapes) == 0 {
		t.Error("Generated icon has no shapes")
	}
}

func TestGenerateInvalidInputs(t *testing.T) {
	generator, err := NewDefaultGenerator()
	if err != nil {
		t.Fatalf("NewDefaultGenerator failed: %v", err)
	}

	tests := []struct {
		name string
		hash string
		size float64
	}{
		{
			name: "Empty hash",
			hash: "",
			size: 64.0,
		},
		{
			name: "Zero size",
			hash: "abcdef1234567890",
			size: 0.0,
		},
		{
			name: "Negative size",
			hash: "abcdef1234567890",
			size: -10.0,
		},
		{
			name: "Invalid hash format",
			hash: "invalid_hash_format",
			size: 64.0,
		},
		{
			name: "Hash too short",
			hash: "abc",
			size: 64.0,
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			_, err := generator.Generate(context.Background(), test.hash, test.size)
			if err == nil {
				t.Errorf("Expected error for %s, but got none", test.name)
			}
		})
	}
}

func TestGenerateWithoutCache(t *testing.T) {
	generator, err := NewDefaultGenerator()
	if err != nil {
		t.Fatalf("NewDefaultGenerator failed: %v", err)
	}

	hash := "abcdef1234567890abcdef1234567890abcdef12"
	size := 64.0

	// Generate without cache
	icon1, err := generator.GenerateWithoutCache(context.Background(), hash, size)
	if err != nil {
		t.Fatalf("GenerateWithoutCache failed: %v", err)
	}

	// Generate again without cache - should be different instances
	icon2, err := generator.GenerateWithoutCache(context.Background(), hash, size)
	if err != nil {
		t.Fatalf("Second GenerateWithoutCache failed: %v", err)
	}

	// Should be different instances
	if icon1 == icon2 {
		t.Error("GenerateWithoutCache returned same instance - should be different")
	}

	// But should have same content
	if icon1.Hash != icon2.Hash {
		t.Error("Icons have different hashes")
	}

	if icon1.Size != icon2.Size {
		t.Error("Icons have different sizes")
	}

	// Cache should remain empty
	if generator.GetCacheSize() != 0 {
		t.Errorf("Expected cache size 0 after GenerateWithoutCache, got %d", generator.GetCacheSize())
	}
}

func TestGenerateWithCancellation(t *testing.T) {
	generator, err := NewDefaultGenerator()
	if err != nil {
		t.Fatalf("NewDefaultGenerator failed: %v", err)
	}

	hash := "abcdef1234567890abcdef1234567890abcdef12"
	size := 64.0

	// Create canceled context
	ctx, cancel := context.WithCancel(context.Background())
	cancel() // Cancel immediately

	_, err = generator.Generate(ctx, hash, size)
	if err == nil {
		t.Error("Expected error for canceled context, but got none")
	}

	if err != context.Canceled {
		t.Errorf("Expected context.Canceled error, got %v", err)
	}
}

func TestGenerateWithTimeout(t *testing.T) {
	generator, err := NewDefaultGenerator()
	if err != nil {
		t.Fatalf("NewDefaultGenerator failed: %v", err)
	}

	hash := "abcdef1234567890abcdef1234567890abcdef12"
	size := 64.0

	// Create context with very short timeout
	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Nanosecond)
	defer cancel()

	// Sleep to ensure timeout
	time.Sleep(1 * time.Millisecond)

	_, err = generator.Generate(ctx, hash, size)
	if err == nil {
		t.Error("Expected timeout error, but got none")
	}

	if err != context.DeadlineExceeded {
		t.Errorf("Expected context.DeadlineExceeded error, got %v", err)
	}
}

func TestConcurrentGenerate(t *testing.T) {
	generator, err := NewDefaultGenerator()
	if err != nil {
		t.Fatalf("NewDefaultGenerator failed: %v", err)
	}

	hash := "abcdef1234567890abcdef1234567890abcdef12"
	size := 64.0

	const numGoroutines = 20
	icons := make([]*Icon, numGoroutines)
	errors := make([]error, numGoroutines)

	var wg sync.WaitGroup
	wg.Add(numGoroutines)

	// Start multiple goroutines that generate the same icon concurrently
	for i := 0; i < numGoroutines; i++ {
		go func(index int) {
			defer wg.Done()
			icon, genErr := generator.Generate(context.Background(), hash, size)
			icons[index] = icon
			errors[index] = genErr
		}(i)
	}

	wg.Wait()

	// Check that all generations succeeded
	for i, err := range errors {
		if err != nil {
			t.Errorf("Goroutine %d failed: %v", i, err)
		}
	}

	// All icons should be identical (same instance due to singleflight)
	firstIcon := icons[0]
	for i, icon := range icons[1:] {
		if icon != firstIcon {
			t.Errorf("Icon %d is different instance from first icon", i+1)
		}
	}

	// Cache should contain exactly one item
	if generator.GetCacheSize() != 1 {
		t.Errorf("Expected cache size 1, got %d", generator.GetCacheSize())
	}

	// Should have exactly one cache miss (the actual generation)
	// Note: With singleflight, concurrent requests share the result directly from singleflight,
	// not from the cache. Cache hits only occur for requests that arrive AFTER the initial
	// generation completes. So we only verify the miss count is 1.
	_, misses := generator.GetCacheMetrics()
	if misses != 1 {
		t.Errorf("Expected exactly 1 cache miss due to singleflight, got %d", misses)
	}

	// Verify subsequent requests DO get cache hits
	_, err = generator.Generate(context.Background(), hash, size)
	if err != nil {
		t.Fatalf("Subsequent Generate failed: %v", err)
	}
	hits, _ := generator.GetCacheMetrics()
	if hits == 0 {
		t.Error("Expected cache hit for subsequent request, got none")
	}
}

func TestConcurrentGenerateDifferentHashes(t *testing.T) {
	generator, err := NewDefaultGenerator()
	if err != nil {
		t.Fatalf("NewDefaultGenerator failed: %v", err)
	}

	const numGoroutines = 10
	size := 64.0

	var wg sync.WaitGroup
	wg.Add(numGoroutines)

	icons := make([]*Icon, numGoroutines)
	errors := make([]error, numGoroutines)

	// Start multiple goroutines that generate different icons concurrently
	for i := 0; i < numGoroutines; i++ {
		go func(index int) {
			defer wg.Done()
			hash := fmt.Sprintf("%032x", index)
			icon, err := generator.Generate(context.Background(), hash, size)
			icons[index] = icon
			errors[index] = err
		}(i)
	}

	wg.Wait()

	// Check that all generations succeeded
	for i, err := range errors {
		if err != nil {
			t.Errorf("Goroutine %d failed: %v", i, err)
		}
	}

	// All icons should be different instances
	for i := 0; i < numGoroutines; i++ {
		for j := i + 1; j < numGoroutines; j++ {
			if icons[i] == icons[j] {
				t.Errorf("Icons %d and %d are the same instance - should be different", i, j)
			}
		}
	}

	// Cache should contain all generated icons
	if generator.GetCacheSize() != numGoroutines {
		t.Errorf("Expected cache size %d, got %d", numGoroutines, generator.GetCacheSize())
	}

	// Should have exactly numGoroutines cache misses and no hits
	hits, misses := generator.GetCacheMetrics()
	if misses != int64(numGoroutines) {
		t.Errorf("Expected %d cache misses, got %d", numGoroutines, misses)
	}
	if hits != 0 {
		t.Errorf("Expected 0 cache hits, got %d", hits)
	}
}

func TestSingleflightDeduplication(t *testing.T) {
	generator, err := NewDefaultGenerator()
	if err != nil {
		t.Fatalf("NewDefaultGenerator failed: %v", err)
	}

	hash := "abcdef1234567890abcdef1234567890abcdef12"
	size := 64.0

	const numGoroutines = 50

	// Use a channel to coordinate goroutine starts
	start := make(chan struct{})
	icons := make([]*Icon, numGoroutines)
	errors := make([]error, numGoroutines)

	var wg sync.WaitGroup
	wg.Add(numGoroutines)

	// Start all goroutines and have them wait for the signal
	for i := 0; i < numGoroutines; i++ {
		go func(index int) {
			defer wg.Done()
			<-start // Wait for start signal
			icon, genErr := generator.Generate(context.Background(), hash, size)
			icons[index] = icon
			errors[index] = genErr
		}(i)
	}

	// Signal all goroutines to start at once
	close(start)
	wg.Wait()

	// Check that all generations succeeded
	for i, err := range errors {
		if err != nil {
			t.Errorf("Goroutine %d failed: %v", i, err)
		}
	}

	// All icons should be the exact same instance due to singleflight
	firstIcon := icons[0]
	for i, icon := range icons[1:] {
		if icon != firstIcon {
			t.Errorf("Icon %d is different instance - singleflight deduplication failed", i+1)
		}
	}

	// Should have exactly one cache miss due to singleflight deduplication
	// Note: Singleflight shares results directly with waiting goroutines, so they don't
	// hit the cache. Cache hits only occur for requests that arrive AFTER generation completes.
	_, misses := generator.GetCacheMetrics()
	if misses != 1 {
		t.Errorf("Expected exactly 1 cache miss due to singleflight deduplication, got %d", misses)
	}

	// Verify subsequent requests DO get cache hits
	_, err = generator.Generate(context.Background(), hash, size)
	if err != nil {
		t.Fatalf("Subsequent Generate failed: %v", err)
	}
	hits, _ := generator.GetCacheMetrics()
	if hits == 0 {
		t.Error("Expected cache hit for subsequent request, got none")
	}
}

func BenchmarkGenerate(b *testing.B) {
	generator, err := NewDefaultGenerator()
	if err != nil {
		b.Fatalf("NewDefaultGenerator failed: %v", err)
	}

	hash := "abcdef1234567890abcdef1234567890abcdef12"
	size := 64.0

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_, err := generator.Generate(context.Background(), hash, size)
		if err != nil {
			b.Fatalf("Generate failed: %v", err)
		}
	}
}

func BenchmarkGenerateWithCache(b *testing.B) {
	generator, err := NewDefaultGenerator()
	if err != nil {
		b.Fatalf("NewDefaultGenerator failed: %v", err)
	}

	hash := "abcdef1234567890abcdef1234567890abcdef12"
	size := 64.0

	// Pre-populate cache
	_, err = generator.Generate(context.Background(), hash, size)
	if err != nil {
		b.Fatalf("Pre-populate failed: %v", err)
	}

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_, err := generator.Generate(context.Background(), hash, size)
		if err != nil {
			b.Fatalf("Generate failed: %v", err)
		}
	}
}
