package engine

import (
	"math"
	"testing"
)

func TestColorConfigValidate(t *testing.T) {
	tests := []struct {
		name    string
		config  ColorConfig
		wantErr bool
		errMsg  string
	}{
		{
			name:    "valid default config",
			config:  DefaultColorConfig(),
			wantErr: false,
		},
		{
			name: "invalid color saturation < 0",
			config: ColorConfig{
				ColorSaturation:     -0.1,
				GrayscaleSaturation: 0.0,
				ColorLightness:      LightnessRange{Min: 0.4, Max: 0.8},
				GrayscaleLightness:  LightnessRange{Min: 0.3, Max: 0.9},
				IconPadding:         0.08,
			},
			wantErr: true,
			errMsg:  "color saturation out of range",
		},
		{
			name: "invalid grayscale saturation > 1",
			config: ColorConfig{
				ColorSaturation:     0.5,
				GrayscaleSaturation: 1.5,
				ColorLightness:      LightnessRange{Min: 0.4, Max: 0.8},
				GrayscaleLightness:  LightnessRange{Min: 0.3, Max: 0.9},
				IconPadding:         0.08,
			},
			wantErr: true,
			errMsg:  "grayscale saturation out of range",
		},
		{
			name: "invalid color lightness min > max",
			config: ColorConfig{
				ColorSaturation:     0.5,
				GrayscaleSaturation: 0.0,
				ColorLightness:      LightnessRange{Min: 0.8, Max: 0.4},
				GrayscaleLightness:  LightnessRange{Min: 0.3, Max: 0.9},
				IconPadding:         0.08,
			},
			wantErr: true,
			errMsg:  "color lightness range invalid",
		},
		{
			name: "invalid icon padding > 1",
			config: ColorConfig{
				ColorSaturation:     0.5,
				GrayscaleSaturation: 0.0,
				ColorLightness:      LightnessRange{Min: 0.4, Max: 0.8},
				GrayscaleLightness:  LightnessRange{Min: 0.3, Max: 0.9},
				IconPadding:         1.5,
			},
			wantErr: true,
			errMsg:  "icon padding out of range",
		},
		{
			name: "multiple validation errors",
			config: ColorConfig{
				ColorSaturation:     -0.1, // Invalid
				GrayscaleSaturation: 1.5,  // Invalid
				ColorLightness:      LightnessRange{Min: 0.4, Max: 0.8},
				GrayscaleLightness:  LightnessRange{Min: 0.3, Max: 0.9},
				IconPadding:         0.08,
			},
			wantErr: true,
			errMsg:  "color saturation out of range",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := tt.config.Validate()

			if tt.wantErr {
				if err == nil {
					t.Errorf("Expected error for config validation, got none")
					return
				}
				if tt.errMsg != "" && !containsString(err.Error(), tt.errMsg) {
					t.Errorf("Expected error message to contain '%s', got '%s'", tt.errMsg, err.Error())
				}
			} else {
				if err != nil {
					t.Errorf("Unexpected error: %v", err)
				}
			}
		})
	}
}

func TestDefaultColorConfig(t *testing.T) {
	config := DefaultColorConfig()

	// Test default values match JavaScript implementation
	if config.ColorSaturation != 0.5 {
		t.Errorf("ColorSaturation = %f, want 0.5", config.ColorSaturation)
	}

	if config.GrayscaleSaturation != 0.0 {
		t.Errorf("GrayscaleSaturation = %f, want 0.0", config.GrayscaleSaturation)
	}

	if config.ColorLightness.Min != 0.4 || config.ColorLightness.Max != 0.8 {
		t.Errorf("ColorLightness = {%f, %f}, want {0.4, 0.8}",
			config.ColorLightness.Min, config.ColorLightness.Max)
	}

	if config.GrayscaleLightness.Min != 0.3 || config.GrayscaleLightness.Max != 0.9 {
		t.Errorf("GrayscaleLightness = {%f, %f}, want {0.3, 0.9}",
			config.GrayscaleLightness.Min, config.GrayscaleLightness.Max)
	}

	if len(config.Hues) != 0 {
		t.Errorf("Hues should be empty by default, got %v", config.Hues)
	}

	if config.BackColor != nil {
		t.Error("BackColor should be nil by default")
	}

	if config.IconPadding != 0.08 {
		t.Errorf("IconPadding = %f, want 0.08", config.IconPadding)
	}
}

func TestLightnessRangeGetLightness(t *testing.T) {
	lr := LightnessRange{Min: 0.3, Max: 0.9}

	tests := []struct {
		value    float64
		expected float64
	}{
		{0.0, 0.3},  // Min value
		{1.0, 0.9},  // Max value
		{0.5, 0.6},  // Middle value: 0.3 + 0.5 * (0.9 - 0.3) = 0.6
		{-0.5, 0.3}, // Below range, should clamp to min
		{1.5, 0.9},  // Above range, should clamp to max
	}

	for _, tt := range tests {
		result := lr.GetLightness(tt.value)
		if math.Abs(result-tt.expected) > 0.001 {
			t.Errorf("GetLightness(%f) = %f, want %f", tt.value, result, tt.expected)
		}
	}
}

func TestConfigRestrictHue(t *testing.T) {
	tests := []struct {
		name        string
		hues        []float64
		originalHue float64
		expectedHue float64
	}{
		{
			name:        "no restriction",
			hues:        nil,
			originalHue: 0.25,
			expectedHue: 0.25,
		},
		{
			name:        "empty restriction",
			hues:        []float64{},
			originalHue: 0.25,
			expectedHue: 0.25,
		},
		{
			name:        "single hue restriction",
			hues:        []float64{180}, // 180 degrees = 0.5 turns
			originalHue: 0.25,
			expectedHue: 0.5,
		},
		{
			name:        "multiple hue restriction",
			hues:        []float64{0, 120, 240}, // Red, Green, Blue
			originalHue: 0.1,                    // Should map to first hue (0 degrees)
			expectedHue: 0.0,
		},
		{
			name:        "hue normalization - negative",
			hues:        []float64{90}, // 90 degrees = 0.25 turns
			originalHue: -0.5,
			expectedHue: 0.25,
		},
		{
			name:        "hue normalization - over 1",
			hues:        []float64{270}, // 270 degrees = 0.75 turns
			originalHue: 1.5,
			expectedHue: 0.75,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			config := ColorConfig{Hues: tt.hues}
			result := config.RestrictHue(tt.originalHue)

			if math.Abs(result-tt.expectedHue) > 0.001 {
				t.Errorf("RestrictHue(%f) = %f, want %f", tt.originalHue, result, tt.expectedHue)
			}
		})
	}
}

func TestConfigNormalize(t *testing.T) {
	// Test that Normalize corrects invalid values
	config := ColorConfig{
		ColorSaturation:     -0.5,                                // Invalid: below 0
		GrayscaleSaturation: 1.5,                                 // Invalid: above 1
		ColorLightness:      LightnessRange{Min: 0.8, Max: 0.2},  // Invalid: min > max
		GrayscaleLightness:  LightnessRange{Min: -0.1, Max: 1.1}, // Invalid: out of range
		IconPadding:         2.0,                                 // Invalid: above 1
	}

	config.Normalize()

	if config.ColorSaturation != 0.0 {
		t.Errorf("ColorSaturation after validation = %f, want 0.0", config.ColorSaturation)
	}

	if config.GrayscaleSaturation != 1.0 {
		t.Errorf("GrayscaleSaturation after validation = %f, want 1.0", config.GrayscaleSaturation)
	}

	// Min and max should be swapped
	if config.ColorLightness.Min != 0.2 || config.ColorLightness.Max != 0.8 {
		t.Errorf("ColorLightness after validation = {%f, %f}, want {0.2, 0.8}",
			config.ColorLightness.Min, config.ColorLightness.Max)
	}

	// Values should be clamped
	if config.GrayscaleLightness.Min != 0.0 || config.GrayscaleLightness.Max != 1.0 {
		t.Errorf("GrayscaleLightness after validation = {%f, %f}, want {0.0, 1.0}",
			config.GrayscaleLightness.Min, config.GrayscaleLightness.Max)
	}

	if config.IconPadding != 1.0 {
		t.Errorf("IconPadding after validation = %f, want 1.0", config.IconPadding)
	}
}

func TestColorConfigBuilder(t *testing.T) {
	redColor := NewColorRGB(255, 0, 0)

	config := DefaultColorConfig()
	config.ColorSaturation = 0.7
	config.GrayscaleSaturation = 0.1
	config.ColorLightness = LightnessRange{Min: 0.2, Max: 0.8}
	config.GrayscaleLightness = LightnessRange{Min: 0.1, Max: 0.9}
	config.Hues = []float64{0, 120, 240}
	config.BackColor = &redColor
	config.IconPadding = 0.1

	if config.ColorSaturation != 0.7 {
		t.Errorf("ColorSaturation = %f, want 0.7", config.ColorSaturation)
	}

	if config.GrayscaleSaturation != 0.1 {
		t.Errorf("GrayscaleSaturation = %f, want 0.1", config.GrayscaleSaturation)
	}

	if config.ColorLightness.Min != 0.2 || config.ColorLightness.Max != 0.8 {
		t.Errorf("ColorLightness = {%f, %f}, want {0.2, 0.8}",
			config.ColorLightness.Min, config.ColorLightness.Max)
	}

	if config.GrayscaleLightness.Min != 0.1 || config.GrayscaleLightness.Max != 0.9 {
		t.Errorf("GrayscaleLightness = {%f, %f}, want {0.1, 0.9}",
			config.GrayscaleLightness.Min, config.GrayscaleLightness.Max)
	}

	if len(config.Hues) != 3 || config.Hues[0] != 0 || config.Hues[1] != 120 || config.Hues[2] != 240 {
		t.Errorf("Hues = %v, want [0, 120, 240]", config.Hues)
	}

	if config.BackColor == nil || !config.BackColor.Equals(redColor) {
		t.Error("BackColor should be set to red")
	}

	if config.IconPadding != 0.1 {
		t.Errorf("IconPadding = %f, want 0.1", config.IconPadding)
	}
}

func TestColorConfigValidation(t *testing.T) {
	// Test direct config validation
	config := DefaultColorConfig()
	config.ColorSaturation = -0.5    // Invalid
	config.GrayscaleSaturation = 1.5 // Invalid

	err := config.Validate()

	// Should return validation error for invalid values
	if err == nil {
		t.Error("Expected validation error for invalid configuration, got nil")
	}

	if !containsString(err.Error(), "color saturation out of range") {
		t.Errorf("Expected error to mention color saturation validation, got: %s", err.Error())
	}
}

// containsString checks if a string contains a substring
func containsString(s, substr string) bool {
	for i := 0; i <= len(s)-len(substr); i++ {
		if s[i:i+len(substr)] == substr {
			return true
		}
	}
	return false
}
