package jdenticon

import (
	"bytes"
	"fmt"
	"strings"
	"testing"
)

func TestGenerate(t *testing.T) {
	tests := []struct {
		name  string
		value string
		size  int
	}{
		{
			name:  "email address",
			value: "test@example.com",
			size:  64,
		},
		{
			name:  "username",
			value: "johndoe",
			size:  32,
		},
		{
			name:  "large icon",
			value: "large-icon-test",
			size:  256,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			icon, err := Generate(tt.value, tt.size)
			if err != nil {
				t.Fatalf("Generate failed: %v", err)
			}

			if icon == nil {
				t.Fatal("Generate returned nil icon")
			}

			// Test SVG generation
			svg, err := icon.ToSVG()
			if err != nil {
				t.Fatalf("ToSVG failed: %v", err)
			}

			if svg == "" {
				t.Error("ToSVG returned empty string")
			}

			// Basic SVG validation
			if !strings.Contains(svg, "<svg") {
				t.Error("SVG output does not contain svg tag")
			}

			if !strings.Contains(svg, "</svg>") {
				t.Error("SVG output does not contain closing svg tag")
			}

			// Test PNG generation
			png, err := icon.ToPNG()
			if err != nil {
				t.Fatalf("ToPNG failed: %v", err)
			}

			if len(png) == 0 {
				t.Error("ToPNG returned empty data")
			}

			// Basic PNG validation (check PNG signature)
			if len(png) < 8 || string(png[1:4]) != "PNG" {
				t.Error("PNG output does not have valid PNG signature")
			}
		})
	}
}

func TestGenerateConsistency(t *testing.T) {
	value := "consistency-test"
	size := 64

	// Generate the same icon multiple times
	icon1, err := Generate(value, size)
	if err != nil {
		t.Fatalf("First generate failed: %v", err)
	}

	icon2, err := Generate(value, size)
	if err != nil {
		t.Fatalf("Second generate failed: %v", err)
	}

	// SVG should be identical
	svg1, err := icon1.ToSVG()
	if err != nil {
		t.Fatalf("First ToSVG failed: %v", err)
	}

	svg2, err := icon2.ToSVG()
	if err != nil {
		t.Fatalf("Second ToSVG failed: %v", err)
	}

	if svg1 != svg2 {
		t.Error("SVG outputs are not consistent for same input")
	}

	// PNG should be identical
	png1, err := icon1.ToPNG()
	if err != nil {
		t.Fatalf("First ToPNG failed: %v", err)
	}

	png2, err := icon2.ToPNG()
	if err != nil {
		t.Fatalf("Second ToPNG failed: %v", err)
	}

	if !bytes.Equal(png1, png2) {
		t.Error("PNG outputs are not consistent for same input")
	}
}

func TestGenerateInvalidInputs(t *testing.T) {
	tests := []struct {
		name  string
		value string
		size  int
	}{
		{
			name:  "zero size",
			value: "test",
			size:  0,
		},
		{
			name:  "negative size",
			value: "test",
			size:  -10,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			_, err := Generate(tt.value, tt.size)
			if err == nil {
				t.Error("Expected error for invalid input")
			}
		})
	}
}

func TestGenerateVariety(t *testing.T) {
	// Test that different inputs produce different outputs
	values := []string{"value1", "value2", "value3", "test@example.com", "another-test"}
	size := 64

	svgs := make([]string, len(values))
	
	for i, value := range values {
		icon, err := Generate(value, size)
		if err != nil {
			t.Fatalf("Generate failed for %s: %v", value, err)
		}

		svg, err := icon.ToSVG()
		if err != nil {
			t.Fatalf("ToSVG failed for %s: %v", value, err)
		}

		svgs[i] = svg
	}

	// Check that all SVGs are different
	for i := 0; i < len(svgs); i++ {
		for j := i + 1; j < len(svgs); j++ {
			if svgs[i] == svgs[j] {
				t.Errorf("SVG outputs are identical for different inputs: %s and %s", values[i], values[j])
			}
		}
	}
}

func BenchmarkGenerate(b *testing.B) {
	value := "benchmark-test"
	size := 64

	b.ReportAllocs()
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_, err := Generate(value, size)
		if err != nil {
			b.Fatalf("Generate failed: %v", err)
		}
	}
}

// BenchmarkGenerateVariousSizes tests generation performance across different icon sizes
func BenchmarkGenerateVariousSizes(b *testing.B) {
	sizes := []int{64, 128, 256, 512, 1024}
	
	for _, size := range sizes {
		b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) {
			b.ResetTimer()
			b.ReportAllocs()
			
			for i := 0; i < b.N; i++ {
				_, err := Generate("benchmark@test.com", size)
				if err != nil {
					b.Fatalf("Generate failed for size %d: %v", size, err)
				}
			}
		})
	}
}

// BenchmarkGenerateVariousInputs tests generation performance with different input types
func BenchmarkGenerateVariousInputs(b *testing.B) {
	inputs := []struct {
		name  string
		value string
	}{
		{"email", "user@example.com"},
		{"username", "john_doe_123"},
		{"uuid", "550e8400-e29b-41d4-a716-446655440000"},
		{"short", "abc"},
		{"long", "this_is_a_very_long_identifier_that_might_be_used_for_generating_identicons_in_some_applications"},
		{"special_chars", "user+test@domain.co.uk"},
		{"numbers", "12345678901234567890"},
	}
	
	for _, input := range inputs {
		b.Run(input.name, func(b *testing.B) {
			b.ResetTimer()
			b.ReportAllocs()
			
			for i := 0; i < b.N; i++ {
				_, err := Generate(input.value, 128)
				if err != nil {
					b.Fatalf("Generate failed for input %s: %v", input.name, err)
				}
			}
		})
	}
}

func BenchmarkToSVG(b *testing.B) {
	icon, err := Generate("benchmark-test", 64)
	if err != nil {
		b.Fatalf("Generate failed: %v", err)
	}

	b.ReportAllocs()
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_, err := icon.ToSVG()
		if err != nil {
			b.Fatalf("ToSVG failed: %v", err)
		}
	}
}

func BenchmarkToPNG(b *testing.B) {
	icon, err := Generate("benchmark-test", 64)
	if err != nil {
		b.Fatalf("Generate failed: %v", err)
	}

	b.ReportAllocs()
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_, err := icon.ToPNG()
		if err != nil {
			b.Fatalf("ToPNG failed: %v", err)
		}
	}
}

// BenchmarkHashGeneration benchmarks just hash computation performance
func BenchmarkHashGeneration(b *testing.B) {
	inputs := []string{
		"user@example.com",
		"john_doe_123", 
		"550e8400-e29b-41d4-a716-446655440000",
		"abc",
		"this_is_a_very_long_identifier_that_might_be_used_for_generating_identicons",
	}
	
	for _, input := range inputs {
		b.Run(fmt.Sprintf("len_%d", len(input)), func(b *testing.B) {
			b.ResetTimer()
			b.ReportAllocs()
			
			for i := 0; i < b.N; i++ {
				_ = ComputeHash(input)
			}
		})
	}
}

// BenchmarkSVGRenderingVariousSizes benchmarks SVG rendering across different sizes
func BenchmarkSVGRenderingVariousSizes(b *testing.B) {
	sizes := []int{64, 128, 256, 512}
	icons := make(map[int]*Icon)
	
	// Pre-generate icons
	for _, size := range sizes {
		icon, err := Generate("benchmark@test.com", size)
		if err != nil {
			b.Fatalf("Failed to generate icon for size %d: %v", size, err)
		}
		icons[size] = icon
	}
	
	for _, size := range sizes {
		b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) {
			icon := icons[size]
			b.ResetTimer()
			b.ReportAllocs()
			
			for i := 0; i < b.N; i++ {
				_, err := icon.ToSVG()
				if err != nil {
					b.Fatalf("ToSVG failed for size %d: %v", size, err)
				}
			}
		})
	}
}

// BenchmarkPNGRenderingVariousSizes benchmarks PNG rendering across different sizes
func BenchmarkPNGRenderingVariousSizes(b *testing.B) {
	sizes := []int{64, 128, 256}  // Smaller range for PNG due to higher memory usage
	icons := make(map[int]*Icon)
	
	// Pre-generate icons
	for _, size := range sizes {
		icon, err := Generate("benchmark@test.com", size)
		if err != nil {
			b.Fatalf("Failed to generate icon for size %d: %v", size, err)
		}
		icons[size] = icon
	}
	
	for _, size := range sizes {
		b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) {
			icon := icons[size]
			b.ResetTimer()
			b.ReportAllocs()
			
			for i := 0; i < b.N; i++ {
				_, err := icon.ToPNG()
				if err != nil {
					b.Fatalf("ToPNG failed for size %d: %v", size, err)
				}
			}
		})
	}
}

// BenchmarkWithCustomConfig benchmarks generation with custom configuration
func BenchmarkWithCustomConfig(b *testing.B) {
	config, err := Configure(
		WithHueRestrictions([]float64{0.0, 0.33, 0.66}),
		WithColorSaturation(0.6),
		WithBackgroundColor("#ffffff"),
		WithPadding(0.1),
	)
	if err != nil {
		b.Fatalf("Configure failed: %v", err)
	}
	
	b.ResetTimer()
	b.ReportAllocs()
	
	for i := 0; i < b.N; i++ {
		_, err := ToSVG("benchmark@test.com", 128, config)
		if err != nil {
			b.Fatalf("ToSVG with config failed: %v", err)
		}
	}
}

// BenchmarkWithCustomConfigPNG benchmarks PNG generation with custom configuration
func BenchmarkWithCustomConfigPNG(b *testing.B) {
	config, err := Configure(
		WithColorSaturation(0.6),
		WithBackgroundColor("#123456"),
		WithPadding(0.1),
	)
	if err != nil {
		b.Fatalf("Configure failed: %v", err)
	}
	
	b.ResetTimer()
	b.ReportAllocs()
	
	for i := 0; i < b.N; i++ {
		_, err := ToPNG("benchmark-png-config@test.com", 128, config)
		if err != nil {
			b.Fatalf("ToPNG with config failed: %v", err)
		}
	}
}

// BenchmarkConcurrentGeneration tests performance under concurrent load
func BenchmarkConcurrentGeneration(b *testing.B) {
	b.ResetTimer()
	b.ReportAllocs()
	
	b.RunParallel(func(pb *testing.PB) {
		i := 0
		for pb.Next() {
			value := fmt.Sprintf("concurrent_user_%d@example.com", i)
			_, err := Generate(value, 128)
			if err != nil {
				b.Fatalf("Generate failed: %v", err)
			}
			i++
		}
	})
}

// BenchmarkBatchGeneration tests memory allocation patterns for batch generation
func BenchmarkBatchGeneration(b *testing.B) {
	const batchSize = 100
	
	b.SetBytes(batchSize) // Report throughput as items/sec
	b.ResetTimer()
	b.ReportAllocs()
	
	for i := 0; i < b.N; i++ {
		for j := 0; j < batchSize; j++ {
			value := fmt.Sprintf("batch_user_%d@example.com", j)
			_, err := Generate(value, 64)
			if err != nil {
				b.Fatalf("Generate failed: %v", err)
			}
		}
	}
}

// Tests for new public API functions

func TestToSVG(t *testing.T) {
	tests := []struct {
		name  string
		value interface{}
		size  int
		valid bool
	}{
		{"string input", "test@example.com", 64, true},
		{"int input", 12345, 64, true},
		{"float input", 123.45, 64, true},
		{"bool input", true, 64, true},
		{"nil input", nil, 64, true},
		{"byte slice", []byte("hello"), 64, true},
		{"zero size", "test", 0, false},
		{"negative size", "test", -10, false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			svg, err := ToSVG(tt.value, tt.size)
			
			if tt.valid {
				if err != nil {
					t.Fatalf("ToSVG failed: %v", err)
				}
				
				if svg == "" {
					t.Error("ToSVG returned empty string")
				}
				
				// Basic SVG validation
				if !strings.Contains(svg, "<svg") {
					t.Error("SVG output does not contain svg tag")
				}
				
				if !strings.Contains(svg, "</svg>") {
					t.Error("SVG output does not contain closing svg tag")
				}
			} else {
				if err == nil {
					t.Error("Expected error for invalid input")
				}
			}
		})
	}
}

func TestToPNG(t *testing.T) {
	tests := []struct {
		name  string
		value interface{}
		size  int
		valid bool
	}{
		{"string input", "test@example.com", 64, true},
		{"int input", 12345, 64, true},
		{"float input", 123.45, 64, true},
		{"bool input", false, 64, true},
		{"nil input", nil, 64, true},
		{"byte slice", []byte("hello"), 64, true},
		{"zero size", "test", 0, false},
		{"negative size", "test", -10, false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			png, err := ToPNG(tt.value, tt.size)
			
			if tt.valid {
				if err != nil {
					t.Fatalf("ToPNG failed: %v", err)
				}
				
				if len(png) == 0 {
					t.Error("ToPNG returned empty data")
				}
				
				// Basic PNG validation (check PNG signature)
				if len(png) < 8 || string(png[1:4]) != "PNG" {
					t.Error("PNG output does not have valid PNG signature")
				}
			} else {
				if err == nil {
					t.Error("Expected error for invalid input")
				}
			}
		})
	}
}

func TestToHash(t *testing.T) {
	tests := []struct {
		name     string
		value    interface{}
		expected string
	}{
		{"string", "test", ComputeHash("test")},
		{"int", 123, ComputeHash("123")},
		{"float", 123.45, ComputeHash("123.45")},
		{"bool true", true, ComputeHash("true")},
		{"bool false", false, ComputeHash("false")},
		{"nil", nil, ComputeHash("")},
		{"byte slice", []byte("hello"), ComputeHash("hello")},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			hash := ToHash(tt.value)
			
			if hash != tt.expected {
				t.Errorf("ToHash(%v) = %s, expected %s", tt.value, hash, tt.expected)
			}
			
			// Hash should be non-empty and valid hex
			if hash == "" {
				t.Error("ToHash returned empty string")
			}
			
			// Should be valid hex characters
			for _, c := range hash {
				if !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')) {
					t.Errorf("Hash contains invalid character: %c", c)
					break
				}
			}
		})
	}
}

func TestToSVGWithConfig(t *testing.T) {
	value := "config-test"
	size := 64
	
	// Test with custom configuration
	config, err := Configure(
		WithHueRestrictions([]float64{120, 240}), // Blue/green hues
		WithColorSaturation(0.8),
		WithBackgroundColor("#ffffff"),
		WithPadding(0.1),
	)
	if err != nil {
		t.Fatalf("Configure failed: %v", err)
	}
	
	svg, err := ToSVG(value, size, config)
	if err != nil {
		t.Fatalf("ToSVG with config failed: %v", err)
	}
	
	if svg == "" {
		t.Error("ToSVG with config returned empty string")
	}
	
	// Test that it's different from default
	defaultSvg, err := ToSVG(value, size)
	if err != nil {
		t.Fatalf("ToSVG default failed: %v", err)
	}
	
	if svg == defaultSvg {
		t.Error("SVG with config is identical to default SVG")
	}
}

func TestToPNGWithConfig(t *testing.T) {
	value := "config-test"
	size := 64
	
	// Test with custom configuration
	config, err := Configure(
		WithHueRestrictions([]float64{60, 180}), // Yellow/cyan hues
		WithColorSaturation(0.9),
		WithBackgroundColor("#000000"),
		WithPadding(0.05),
	)
	if err != nil {
		t.Fatalf("Configure failed: %v", err)
	}
	
	png, err := ToPNG(value, size, config)
	if err != nil {
		t.Fatalf("ToPNG with config failed: %v", err)
	}
	
	if len(png) == 0 {
		t.Error("ToPNG with config returned empty data")
	}
	
	// Test that it's different from default
	defaultPng, err := ToPNG(value, size)
	if err != nil {
		t.Fatalf("ToPNG default failed: %v", err)
	}
	
	if bytes.Equal(png, defaultPng) {
		t.Error("PNG with config is identical to default PNG")
	}
}

func TestPublicAPIConsistency(t *testing.T) {
	value := "consistency-test"
	size := 64
	
	// Generate with both old and new APIs
	icon, err := Generate(value, size)
	if err != nil {
		t.Fatalf("Generate failed: %v", err)
	}
	
	oldSvg, err := icon.ToSVG()
	if err != nil {
		t.Fatalf("Icon.ToSVG failed: %v", err)
	}
	
	oldPng, err := icon.ToPNG()
	if err != nil {
		t.Fatalf("Icon.ToPNG failed: %v", err)
	}
	
	newSvg, err := ToSVG(value, size)
	if err != nil {
		t.Fatalf("ToSVG failed: %v", err)
	}
	
	newPng, err := ToPNG(value, size)
	if err != nil {
		t.Fatalf("ToPNG failed: %v", err)
	}
	
	// Results should be identical
	if oldSvg != newSvg {
		t.Error("SVG output differs between old and new APIs")
	}
	
	if !bytes.Equal(oldPng, newPng) {
		t.Error("PNG output differs between old and new APIs")
	}
}

func TestTypeConversion(t *testing.T) {
	tests := []struct {
		name     string
		input    interface{}
		expected string
	}{
		{"string", "hello", "hello"},
		{"int", 42, "42"},
		{"int64", int64(42), "42"},
		{"float64", 3.14, "3.14"},
		{"bool true", true, "true"},
		{"bool false", false, "false"},
		{"nil", nil, ""},
		{"byte slice", []byte("test"), "test"},
		{"struct", struct{ Name string }{"test"}, "{test}"},
	}
	
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := convertToString(tt.input)
			if result != tt.expected {
				t.Errorf("convertToString(%v) = %s, expected %s", tt.input, result, tt.expected)
			}
		})
	}
}

// Helper function for min
func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}