package engine

import (
	"testing"

	"github.com/kevin/go-jdenticon/internal/util"
)

func TestNewGenerator(t *testing.T) {
	config := DefaultColorConfig()
	generator := NewGenerator(config)
	
	if generator == nil {
		t.Fatal("NewGenerator returned nil")
	}
	
	if generator.config.IconPadding != config.IconPadding {
		t.Errorf("Expected icon padding %f, got %f", config.IconPadding, generator.config.IconPadding)
	}
	
	if generator.cache == nil {
		t.Error("Generator cache was not initialized")
	}
}

func TestNewDefaultGenerator(t *testing.T) {
	generator := NewDefaultGenerator()
	
	if generator == nil {
		t.Fatal("NewDefaultGenerator returned nil")
	}
	
	expectedConfig := DefaultColorConfig()
	if generator.config.IconPadding != expectedConfig.IconPadding {
		t.Errorf("Expected icon padding %f, got %f", expectedConfig.IconPadding, generator.config.IconPadding)
	}
}

func TestGenerateValidHash(t *testing.T) {
	generator := NewDefaultGenerator()
	hash := "abcdef123456789"
	size := 64.0
	
	icon, err := generator.Generate(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 := NewDefaultGenerator()
	
	tests := []struct {
		name     string
		hash     string
		size     float64
		wantErr  bool
	}{
		{
			name:    "empty hash",
			hash:    "",
			size:    64.0,
			wantErr: true,
		},
		{
			name:    "zero size",
			hash:    "abcdef123456789",
			size:    0.0,
			wantErr: true,
		},
		{
			name:    "negative size",
			hash:    "abcdef123456789",
			size:    -10.0,
			wantErr: true,
		},
		{
			name:    "short hash",
			hash:    "abc",
			size:    64.0,
			wantErr: true,
		},
		{
			name:    "invalid hex characters",
			hash:    "xyz123456789abc",
			size:    64.0,
			wantErr: true,
		},
	}
	
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			_, err := generator.Generate(tt.hash, tt.size)
			if (err != nil) != tt.wantErr {
				t.Errorf("Generate() error = %v, wantErr %v", err, tt.wantErr)
			}
		})
	}
}

func TestGenerateCaching(t *testing.T) {
	generator := NewDefaultGenerator()
	hash := "abcdef123456789"
	size := 64.0
	
	// Generate icon first time
	icon1, err := generator.Generate(hash, size)
	if err != nil {
		t.Fatalf("First generate failed: %v", err)
	}
	
	// Check cache size
	if generator.GetCacheSize() != 1 {
		t.Errorf("Expected cache size 1, got %d", generator.GetCacheSize())
	}
	
	// Generate same icon again
	icon2, err := generator.Generate(hash, size)
	if err != nil {
		t.Fatalf("Second generate failed: %v", err)
	}
	
	// Should be the same instance from cache
	if icon1 != icon2 {
		t.Error("Second generate did not return cached instance")
	}
	
	// Cache size should still be 1
	if generator.GetCacheSize() != 1 {
		t.Errorf("Expected cache size 1 after second generate, got %d", generator.GetCacheSize())
	}
}

func TestClearCache(t *testing.T) {
	generator := NewDefaultGenerator()
	hash := "abcdef123456789"
	size := 64.0
	
	// Generate an icon to populate cache
	_, err := generator.Generate(hash, size)
	if err != nil {
		t.Fatalf("Generate failed: %v", err)
	}
	
	// Verify cache has content
	if generator.GetCacheSize() == 0 {
		t.Error("Cache should not be empty after generate")
	}
	
	// Clear cache
	generator.ClearCache()
	
	// Verify cache is empty
	if generator.GetCacheSize() != 0 {
		t.Errorf("Expected cache size 0 after clear, got %d", generator.GetCacheSize())
	}
}

func TestSetConfig(t *testing.T) {
	generator := NewDefaultGenerator()
	hash := "abcdef123456789"
	size := 64.0
	
	// Generate an icon to populate cache
	_, err := generator.Generate(hash, size)
	if err != nil {
		t.Fatalf("Generate failed: %v", err)
	}
	
	// Verify cache has content
	if generator.GetCacheSize() == 0 {
		t.Error("Cache should not be empty after generate")
	}
	
	// Set new config
	newConfig := DefaultColorConfig()
	newConfig.IconPadding = 0.1
	generator.SetConfig(newConfig)
	
	// Verify config was updated
	if generator.GetConfig().IconPadding != 0.1 {
		t.Errorf("Expected icon padding 0.1, got %f", generator.GetConfig().IconPadding)
	}
	
	// Verify cache was cleared
	if generator.GetCacheSize() != 0 {
		t.Errorf("Expected cache size 0 after config change, got %d", generator.GetCacheSize())
	}
}

func TestExtractHue(t *testing.T) {
	generator := NewDefaultGenerator()
	
	tests := []struct {
		name     string
		hash     string
		expected float64
		tolerance float64
	}{
		{
			name:     "all zeros",
			hash:     "0000000000000000000",
			expected: 0.0,
			tolerance: 0.0001,
		},
		{
			name:     "all fs",
			hash:     "ffffffffffffffffff",
			expected: 1.0,
			tolerance: 0.0001,
		},
		{
			name:     "half value",  
			hash:     "000000000007ffffff",
			expected: 0.5,
			tolerance: 0.001, // Allow small floating point variance
		},
	}
	
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result, err := generator.extractHue(tt.hash)
			if err != nil {
				t.Fatalf("extractHue failed: %v", err)
			}
			diff := result - tt.expected
			if diff < 0 {
				diff = -diff
			}
			if diff > tt.tolerance {
				t.Errorf("Expected hue %f, got %f (tolerance %f)", tt.expected, result, tt.tolerance)
			}
		})
	}
}

func TestSelectColors(t *testing.T) {
	generator := NewDefaultGenerator()
	hash := "123456789abcdef"
	
	// Create test color palette
	availableColors := []Color{
		NewColorRGB(50, 50, 50),   // 0: Dark gray
		NewColorRGB(100, 100, 200), // 1: Mid color
		NewColorRGB(200, 200, 200), // 2: Light gray
		NewColorRGB(150, 150, 255), // 3: Light color
		NewColorRGB(25, 25, 100),   // 4: Dark color
	}
	
	selectedIndexes, err := generator.selectColors(hash, availableColors)
	
	if err != nil {
		t.Fatalf("selectColors failed: %v", err)
	}
	
	if len(selectedIndexes) != 3 {
		t.Fatalf("Expected 3 selected colors, got %d", len(selectedIndexes))
	}
	
	for i, index := range selectedIndexes {
		if index < 0 || index >= len(availableColors) {
			t.Errorf("Color index %d at position %d is out of range [0, %d)", index, i, len(availableColors))
		}
	}
}

func TestSelectColorsEmptyPalette(t *testing.T) {
	generator := NewDefaultGenerator()
	hash := "123456789abcdef"
	
	_, err := generator.selectColors(hash, []Color{})
	
	if err == nil {
		t.Error("Expected error for empty color palette")
	}
}

func TestIsValidHash(t *testing.T) {
	
	tests := []struct {
		name  string
		hash  string
		valid bool
	}{
		{
			name:  "valid hash",
			hash:  "abcdef123456789",
			valid: true,
		},
		{
			name:  "too short",
			hash:  "abc",
			valid: false,
		},
		{
			name:  "invalid characters",
			hash:  "xyz123456789abc",
			valid: false,
		},
		{
			name:  "uppercase valid",
			hash:  "ABCDEF123456789",
			valid: true,
		},
		{
			name:  "mixed case valid",
			hash:  "AbCdEf123456789",
			valid: true,
		},
		{
			name:  "empty",
			hash:  "",
			valid: false,
		},
	}
	
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := util.IsValidHash(tt.hash)
			if result != tt.valid {
				t.Errorf("Expected isValidHash(%s) = %v, got %v", tt.hash, tt.valid, result)
			}
		})
	}
}

func TestParseHex(t *testing.T) {
	hash := "123456789abcdef"
	
	tests := []struct {
		name     string
		start    int
		octets   int
		expected int
		wantErr  bool
	}{
		{
			name:     "single character",
			start:    0,
			octets:   1,
			expected: 1,
			wantErr:  false,
		},
		{
			name:     "two characters",
			start:    1,
			octets:   2,
			expected: 0x23,
			wantErr:  false,
		},
		{
			name:     "negative index",
			start:    -1,
			octets:   1,
			expected: 0xf,
			wantErr:  false,
		},
		{
			name:     "out of bounds",
			start:    100,
			octets:   1,
			expected: 0,
			wantErr:  true,
		},
	}
	
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result, err := util.ParseHex(hash, tt.start, tt.octets)
			if tt.wantErr {
				if err == nil {
					t.Errorf("Expected an error, but got nil")
				}
				return // Test is done for error cases
			}
			if err != nil {
				t.Fatalf("parseHex failed unexpectedly: %v", err)
			}
			if result != tt.expected {
				t.Errorf("Expected %d, got %d", tt.expected, result)
			}
		})
	}
}

func TestShapeCollector(t *testing.T) {
	collector := &shapeCollector{}
	
	// Test AddPolygon
	points := []Point{{X: 0, Y: 0}, {X: 1, Y: 0}, {X: 1, Y: 1}, {X: 0, Y: 1}}
	collector.AddPolygon(points)
	
	if len(collector.shapes) != 1 {
		t.Fatalf("Expected 1 shape after AddPolygon, got %d", len(collector.shapes))
	}
	
	shape := collector.shapes[0]
	if shape.Type != "polygon" {
		t.Errorf("Expected shape type 'polygon', got '%s'", shape.Type)
	}
	
	if len(shape.Points) != len(points) {
		t.Errorf("Expected %d points, got %d", len(points), len(shape.Points))
	}
	
	// Test AddCircle
	center := Point{X: 5, Y: 5}
	radius := 2.5
	collector.AddCircle(center, radius, false)
	
	if len(collector.shapes) != 2 {
		t.Fatalf("Expected 2 shapes after AddCircle, got %d", len(collector.shapes))
	}
	
	circleShape := collector.shapes[1]
	if circleShape.Type != "circle" {
		t.Errorf("Expected shape type 'circle', got '%s'", circleShape.Type)
	}
	
	// Verify circle fields are set correctly
	if circleShape.CircleX != center.X {
		t.Errorf("Expected CircleX %f, got %f", center.X, circleShape.CircleX)
	}
	if circleShape.CircleY != center.Y {
		t.Errorf("Expected CircleY %f, got %f", center.Y, circleShape.CircleY)
	}
	if circleShape.CircleSize != radius {
		t.Errorf("Expected CircleSize %f, got %f", radius, circleShape.CircleSize)
	}
	if circleShape.Invert != false {
		t.Errorf("Expected Invert false, got %t", circleShape.Invert)
	}
}

func BenchmarkGenerate(b *testing.B) {
	generator := NewDefaultGenerator()
	hash := "abcdef123456789"
	size := 64.0
	
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_, err := generator.Generate(hash, size)
		if err != nil {
			b.Fatalf("Generate failed: %v", err)
		}
	}
}

func BenchmarkGenerateWithCache(b *testing.B) {
	generator := NewDefaultGenerator()
	hash := "abcdef123456789"
	size := 64.0
	
	// Pre-populate cache
	_, err := generator.Generate(hash, size)
	if err != nil {
		b.Fatalf("Initial generate failed: %v", err)
	}
	
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_, err := generator.Generate(hash, size)
		if err != nil {
			b.Fatalf("Generate failed: %v", err)
		}
	}
}

func TestConsistentGeneration(t *testing.T) {
	generator1 := NewDefaultGenerator()
	generator2 := NewDefaultGenerator()
	
	hash := "abcdef123456789"
	size := 64.0
	
	icon1, err := generator1.Generate(hash, size)
	if err != nil {
		t.Fatalf("Generator1 failed: %v", err)
	}
	
	icon2, err := generator2.Generate(hash, size)
	if err != nil {
		t.Fatalf("Generator2 failed: %v", err)
	}
	
	// Icons should have same number of shape groups
	if len(icon1.Shapes) != len(icon2.Shapes) {
		t.Errorf("Different number of shape groups: %d vs %d", len(icon1.Shapes), len(icon2.Shapes))
	}
	
	// Colors should be the same
	for i := range icon1.Shapes {
		if i >= len(icon2.Shapes) {
			break
		}
		if !icon1.Shapes[i].Color.Equals(icon2.Shapes[i].Color) {
			t.Errorf("Different colors at group %d", i)
		}
	}
}