package jdenticon

import (
	"testing"
)

func TestDefaultConfig(t *testing.T) {
	config := DefaultConfig()
	
	// Test default values match jdenticon-js
	if len(config.HueRestrictions) != 0 {
		t.Errorf("Expected no hue restrictions, got %v", config.HueRestrictions)
	}
	
	expectedColorRange := [2]float64{0.4, 0.8}
	if config.ColorLightnessRange != expectedColorRange {
		t.Errorf("Expected color lightness range %v, got %v", expectedColorRange, config.ColorLightnessRange)
	}
	
	expectedGrayscaleRange := [2]float64{0.3, 0.9}
	if config.GrayscaleLightnessRange != expectedGrayscaleRange {
		t.Errorf("Expected grayscale lightness range %v, got %v", expectedGrayscaleRange, config.GrayscaleLightnessRange)
	}
	
	if config.ColorSaturation != 0.5 {
		t.Errorf("Expected color saturation 0.5, got %f", config.ColorSaturation)
	}
	
	if config.GrayscaleSaturation != 0.0 {
		t.Errorf("Expected grayscale saturation 0.0, got %f", config.GrayscaleSaturation)
	}
	
	if config.BackgroundColor != "" {
		t.Errorf("Expected empty background color, got %s", config.BackgroundColor)
	}
	
	if config.Padding != 0.08 {
		t.Errorf("Expected padding 0.08, got %f", config.Padding)
	}
}

func TestWithHueRestrictions(t *testing.T) {
	tests := []struct {
		name    string
		hues    []float64
		wantErr bool
	}{
		{"valid hues", []float64{0, 90, 180, 270}, false},
		{"single hue", []float64{120}, false},
		{"empty slice", []float64{}, false},
		{"negative hue", []float64{-10}, true},
		{"hue too large", []float64{360}, true},
		{"mixed valid/invalid", []float64{120, 400}, true},
	}
	
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			config, err := Configure(WithHueRestrictions(tt.hues))
			
			if tt.wantErr {
				if err == nil {
					t.Errorf("Expected error for hues %v, got none", tt.hues)
				}
				return
			}
			
			if err != nil {
				t.Errorf("Unexpected error: %v", err)
				return
			}
			
			if len(config.HueRestrictions) != len(tt.hues) {
				t.Errorf("Expected %d hue restrictions, got %d", len(tt.hues), len(config.HueRestrictions))
			}
			
			for i, hue := range tt.hues {
				if config.HueRestrictions[i] != hue {
					t.Errorf("Expected hue %f at index %d, got %f", hue, i, config.HueRestrictions[i])
				}
			}
		})
	}
}

func TestWithLightnessRanges(t *testing.T) {
	tests := []struct {
		name    string
		min     float64
		max     float64
		wantErr bool
	}{
		{"valid range", 0.2, 0.8, false},
		{"full range", 0.0, 1.0, false},
		{"equal values", 0.5, 0.5, false},
		{"min > max", 0.8, 0.2, true},
		{"negative min", -0.1, 0.5, true},
		{"max > 1", 0.5, 1.1, true},
		{"both invalid", -0.1, 1.1, true},
	}
	
	for _, tt := range tests {
		t.Run("color_"+tt.name, func(t *testing.T) {
			config, err := Configure(WithColorLightnessRange(tt.min, tt.max))
			
			if tt.wantErr {
				if err == nil {
					t.Errorf("Expected error for range [%f, %f], got none", tt.min, tt.max)
				}
				return
			}
			
			if err != nil {
				t.Errorf("Unexpected error: %v", err)
				return
			}
			
			expected := [2]float64{tt.min, tt.max}
			if config.ColorLightnessRange != expected {
				t.Errorf("Expected range %v, got %v", expected, config.ColorLightnessRange)
			}
		})
		
		t.Run("grayscale_"+tt.name, func(t *testing.T) {
			config, err := Configure(WithGrayscaleLightnessRange(tt.min, tt.max))
			
			if tt.wantErr {
				if err == nil {
					t.Errorf("Expected error for range [%f, %f], got none", tt.min, tt.max)
				}
				return
			}
			
			if err != nil {
				t.Errorf("Unexpected error: %v", err)
				return
			}
			
			expected := [2]float64{tt.min, tt.max}
			if config.GrayscaleLightnessRange != expected {
				t.Errorf("Expected range %v, got %v", expected, config.GrayscaleLightnessRange)
			}
		})
	}
}

func TestWithSaturation(t *testing.T) {
	tests := []struct {
		name       string
		saturation float64
		wantErr    bool
	}{
		{"valid saturation", 0.5, false},
		{"zero saturation", 0.0, false},
		{"max saturation", 1.0, false},
		{"negative saturation", -0.1, true},
		{"saturation > 1", 1.1, true},
	}
	
	for _, tt := range tests {
		t.Run("color_"+tt.name, func(t *testing.T) {
			config, err := Configure(WithColorSaturation(tt.saturation))
			
			if tt.wantErr {
				if err == nil {
					t.Errorf("Expected error for saturation %f, got none", tt.saturation)
				}
				return
			}
			
			if err != nil {
				t.Errorf("Unexpected error: %v", err)
				return
			}
			
			if config.ColorSaturation != tt.saturation {
				t.Errorf("Expected saturation %f, got %f", tt.saturation, config.ColorSaturation)
			}
		})
		
		t.Run("grayscale_"+tt.name, func(t *testing.T) {
			config, err := Configure(WithGrayscaleSaturation(tt.saturation))
			
			if tt.wantErr {
				if err == nil {
					t.Errorf("Expected error for saturation %f, got none", tt.saturation)
				}
				return
			}
			
			if err != nil {
				t.Errorf("Unexpected error: %v", err)
				return
			}
			
			if config.GrayscaleSaturation != tt.saturation {
				t.Errorf("Expected saturation %f, got %f", tt.saturation, config.GrayscaleSaturation)
			}
		})
	}
}

func TestWithBackgroundColor(t *testing.T) {
	tests := []struct {
		name     string
		color    string
		wantErr  bool
		expected string
	}{
		{"empty color", "", false, ""},
		{"3-char hex", "#fff", false, "#fff"},
		{"4-char hex with alpha", "#ffff", false, "#ffff"},
		{"6-char hex", "#ffffff", false, "#ffffff"},
		{"8-char hex with alpha", "#ffffff80", false, "#ffffff80"},
		{"lowercase", "#abc123", false, "#abc123"},
		{"uppercase", "#ABC123", false, "#ABC123"},
		{"invalid format", "ffffff", true, ""},
		{"invalid chars", "#gggggg", true, ""},
		{"too short", "#ff", true, ""},
		{"5-char hex", "#fffff", true, ""},
		{"7-char hex", "#fffffff", true, ""},
		{"9-char hex", "#fffffffff", true, ""},
	}
	
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			config, err := Configure(WithBackgroundColor(tt.color))
			
			if tt.wantErr {
				if err == nil {
					t.Errorf("Expected error for color %s, got none", tt.color)
				}
				return
			}
			
			if err != nil {
				t.Errorf("Unexpected error: %v", err)
				return
			}
			
			if config.BackgroundColor != tt.expected {
				t.Errorf("Expected color %s, got %s", tt.expected, config.BackgroundColor)
			}
		})
	}
}

func TestWithPadding(t *testing.T) {
	tests := []struct {
		name    string
		padding float64
		wantErr bool
	}{
		{"valid padding", 0.08, false},
		{"zero padding", 0.0, false},
		{"max padding", 0.5, false},
		{"negative padding", -0.1, true},
		{"padding > 0.5", 0.6, true},
	}
	
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			config, err := Configure(WithPadding(tt.padding))
			
			if tt.wantErr {
				if err == nil {
					t.Errorf("Expected error for padding %f, got none", tt.padding)
				}
				return
			}
			
			if err != nil {
				t.Errorf("Unexpected error: %v", err)
				return
			}
			
			if config.Padding != tt.padding {
				t.Errorf("Expected padding %f, got %f", tt.padding, config.Padding)
			}
		})
	}
}

func TestConfigureMultipleOptions(t *testing.T) {
	config, err := Configure(
		WithHueRestrictions([]float64{120, 240}),
		WithColorSaturation(0.7),
		WithPadding(0.1),
		WithBackgroundColor("#ff0000"),
	)
	
	if err != nil {
		t.Errorf("Unexpected error: %v", err)
		return
	}
	
	if len(config.HueRestrictions) != 2 || config.HueRestrictions[0] != 120 || config.HueRestrictions[1] != 240 {
		t.Errorf("Expected hue restrictions [120, 240], got %v", config.HueRestrictions)
	}
	
	if config.ColorSaturation != 0.7 {
		t.Errorf("Expected color saturation 0.7, got %f", config.ColorSaturation)
	}
	
	if config.Padding != 0.1 {
		t.Errorf("Expected padding 0.1, got %f", config.Padding)
	}
	
	if config.BackgroundColor != "#ff0000" {
		t.Errorf("Expected background color #ff0000, got %s", config.BackgroundColor)
	}
	
	// Check that other values are still default
	expectedColorRange := [2]float64{0.4, 0.8}
	if config.ColorLightnessRange != expectedColorRange {
		t.Errorf("Expected default color lightness range %v, got %v", expectedColorRange, config.ColorLightnessRange)
	}
}

func TestParseColor(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		expected string
		wantErr  bool
	}{
		{"empty", "", "", false},
		{"3-char to 6-char", "#abc", "#aabbcc", false},
		{"4-char to 8-char", "#abcd", "#aabbccdd", false},
		{"6-char unchanged", "#abcdef", "#abcdef", false},
		{"8-char unchanged", "#abcdef80", "#abcdef80", false},
		{"invalid format", "abcdef", "", true},
		{"invalid length", "#abcde", "", true},
	}
	
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result, err := ParseColor(tt.input)
			
			if tt.wantErr {
				if err == nil {
					t.Errorf("Expected error for input %s, got none", tt.input)
				}
				return
			}
			
			if err != nil {
				t.Errorf("Unexpected error: %v", err)
				return
			}
			
			if result != tt.expected {
				t.Errorf("Expected %s, got %s", tt.expected, result)
			}
		})
	}
}

func TestConfigGetHue(t *testing.T) {
	tests := []struct {
		name         string
		restrictions []float64
		input        float64
		expected     float64
	}{
		{"no restrictions", nil, 0.5, 0.5},
		{"single restriction", []float64{180}, 0.5, 0.5},
		{"multiple restrictions", []float64{0, 120, 240}, 0.0, 0.0},
		{"multiple restrictions mid", []float64{0, 120, 240}, 0.5, 120.0 / 360.0},
		{"multiple restrictions high", []float64{0, 120, 240}, 0.99, 240.0 / 360.0},
	}
	
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			config := DefaultConfig()
			config.HueRestrictions = tt.restrictions
			
			result := config.GetHue(tt.input)
			
			// Allow small floating point differences
			if abs(result-tt.expected) > 0.001 {
				t.Errorf("Expected hue %f, got %f", tt.expected, result)
			}
		})
	}
}

func TestConfigGetLightness(t *testing.T) {
	config := DefaultConfig()
	
	// Test color lightness
	colorLight := config.GetColorLightness(0.0)
	if colorLight != 0.4 {
		t.Errorf("Expected min color lightness 0.4, got %f", colorLight)
	}
	
	colorLight = config.GetColorLightness(1.0)
	if colorLight != 0.8 {
		t.Errorf("Expected max color lightness 0.8, got %f", colorLight)
	}
	
	colorLight = config.GetColorLightness(0.5)
	expected := 0.4 + 0.5*(0.8-0.4)
	if abs(colorLight-expected) > 0.001 {
		t.Errorf("Expected mid color lightness %f, got %f", expected, colorLight)
	}
	
	// Test grayscale lightness
	grayLight := config.GetGrayscaleLightness(0.0)
	if grayLight != 0.3 {
		t.Errorf("Expected min grayscale lightness 0.3, got %f", grayLight)
	}
	
	grayLight = config.GetGrayscaleLightness(1.0)
	if abs(grayLight-0.9) > 0.001 {
		t.Errorf("Expected max grayscale lightness 0.9, got %f", grayLight)
	}
}

func TestConfigureFailing(t *testing.T) {
	// Test that Configure fails on invalid options
	_, err := Configure(
		WithHueRestrictions([]float64{400}), // Invalid hue
		WithColorSaturation(0.5),            // Valid option after invalid
	)
	
	if err == nil {
		t.Error("Expected Configure to fail with invalid hue restriction")
	}
}

// Helper function for floating point comparison
func abs(x float64) float64 {
	if x < 0 {
		return -x
	}
	return x
}