package engine

import (
	"context"
	"math"
	"testing"

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

// FuzzGeneratorGenerate tests the internal engine generator with arbitrary inputs
func FuzzGeneratorGenerate(f *testing.F) {
	// Seed with known hash patterns and sizes
	f.Add("abcdef1234567890", 64.0)
	f.Add("", 32.0)
	f.Add("0123456789abcdef", 128.0)
	f.Add("ffffffffffffffff", 256.0)
	f.Add("0000000000000000", 1.0)

	f.Fuzz(func(t *testing.T, hash string, size float64) {
		// Test invalid sizes for proper error handling
		if size <= 0 || math.IsNaN(size) || math.IsInf(size, 0) {
			// Create a generator with default config
			config := GeneratorConfig{
				ColorConfig: DefaultColorConfig(),
				CacheSize:   100,
			}

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

			_, err = generator.Generate(context.Background(), hash, size)
			if err == nil {
				t.Errorf("Generate with invalid size %f should have returned an error", size)
			}
			return // Stop further processing for invalid inputs
		}
		if size > 10000 {
			return // Avoid resource exhaustion
		}

		// Create a generator with default config
		config := GeneratorConfig{
			ColorConfig: DefaultColorConfig(),
			CacheSize:   100,
		}

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

		// Generate should never panic, regardless of hash input
		icon, err := generator.Generate(context.Background(), hash, size)

		// We don't require success for all inputs, but we require no crashes
		if err != nil {
			// Check that error is reasonable
			_ = err
			return
		}

		if icon == nil {
			t.Errorf("Generate(%q, %f) returned nil icon without error", hash, size)
			return
		}

		// Verify icon has reasonable properties
		if icon.Size != size {
			t.Errorf("Generated icon size %f does not match requested size %f", icon.Size, size)
		}

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

// FuzzColorConfigValidation tests color configuration validation
func FuzzColorConfigValidation(f *testing.F) {
	// Seed with various color configuration patterns
	f.Add(0.5, 0.5, 0.4, 0.8, 0.3, 0.9, 0.08)
	f.Add(-1.0, 2.0, -0.5, 1.5, -0.1, 1.1, -0.1)
	f.Add(0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0)

	f.Fuzz(func(t *testing.T, colorSat, grayscaleSat, colorLightMin, colorLightMax,
		grayscaleLightMin, grayscaleLightMax, padding float64) {
		config := ColorConfig{
			ColorSaturation:     colorSat,
			GrayscaleSaturation: grayscaleSat,
			ColorLightness: LightnessRange{
				Min: colorLightMin,
				Max: colorLightMax,
			},
			GrayscaleLightness: LightnessRange{
				Min: grayscaleLightMin,
				Max: grayscaleLightMax,
			},
			IconPadding: padding,
		}

		// Validation should never panic
		err := config.Validate()
		_ = err

		// If validation passes, test that we can create a generator
		if err == nil {
			genConfig := GeneratorConfig{
				ColorConfig: config,
				CacheSize:   10,
			}

			generator, genErr := NewGeneratorWithConfig(genConfig)
			if genErr == nil && generator != nil {
				// Try to generate an icon
				icon, iconErr := generator.Generate(context.Background(), "test-hash", 64.0)
				if iconErr == nil && icon != nil {
					// Verify the icon has valid properties
					if icon.Size != 64.0 {
						t.Errorf("Icon size mismatch: expected 64.0, got %f", icon.Size)
					}
				}
			}
		}
	})
}

// FuzzParseHex tests the hex parsing function with arbitrary inputs
func FuzzParseHex(f *testing.F) {
	// Seed with various hex patterns
	f.Add("abcdef123456", 0, 1)
	f.Add("0123456789", 5, 2)
	f.Add("", 0, 1)
	f.Add("xyz", 0, 1)
	f.Add("ffffffffff", 10, 5)

	f.Fuzz(func(t *testing.T, hash string, position, octets int) {
		// ParseHex should never panic, even with invalid inputs
		result, err := util.ParseHex(hash, position, octets)

		// Determine the actual slice being parsed (mimic ParseHex logic)
		startPosition := position
		if startPosition < 0 {
			startPosition = len(hash) + startPosition
		}

		// Only check substring if it would be valid to parse
		if startPosition >= 0 && startPosition < len(hash) {
			end := len(hash)
			if octets > 0 {
				end = startPosition + octets
				if end > len(hash) {
					end = len(hash)
				}
			}

			// Extract the substring that ParseHex would actually process
			if startPosition < end {
				substr := hash[startPosition:end]

				// Check if the relevant substring contains invalid hex characters
				isInvalidHex := containsNonHex(substr)
				if isInvalidHex && err == nil {
					t.Errorf("ParseHex should have returned an error for invalid hex substring %q, but didn't", substr)
				}
			}
		}

		// Check for position out of bounds (after negative position handling)
		if startPosition >= len(hash) && len(hash) > 0 && err == nil {
			t.Errorf("ParseHex should return error for position %d >= hash length %d", startPosition, len(hash))
		}

		if err != nil {
			return // Correctly returned an error
		}

		// On success, verify the result is reasonable
		_ = result // Result could be any valid integer
	})
}

// FuzzColorGeneration tests color generation with arbitrary hue values
func FuzzColorGeneration(f *testing.F) {
	// Seed with various hue values
	f.Add(0.0, 0.5)
	f.Add(0.5, 0.7)
	f.Add(1.0, 0.3)
	f.Add(-0.1, 0.9)
	f.Add(1.1, 0.1)

	f.Fuzz(func(t *testing.T, hue, lightnessValue float64) {
		// Skip extreme values that might cause issues
		if math.IsNaN(hue) || math.IsInf(hue, 0) || math.IsNaN(lightnessValue) || math.IsInf(lightnessValue, 0) {
			return
		}

		config := DefaultColorConfig()

		// Test actual production color generation functions
		color := GenerateColor(hue, config, lightnessValue)

		// Verify color has reasonable RGB values (0-255)
		r, g, b, err := color.ToRGB()
		if err != nil {
			t.Errorf("color.ToRGB failed: %v", err)
			return
		}
		// RGB values are uint8, so they're guaranteed to be in 0-255 range
		_, _, _ = r, g, b

		// Test grayscale generation as well
		grayscale := GenerateGrayscale(config, lightnessValue)
		gr, gg, gb, err := grayscale.ToRGB()
		if err != nil {
			t.Errorf("grayscale.ToRGB failed: %v", err)
			return
		}
		// RGB values are uint8, so they're guaranteed to be in 0-255 range
		_, _, _ = gr, gg, gb

		// Test color theme generation
		theme := GenerateColorTheme(hue, config)
		if len(theme) != 5 {
			t.Errorf("GenerateColorTheme should return 5 colors, got %d", len(theme))
		}
		for _, themeColor := range theme {
			tr, tg, tb, err := themeColor.ToRGB()
			if err != nil {
				t.Errorf("themeColor.ToRGB failed: %v", err)
				continue
			}
			// RGB values are uint8, so they're guaranteed to be in 0-255 range
			_, _, _ = tr, tg, tb
		}
	})
}

// FuzzHexColorParsing tests hex color parsing with arbitrary strings
func FuzzHexColorParsing(f *testing.F) {
	// Seed with various hex color patterns
	f.Add("#ffffff")
	f.Add("#000000")
	f.Add("#fff")
	f.Add("#12345678")
	f.Add("invalid")
	f.Add("")
	f.Add("#")
	f.Add("#gggggg")

	f.Fuzz(func(t *testing.T, colorStr string) {
		// ValidateHexColor should never panic
		err := ValidateHexColor(colorStr)
		_ = err

		// If validation passes, try parsing
		if err == nil {
			color, parseErr := ParseHexColorToEngine(colorStr)
			if parseErr == nil {
				// Verify parsed color has valid properties
				r, g, b, err := color.ToRGB()
				if err != nil {
					t.Errorf("color.ToRGB failed: %v", err)
					return
				}
				// RGB and alpha values are uint8, so they're guaranteed to be in 0-255 range
				_, _, _ = r, g, b
				_ = color.A
			}
		}
	})
}

// FuzzGeneratorCaching tests generator caching behavior with arbitrary inputs
func FuzzGeneratorCaching(f *testing.F) {
	// Seed with various cache scenarios
	f.Add("hash1", 64.0, "hash2", 128.0)
	f.Add("same", 64.0, "same", 64.0)
	f.Add("", 1.0, "different", 1.0)

	f.Fuzz(func(t *testing.T, hash1 string, size1 float64, hash2 string, size2 float64) {
		// Skip invalid sizes
		if size1 <= 0 || size1 > 1000 || size2 <= 0 || size2 > 1000 {
			return
		}
		if math.IsNaN(size1) || math.IsInf(size1, 0) || math.IsNaN(size2) || math.IsInf(size2, 0) {
			return
		}

		config := GeneratorConfig{
			ColorConfig: DefaultColorConfig(),
			CacheSize:   10,
		}

		generator, err := NewGeneratorWithConfig(config)
		if err != nil {
			return
		}

		// Generate first icon
		icon1, err1 := generator.Generate(context.Background(), hash1, size1)
		if err1 != nil {
			return
		}

		// Check cache metrics after first generation
		initialHits, initialMisses := generator.GetCacheMetrics()

		// Generate second icon (might be cache hit if same)
		icon2, err2 := generator.Generate(context.Background(), hash2, size2)
		if err2 != nil {
			return
		}

		// Verify cache behavior
		finalSize := generator.GetCacheSize()
		finalHits, finalMisses := generator.GetCacheMetrics()

		// Cache size should not exceed capacity
		if finalSize > generator.GetCacheCapacity() {
			t.Errorf("Cache size %d exceeds capacity %d", finalSize, generator.GetCacheCapacity())
		}

		// Metrics should increase appropriately
		if finalHits < initialHits || finalMisses < initialMisses {
			t.Errorf("Cache metrics decreased: hits %d->%d, misses %d->%d",
				initialHits, finalHits, initialMisses, finalMisses)
		}

		// If same hash and size, should be cache hit
		if hash1 == hash2 && size1 == size2 && icon1 != nil && icon2 != nil {
			if finalHits <= initialHits {
				t.Errorf("Expected cache hit for identical inputs, but hits did not increase. Initial: %d, Final: %d", initialHits, finalHits)
			}
		}

		// Clear cache should not panic and should reset metrics
		generator.ClearCache()
		clearedSize := generator.GetCacheSize()
		clearedHits, clearedMisses := generator.GetCacheMetrics()

		if clearedSize != 0 {
			t.Errorf("Cache size after clear: expected 0, got %d", clearedSize)
		}
		if clearedHits != 0 || clearedMisses != 0 {
			t.Errorf("Metrics after clear: expected 0,0 got %d,%d", clearedHits, clearedMisses)
		}
	})
}

// FuzzLightnessRangeOperations tests lightness range calculations
func FuzzLightnessRangeOperations(f *testing.F) {
	// Seed with various lightness range values
	f.Add(0.0, 1.0, 0.5)
	f.Add(0.4, 0.8, 0.7)
	f.Add(-0.1, 1.1, 0.5)
	f.Add(0.9, 0.1, 0.5) // Invalid range (min > max)

	f.Fuzz(func(t *testing.T, min, max, value float64) {
		// Skip NaN and infinite values
		if math.IsNaN(min) || math.IsInf(min, 0) ||
			math.IsNaN(max) || math.IsInf(max, 0) ||
			math.IsNaN(value) || math.IsInf(value, 0) {
			return
		}

		lightnessRange := LightnessRange{Min: min, Max: max}

		// Test actual production LightnessRange.GetLightness method
		result := lightnessRange.GetLightness(value)

		// GetLightness should never panic and should return a valid result
		if math.IsNaN(result) || math.IsInf(result, 0) {
			t.Errorf("GetLightness(%f) with range [%f, %f] returned invalid result: %f", value, min, max, result)
		}

		// Result should be clamped to [0, 1] range
		if result < 0 || result > 1 {
			t.Errorf("GetLightness(%f) with range [%f, %f] returned out-of-range result: %f", value, min, max, result)
		}

		// If input range is valid and value is in [0,1], result should be in range
		if min >= 0 && max <= 1 && min <= max && value >= 0 && value <= 1 {
			expectedMin := math.Min(min, max)
			expectedMax := math.Max(min, max)
			if result < expectedMin || result > expectedMax {
				// Allow for floating point precision issues
				if math.Abs(result-expectedMin) > 1e-10 && math.Abs(result-expectedMax) > 1e-10 {
					t.Errorf("GetLightness(%f) with valid range [%f, %f] returned result %f outside expected range [%f, %f]",
						value, min, max, result, expectedMin, expectedMax)
				}
			}
		}
	})
}

// containsNonHex checks if a string contains non-hexadecimal characters
// Note: strconv.ParseInt allows negative hex numbers, so '-' is valid at the start
func containsNonHex(s string) bool {
	for i, r := range s {
		isHexDigit := (r >= '0' && r <= '9') || (r >= 'a' && r <= 'f') || (r >= 'A' && r <= 'F')
		isValidMinus := (r == '-' && i == 0) // Minus only valid at start
		if !isHexDigit && !isValidMinus {
			return true
		}
	}
	return false
}
