package engine

import (
	"context"
	"fmt"
	"testing"

	"gitea.dockr.co/kev/go-jdenticon/internal/util"
)

func TestNewGenerator(t *testing.T) {
	config := DefaultColorConfig()
	generator, err := NewGenerator(config)
	if err != nil {
		t.Fatalf("NewGenerator failed: %v", err)
	}

	if generator == nil {
		t.Fatal("NewGenerator returned nil")
	}

	if generator.config.ColorConfig.IconPadding != config.IconPadding {
		t.Errorf("Expected icon padding %f, got %f", config.IconPadding, generator.config.ColorConfig.IconPadding)
	}

	if generator.cache == nil {
		t.Error("Generator cache was not initialized")
	}
}

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

	if generator == nil {
		t.Fatal("NewDefaultGenerator returned nil")
	}

	expectedConfig := DefaultColorConfig()
	if generator.config.ColorConfig.IconPadding != expectedConfig.IconPadding {
		t.Errorf("Expected icon padding %f, got %f", expectedConfig.IconPadding, generator.config.ColorConfig.IconPadding)
	}
}

func TestNewGeneratorWithConfig(t *testing.T) {
	config := GeneratorConfig{
		ColorConfig: DefaultColorConfig(),
		CacheSize:   500,
	}

	generator, err := NewGeneratorWithConfig(config)
	if err != nil {
		t.Fatalf("NewGeneratorWithConfig failed: %v", err)
	}

	if generator == nil {
		t.Fatal("NewGeneratorWithConfig returned nil")
	}

	if generator.config.CacheSize != 500 {
		t.Errorf("Expected cache size 500, got %d", generator.config.CacheSize)
	}
}

func TestDefaultGeneratorConfig(t *testing.T) {
	config := DefaultGeneratorConfig()

	if config.CacheSize != 1000 {
		t.Errorf("Expected default cache size 1000, got %d", config.CacheSize)
	}

	if config.MaxComplexity != 0 {
		t.Errorf("Expected default max complexity 0, got %d", config.MaxComplexity)
	}
}

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

	tests := []struct {
		name         string
		hash         string
		expectedHue  float64
		expectsError bool
	}{
		{
			name:         "Valid 40-character hash",
			hash:         "abcdef1234567890abcdef1234567890abcdef12",
			expectedHue:  float64(0xbcdef12) / float64(0xfffffff),
			expectsError: false,
		},
		{
			name:         "Valid hash with different values",
			hash:         "1234567890abcdef1234567890abcdef12345678",
			expectedHue:  float64(0x2345678) / float64(0xfffffff),
			expectsError: false,
		},
		{
			name:         "Hash too short",
			hash:         "abc",
			expectedHue:  0,
			expectsError: true,
		},
		{
			name:         "Invalid hex characters",
			hash:         "abcdef1234567890abcdef1234567890abcdefgh",
			expectedHue:  0,
			expectsError: true,
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			hue, err := generator.extractHue(test.hash)

			if test.expectsError {
				if err == nil {
					t.Errorf("Expected error for hash %s, but got none", test.hash)
				}
				return
			}

			if err != nil {
				t.Errorf("Unexpected error for hash %s: %v", test.hash, err)
				return
			}

			if fmt.Sprintf("%.6f", hue) != fmt.Sprintf("%.6f", test.expectedHue) {
				t.Errorf("Expected hue %.6f, got %.6f", test.expectedHue, hue)
			}
		})
	}
}

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

	availableColors := []Color{
		{H: 0.0, S: 1.0, L: 0.5, A: 255},  // Red
		{H: 0.33, S: 1.0, L: 0.5, A: 255}, // Green
		{H: 0.67, S: 1.0, L: 0.5, A: 255}, // Blue
		{H: 0.17, S: 1.0, L: 0.5, A: 255}, // Yellow
		{H: 0.0, S: 0.0, L: 0.5, A: 255},  // Gray
	}

	hash := "abcdef1234567890abcdef1234567890abcdef12"
	selectedIndexes, err := generator.selectColors(hash, availableColors)

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

	if len(selectedIndexes) != 3 {
		t.Errorf("Expected 3 selected color indexes, got %d", len(selectedIndexes))
	}

	for i, index := range selectedIndexes {
		if index < 0 || index >= len(availableColors) {
			t.Errorf("Selected index %d at position %d is out of range [0, %d)", index, i, len(availableColors))
		}
	}
}

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

	hash := "abcdef1234567890abcdef1234567890abcdef12"
	_, err = generator.selectColors(hash, []Color{})

	if err == nil {
		t.Error("Expected error for empty color palette, but got none")
	}
}

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

	hash := "abcdef1234567890abcdef1234567890abcdef12"
	size := 64.0

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

	icon2, err := generator.GenerateWithoutCache(context.Background(), hash, size)
	if err != nil {
		t.Fatalf("Second generation failed: %v", err)
	}

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

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

	if len(icon1.Shapes) != len(icon2.Shapes) {
		t.Errorf("Icons have different number of shape groups: %d vs %d", len(icon1.Shapes), len(icon2.Shapes))
	}

	for i, group1 := range icon1.Shapes {
		group2 := icon2.Shapes[i]
		if len(group1.Shapes) != len(group2.Shapes) {
			t.Errorf("Shape group %d has different number of shapes: %d vs %d", i, len(group1.Shapes), len(group2.Shapes))
		}
	}
}

func TestIsColorInForbiddenSet(t *testing.T) {
	tests := []struct {
		name      string
		index     int
		forbidden []int
		expected  bool
	}{
		{
			name:      "Index in forbidden set",
			index:     2,
			forbidden: []int{0, 2, 4},
			expected:  true,
		},
		{
			name:      "Index not in forbidden set",
			index:     1,
			forbidden: []int{0, 2, 4},
			expected:  false,
		},
		{
			name:      "Empty forbidden set",
			index:     1,
			forbidden: []int{},
			expected:  false,
		},
		{
			name:      "Single element forbidden set - match",
			index:     5,
			forbidden: []int{5},
			expected:  true,
		},
		{
			name:      "Single element forbidden set - no match",
			index:     3,
			forbidden: []int{5},
			expected:  false,
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			result := isColorInForbiddenSet(test.index, test.forbidden)
			if result != test.expected {
				t.Errorf("Expected %v, got %v", test.expected, result)
			}
		})
	}
}

func TestHasSelectedColorInForbiddenSet(t *testing.T) {
	tests := []struct {
		name      string
		selected  []int
		forbidden []int
		expected  bool
	}{
		{
			name:      "No overlap",
			selected:  []int{1, 3, 5},
			forbidden: []int{0, 2, 4},
			expected:  false,
		},
		{
			name:      "Partial overlap",
			selected:  []int{1, 2, 5},
			forbidden: []int{0, 2, 4},
			expected:  true,
		},
		{
			name:      "Complete overlap",
			selected:  []int{0, 2, 4},
			forbidden: []int{0, 2, 4},
			expected:  true,
		},
		{
			name:      "Empty selected",
			selected:  []int{},
			forbidden: []int{0, 2, 4},
			expected:  false,
		},
		{
			name:      "Empty forbidden",
			selected:  []int{1, 3, 5},
			forbidden: []int{},
			expected:  false,
		},
		{
			name:      "Both empty",
			selected:  []int{},
			forbidden: []int{},
			expected:  false,
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			result := hasSelectedColorInForbiddenSet(test.selected, test.forbidden)
			if result != test.expected {
				t.Errorf("Expected %v, got %v", test.expected, result)
			}
		})
	}
}

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

	tests := []struct {
		name      string
		index     int
		selected  []int
		forbidden []int
		expected  bool
	}{
		{
			name:      "Index not in forbidden set",
			index:     1,
			selected:  []int{0, 4},
			forbidden: []int{0, 4},
			expected:  false,
		},
		{
			name:      "Index in forbidden set, no selected colors in forbidden set",
			index:     0,
			selected:  []int{1, 3},
			forbidden: []int{0, 4},
			expected:  false,
		},
		{
			name:      "Index in forbidden set, has selected colors in forbidden set",
			index:     0,
			selected:  []int{1, 4},
			forbidden: []int{0, 4},
			expected:  true,
		},
		{
			name:      "Dark gray and dark main conflict",
			index:     colorDarkGray,
			selected:  []int{colorDarkMain},
			forbidden: []int{colorDarkGray, colorDarkMain},
			expected:  true,
		},
		{
			name:      "Light gray and light main conflict",
			index:     colorLightGray,
			selected:  []int{colorLightMain},
			forbidden: []int{colorLightGray, colorLightMain},
			expected:  true,
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			result := generator.isDuplicateColor(test.index, test.selected, test.forbidden)
			if result != test.expected {
				t.Errorf("Expected %v, got %v", test.expected, result)
			}
		})
	}
}

func TestShapeCollector(t *testing.T) {
	collector := &shapeCollector{}

	// Test initial state
	if len(collector.shapes) != 0 {
		t.Error("Expected empty shapes slice initially")
	}

	// Test AddPolygon
	points := []Point{{X: 0, Y: 0}, {X: 10, Y: 0}, {X: 5, Y: 10}}
	collector.AddPolygon(points)

	if len(collector.shapes) != 1 {
		t.Errorf("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) != 3 {
		t.Errorf("Expected 3 points, got %d", len(shape.Points))
	}

	// Test AddCircle
	collector.AddCircle(Point{X: 5, Y: 5}, 20, false)

	if len(collector.shapes) != 2 {
		t.Errorf("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)
	}

	if circleShape.CircleX != 5 {
		t.Errorf("Expected CircleX 5, got %f", circleShape.CircleX)
	}

	if circleShape.CircleY != 5 {
		t.Errorf("Expected CircleY 5, got %f", circleShape.CircleY)
	}

	if circleShape.CircleSize != 20 {
		t.Errorf("Expected CircleSize 20, got %f", circleShape.CircleSize)
	}

	if circleShape.Invert != false {
		t.Errorf("Expected Invert false, got %v", circleShape.Invert)
	}

	// Test Reset
	collector.Reset()
	if len(collector.shapes) != 0 {
		t.Errorf("Expected empty shapes slice after Reset, got %d", len(collector.shapes))
	}

	// Test that we can add shapes again after reset
	collector.AddPolygon([]Point{{X: 1, Y: 1}})
	if len(collector.shapes) != 1 {
		t.Errorf("Expected 1 shape after Reset and AddPolygon, got %d", len(collector.shapes))
	}
}

func TestIsValidHash(t *testing.T) {
	tests := []struct {
		name     string
		hash     string
		expected bool
	}{
		{
			name:     "Valid 40-character hex hash",
			hash:     "abcdef1234567890abcdef1234567890abcdef12",
			expected: true,
		},
		{
			name:     "Valid 32-character hex hash",
			hash:     "abcdef1234567890abcdef1234567890",
			expected: true,
		},
		{
			name:     "Empty hash",
			hash:     "",
			expected: false,
		},
		{
			name:     "Hash too short",
			hash:     "abc",
			expected: false,
		},
		{
			name:     "Hash with invalid characters",
			hash:     "abcdef1234567890abcdef1234567890abcdefgh",
			expected: false,
		},
		{
			name:     "Hash with uppercase letters",
			hash:     "ABCDEF1234567890ABCDEF1234567890ABCDEF12",
			expected: true,
		},
		{
			name:     "Mixed case hash",
			hash:     "AbCdEf1234567890aBcDeF1234567890AbCdEf12",
			expected: true,
		},
		{
			name:     "Hash with spaces",
			hash:     "abcdef12 34567890abcdef1234567890abcdef12",
			expected: false,
		},
		{
			name:     "All zeros",
			hash:     "0000000000000000000000000000000000000000",
			expected: true,
		},
		{
			name:     "All f's",
			hash:     "ffffffffffffffffffffffffffffffffffffffff",
			expected: true,
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			result := util.IsValidHash(test.hash)
			if result != test.expected {
				t.Errorf("Expected %v for hash '%s', got %v", test.expected, test.hash, result)
			}
		})
	}
}

func TestParseHex(t *testing.T) {
	tests := []struct {
		name         string
		hash         string
		position     int
		octets       int
		expected     int
		expectsError bool
	}{
		{
			name:         "Valid single octet",
			hash:         "abcdef1234567890",
			position:     0,
			octets:       1,
			expected:     0xa,
			expectsError: false,
		},
		{
			name:         "Valid two octets",
			hash:         "abcdef1234567890",
			position:     1,
			octets:       2,
			expected:     0xbc,
			expectsError: false,
		},
		{
			name:         "Position at end of hash",
			hash:         "abcdef12",
			position:     7,
			octets:       1,
			expected:     0x2,
			expectsError: false,
		},
		{
			name:         "Position beyond hash length",
			hash:         "abc",
			position:     5,
			octets:       1,
			expected:     0,
			expectsError: true,
		},
		{
			name:         "Octets extend beyond hash",
			hash:         "abcdef12",
			position:     6,
			octets:       3,
			expected:     0x12, // Should read to end of hash
			expectsError: false,
		},
		{
			name:         "Zero octets",
			hash:         "abcdef12",
			position:     0,
			octets:       0,
			expected:     0xabcdef12, // Should read to end when octets is 0
			expectsError: false,
		},
		{
			name:         "Negative position",
			hash:         "abcdef12",
			position:     -1,
			octets:       1,
			expected:     0x2, // Should read from end
			expectsError: false,
		},
		{
			name:         "Empty hash",
			hash:         "",
			position:     0,
			octets:       1,
			expected:     0,
			expectsError: true,
		},
		{
			name:         "All f's",
			hash:         "ffffffff",
			position:     0,
			octets:       4,
			expected:     0xffff,
			expectsError: false,
		},
		{
			name:         "Mixed case",
			hash:         "AbCdEf12",
			position:     2,
			octets:       2,
			expected:     0xcd,
			expectsError: false,
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			result, err := util.ParseHex(test.hash, test.position, test.octets)

			if test.expectsError {
				if err == nil {
					t.Errorf("Expected error for ParseHex(%s, %d, %d), but got none", test.hash, test.position, test.octets)
				}
				return
			}

			if err != nil {
				t.Errorf("Unexpected error for ParseHex(%s, %d, %d): %v", test.hash, test.position, test.octets, err)
				return
			}

			if result != test.expected {
				t.Errorf("Expected %d (0x%x), got %d (0x%x)", test.expected, test.expected, result, result)
			}
		})
	}
}
