package jdenticon

import (
	"context"
	"errors"
	"strings"
	"testing"

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

// TestDoSProtection_InputLength tests protection against large input strings.
func TestDoSProtection_InputLength(t *testing.T) {
	tests := []struct {
		name        string
		inputLength int
		config      Config
		expectError bool
		errorType   string
	}{
		{
			name:        "normal input with default config",
			inputLength: 100,
			config:      DefaultConfig(),
			expectError: false,
		},
		{
			name:        "maximum allowed input with default config",
			inputLength: constants.DefaultMaxInputLength,
			config:      DefaultConfig(),
			expectError: false,
		},
		{
			name:        "oversized input with default config",
			inputLength: constants.DefaultMaxInputLength + 1,
			config:      DefaultConfig(),
			expectError: true,
			errorType:   "*jdenticon.ErrValueTooLarge",
		},
		{
			name:        "oversized input with custom smaller limit",
			inputLength: 1000,
			config: func() Config {
				c := DefaultConfig()
				c.MaxInputLength = 500
				return c
			}(),
			expectError: true,
			errorType:   "*jdenticon.ErrValueTooLarge",
		},
		{
			name:        "oversized input with disabled limit",
			inputLength: constants.DefaultMaxInputLength + 1000,
			config: func() Config {
				c := DefaultConfig()
				c.MaxInputLength = -1 // Disabled
				return c
			}(),
			expectError: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Create input string of specified length
			input := strings.Repeat("a", tt.inputLength)

			// Test ToSVGWithConfig
			_, err := ToSVGWithConfig(context.Background(), input, 100, tt.config)
			if tt.expectError {
				if err == nil {
					t.Errorf("ToSVGWithConfig: expected error but got none")
					return
				}

				// Check error type if specified
				if tt.errorType != "" {
					var valueErr *ErrValueTooLarge
					if !errors.As(err, &valueErr) {
						t.Errorf("ToSVGWithConfig: expected error type %s, got %T", tt.errorType, err)
					} else if valueErr.ParameterName != "InputLength" {
						t.Errorf("ToSVGWithConfig: expected InputLength error, got %s", valueErr.ParameterName)
					}
				}
			} else if err != nil {
				t.Errorf("ToSVGWithConfig: unexpected error: %v", err)
			}

			// Test ToPNGWithConfig
			_, err = ToPNGWithConfig(context.Background(), input, 100, tt.config)
			if tt.expectError {
				if err == nil {
					t.Errorf("ToPNGWithConfig: expected error but got none")
				}
			} else if err != nil {
				t.Errorf("ToPNGWithConfig: unexpected error: %v", err)
			}
		})
	}
}

// TestDoSProtection_IconSize tests protection against large icon sizes.
func TestDoSProtection_IconSize(t *testing.T) {
	tests := []struct {
		name        string
		size        int
		config      Config
		expectError bool
		errorType   string
	}{
		{
			name:        "normal size with default config",
			size:        256,
			config:      DefaultConfig(),
			expectError: false,
		},
		{
			name:        "maximum allowed size with default config",
			size:        constants.DefaultMaxIconSize,
			config:      DefaultConfig(),
			expectError: false,
		},
		{
			name:        "oversized icon with default config",
			size:        constants.DefaultMaxIconSize + 1,
			config:      DefaultConfig(),
			expectError: true,
			errorType:   "*jdenticon.ErrValueTooLarge",
		},
		{
			name: "oversized icon with custom smaller limit",
			size: 2000,
			config: func() Config {
				c := DefaultConfig()
				c.MaxIconSize = 1000
				return c
			}(),
			expectError: true,
			errorType:   "*jdenticon.ErrValueTooLarge",
		},
		{
			name: "oversized icon with disabled limit",
			size: constants.DefaultMaxIconSize + 1000,
			config: func() Config {
				c := DefaultConfig()
				c.MaxIconSize = -1 // Disabled
				return c
			}(),
			expectError: false,
		},
		{
			name:        "zero size",
			size:        0,
			config:      DefaultConfig(),
			expectError: true,
			errorType:   "jdenticon.ErrInvalidSize",
		},
		{
			name:        "negative size",
			size:        -100,
			config:      DefaultConfig(),
			expectError: true,
			errorType:   "jdenticon.ErrInvalidSize",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			input := "test"

			// Test ToSVGWithConfig
			_, err := ToSVGWithConfig(context.Background(), input, tt.size, tt.config)
			if tt.expectError {
				if err == nil {
					t.Errorf("ToSVGWithConfig: expected error but got none")
					return
				}

				// Check error type if specified
				if tt.errorType != "" {
					switch tt.errorType {
					case "*jdenticon.ErrValueTooLarge":
						var valueErr *ErrValueTooLarge
						if !errors.As(err, &valueErr) {
							t.Errorf("ToSVGWithConfig: expected error type %s, got %T", tt.errorType, err)
						} else if valueErr.ParameterName != "IconSize" {
							t.Errorf("ToSVGWithConfig: expected IconSize error, got %s", valueErr.ParameterName)
						}
					case "jdenticon.ErrInvalidSize":
						var sizeErr ErrInvalidSize
						if !errors.As(err, &sizeErr) {
							t.Errorf("ToSVGWithConfig: expected error type %s, got %T", tt.errorType, err)
						}
					}
				}
			} else if err != nil {
				t.Errorf("ToSVGWithConfig: unexpected error: %v", err)
			}
		})
	}
}

// TestDoSProtection_PNGEffectiveSize tests protection against PNG supersampling creating oversized effective images.
func TestDoSProtection_PNGEffectiveSize(t *testing.T) {
	tests := []struct {
		name          string
		size          int
		supersampling int
		config        Config
		expectError   bool
		errorType     string
	}{
		{
			name:          "normal PNG with default supersampling",
			size:          512,
			supersampling: 8, // 512 * 8 = 4096 (exactly at limit)
			expectError:   false,
		},
		{
			name:          "oversized effective PNG size",
			size:          1024,
			supersampling: 8, // 1024 * 8 = 8192 (exceeds 4096 limit)
			expectError:   true,
			errorType:     "*jdenticon.ErrEffectiveSizeTooLarge",
		},
		{
			name:          "large PNG with low supersampling (within limit)",
			size:          2048,
			supersampling: 2, // 2048 * 2 = 4096 (exactly at limit)
			expectError:   false,
		},
		{
			name:          "maximum PNG with 1x supersampling",
			size:          4096,
			supersampling: 1, // 4096 * 1 = 4096 (exactly at limit)
			expectError:   false,
		},
		{
			name:          "PNG with disabled size limit",
			size:          2000,
			supersampling: 10, // Would exceed default limit but should be allowed
			config: func() Config {
				c := DefaultConfig()
				c.MaxIconSize = -1 // Disabled
				return c
			}(),
			expectError: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			config := tt.config
			if config.MaxIconSize == 0 && config.MaxInputLength == 0 {
				// Use default config if not specified
				config = DefaultConfig()
			}
			config.PNGSupersampling = tt.supersampling

			input := "test"

			_, err := ToPNGWithConfig(context.Background(), input, tt.size, config)
			if tt.expectError {
				if err == nil {
					t.Errorf("ToPNGWithConfig: expected error but got none")
					return
				}

				// Check error type if specified
				if tt.errorType == "*jdenticon.ErrEffectiveSizeTooLarge" {
					var effectiveErr *ErrEffectiveSizeTooLarge
					if !errors.As(err, &effectiveErr) {
						t.Errorf("ToPNGWithConfig: expected error type %s, got %T", tt.errorType, err)
					} else {
						expectedEffective := tt.size * tt.supersampling
						if effectiveErr.Actual != expectedEffective {
							t.Errorf("ToPNGWithConfig: expected effective size %d, got %d",
								expectedEffective, effectiveErr.Actual)
						}
						if effectiveErr.Size != tt.size {
							t.Errorf("ToPNGWithConfig: expected size %d, got %d",
								tt.size, effectiveErr.Size)
						}
						if effectiveErr.Supersampling != tt.supersampling {
							t.Errorf("ToPNGWithConfig: expected supersampling %d, got %d",
								tt.supersampling, effectiveErr.Supersampling)
						}
					}
				}
			} else if err != nil {
				t.Errorf("ToPNGWithConfig: unexpected error: %v", err)
			}
		})
	}
}

// TestDoSProtection_DynamicSupersampling tests the dynamic supersampling feature in ToPNG.
func TestDoSProtection_DynamicSupersampling(t *testing.T) {
	tests := []struct {
		name          string
		size          int
		expectError   bool
		expectedMaxSS int // Expected maximum supersampling that should be used
	}{
		{
			name:          "small size uses full supersampling",
			size:          256,
			expectError:   false,
			expectedMaxSS: 8, // 256 * 8 = 2048 < 4096, so full supersampling
		},
		{
			name:          "medium size uses reduced supersampling",
			size:          1024,
			expectError:   false,
			expectedMaxSS: 4, // 1024 * 4 = 4096, reduced from default 8
		},
		{
			name:          "large size uses minimal supersampling",
			size:          4096,
			expectError:   false,
			expectedMaxSS: 1, // 4096 * 1 = 4096, minimal supersampling
		},
		{
			name:        "oversized even with minimal supersampling",
			size:        4097,
			expectError: true, // Even 4097 * 1 = 4097 > 4096
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			input := "test"

			data, err := ToPNG(context.Background(), input, tt.size)
			if tt.expectError {
				if err == nil {
					t.Errorf("ToPNG: expected error but got none")
				}
				return
			}

			if err != nil {
				t.Errorf("ToPNG: unexpected error: %v", err)
				return
			}

			if len(data) == 0 {
				t.Errorf("ToPNG: expected PNG data but got empty result")
			}
		})
	}
}

// TestDoSProtection_EmptyInput tests protection against empty input strings.
func TestDoSProtection_EmptyInput(t *testing.T) {
	config := DefaultConfig()

	_, err := ToSVGWithConfig(context.Background(), "", 100, config)
	if err == nil {
		t.Errorf("ToSVGWithConfig: expected error for empty input but got none")
		return
	}

	var inputErr *ErrInvalidInput
	if !errors.As(err, &inputErr) {
		t.Errorf("ToSVGWithConfig: expected ErrInvalidInput, got %T", err)
	} else if inputErr.Field != "input" {
		t.Errorf("ToSVGWithConfig: expected input field error, got %s", inputErr.Field)
	}
}

// TestDoSProtection_ConfigurableLimits tests that the configurable limits work as expected.
func TestDoSProtection_ConfigurableLimits(t *testing.T) {
	// Test custom limits
	customConfig := DefaultConfig()
	customConfig.MaxIconSize = 1000     // Much smaller than default
	customConfig.MaxInputLength = 10000 // Much smaller than default
	customConfig.PNGSupersampling = 4

	// This should work with custom config
	_, err := ToSVGWithConfig(context.Background(), "test", 500, customConfig)
	if err != nil {
		t.Errorf("ToSVGWithConfig with custom config: unexpected error: %v", err)
	}

	// This should fail with custom config (size too large)
	_, err = ToSVGWithConfig(context.Background(), "test", 1001, customConfig)
	if err == nil {
		t.Errorf("ToSVGWithConfig with custom config: expected error for oversized icon but got none")
	}

	// Test disabled limits
	noLimitsConfig := DefaultConfig()
	noLimitsConfig.MaxIconSize = -1    // Disabled
	noLimitsConfig.MaxInputLength = -1 // Disabled
	noLimitsConfig.PNGSupersampling = 1

	// This should work even with very large size (disabled limits)
	largeInput := strings.Repeat("x", constants.DefaultMaxInputLength+1000)
	_, err = ToSVGWithConfig(context.Background(), largeInput, constants.DefaultMaxIconSize+1000, noLimitsConfig)
	if err != nil {
		t.Errorf("ToSVGWithConfig with disabled limits: unexpected error: %v", err)
	}
}

// TestDoSProtection_GeneratorConsistency tests that Generator API respects configured limits.
func TestDoSProtection_GeneratorConsistency(t *testing.T) {
	// Test generator with custom limits
	customConfig := DefaultConfig()
	customConfig.MaxIconSize = 1000
	customConfig.MaxInputLength = 100

	generator, err := NewGeneratorWithConfig(customConfig, 10)
	if err != nil {
		t.Fatalf("NewGeneratorWithConfig: unexpected error: %v", err)
	}

	// This should work within limits
	_, err = generator.Generate(context.Background(), "test", 500)
	if err != nil {
		t.Errorf("Generator.Generate within limits: unexpected error: %v", err)
	}

	// This should fail due to size limit
	_, err = generator.Generate(context.Background(), "test", 1001)
	if err == nil {
		t.Errorf("Generator.Generate with oversized icon: expected error but got none")
	} else {
		var valueErr *ErrValueTooLarge
		if !errors.As(err, &valueErr) {
			t.Errorf("Generator.Generate: expected ErrValueTooLarge, got %T", err)
		} else if valueErr.ParameterName != "IconSize" {
			t.Errorf("Generator.Generate: expected IconSize error, got %s", valueErr.ParameterName)
		}
	}

	// This should fail due to input length limit
	longInput := strings.Repeat("a", 101)
	_, err = generator.Generate(context.Background(), longInput, 100)
	if err == nil {
		t.Errorf("Generator.Generate with long input: expected error but got none")
	} else {
		var valueErr *ErrValueTooLarge
		if !errors.As(err, &valueErr) {
			t.Errorf("Generator.Generate: expected ErrValueTooLarge, got %T", err)
		} else if valueErr.ParameterName != "InputLength" {
			t.Errorf("Generator.Generate: expected InputLength error, got %s", valueErr.ParameterName)
		}
	}

	// Test generator with default limits
	defaultGenerator, err := NewGenerator()
	if err != nil {
		t.Fatalf("NewGenerator: unexpected error: %v", err)
	}

	// Should work with normal inputs
	_, err = defaultGenerator.Generate(context.Background(), "test", 256)
	if err != nil {
		t.Errorf("Default generator: unexpected error: %v", err)
	}

	// Should fail with oversized input
	_, err = defaultGenerator.Generate(context.Background(), "test", constants.DefaultMaxIconSize+1)
	if err == nil {
		t.Errorf("Default generator with oversized icon: expected error but got none")
	}
}
