package jdenticon

import (
	"fmt"
	"regexp"

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

// hexColorRegex validates hex color strings in #RGB or #RRGGBB format.
// It's compiled once at package initialization for efficiency.
var hexColorRegex = regexp.MustCompile(`^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$`)

// isValidHexColor checks if the given string is a valid hex color code.
// It accepts both #RGB and #RRGGBB formats, case-insensitive.
// Returns true for empty strings, which are treated as "transparent background".
func isValidHexColor(color string) bool {
	if color == "" {
		return true // Empty string means transparent background
	}
	return hexColorRegex.MatchString(color)
}

// Config holds the configuration options for identicon generation.
// This is the public API configuration that wraps the internal engine config.
type Config struct {
	// Color configuration
	ColorSaturation         float64    // Saturation for colored shapes (0.0-1.0)
	GrayscaleSaturation     float64    // Saturation for grayscale shapes (0.0-1.0)
	ColorLightnessRange     [2]float64 // Lightness range for colored shapes [min, max] (0.0-1.0)
	GrayscaleLightnessRange [2]float64 // Lightness range for grayscale shapes [min, max] (0.0-1.0)

	// Hue restrictions - if specified, only these hues will be used
	HueRestrictions []float64 // Specific hue values in degrees (0-360)

	// Layout configuration
	Padding         float64 // Padding as percentage of icon size (0.0-0.5)
	BackgroundColor string  // Background color in hex format (e.g., "#ffffff") or empty for transparent

	// PNG rendering configuration
	PNGSupersampling int // Supersampling factor for PNG rendering (1-16)

	// Security limits for DoS protection.
	//
	// MaxIconSize sets the maximum icon dimension in pixels to prevent memory exhaustion.
	// - 0 (default): Use library default of 4096 pixels (64MB max memory)
	// - Positive value: Use custom pixel limit
	// - -1: Disable size limits entirely (use with caution in server environments)
	MaxIconSize int

	// MaxInputLength sets the maximum input string length in bytes to prevent hash DoS.
	// - 0 (default): Use library default of 1MB
	// - Positive value: Use custom byte limit
	// - -1: Disable input length limits entirely
	MaxInputLength int

	// MaxComplexity sets the maximum geometric complexity score to prevent resource exhaustion.
	// This is calculated as the sum of complexity scores for all shapes in an identicon.
	// - 0 (default): Use library default complexity limit
	// - Positive value: Use custom complexity limit
	// - -1: Disable complexity limits entirely (use with caution)
	MaxComplexity int
}

// DefaultConfig returns a Config with sensible default values.
func DefaultConfig() Config {
	return Config{
		ColorSaturation:         0.5,
		GrayscaleSaturation:     0.0,
		ColorLightnessRange:     [2]float64{0.4, 0.8},
		GrayscaleLightnessRange: [2]float64{0.3, 0.9},
		HueRestrictions:         nil, // No restrictions by default
		Padding:                 0.08,
		BackgroundColor:         "", // Transparent background
		PNGSupersampling:        8,
	}
}

// Validate checks if the configuration values are valid.
func (c *Config) Validate() error {
	// Validate saturation ranges
	if c.ColorSaturation < 0.0 || c.ColorSaturation > 1.0 {
		return fmt.Errorf("color saturation must be between 0.0 and 1.0, got %f", c.ColorSaturation)
	}
	if c.GrayscaleSaturation < 0.0 || c.GrayscaleSaturation > 1.0 {
		return fmt.Errorf("grayscale saturation must be between 0.0 and 1.0, got %f", c.GrayscaleSaturation)
	}

	// Validate lightness ranges
	if c.ColorLightnessRange[0] < 0.0 || c.ColorLightnessRange[0] > 1.0 ||
		c.ColorLightnessRange[1] < 0.0 || c.ColorLightnessRange[1] > 1.0 {
		return fmt.Errorf("color lightness range values must be between 0.0 and 1.0, got [%f, %f]",
			c.ColorLightnessRange[0], c.ColorLightnessRange[1])
	}
	if c.ColorLightnessRange[0] >= c.ColorLightnessRange[1] {
		return fmt.Errorf("color lightness range min must be less than max, got [%f, %f]",
			c.ColorLightnessRange[0], c.ColorLightnessRange[1])
	}

	if c.GrayscaleLightnessRange[0] < 0.0 || c.GrayscaleLightnessRange[0] > 1.0 ||
		c.GrayscaleLightnessRange[1] < 0.0 || c.GrayscaleLightnessRange[1] > 1.0 {
		return fmt.Errorf("grayscale lightness range values must be between 0.0 and 1.0, got [%f, %f]",
			c.GrayscaleLightnessRange[0], c.GrayscaleLightnessRange[1])
	}
	if c.GrayscaleLightnessRange[0] >= c.GrayscaleLightnessRange[1] {
		return fmt.Errorf("grayscale lightness range min must be less than max, got [%f, %f]",
			c.GrayscaleLightnessRange[0], c.GrayscaleLightnessRange[1])
	}

	// Validate padding
	if c.Padding < 0.0 || c.Padding > 0.5 {
		return fmt.Errorf("padding must be between 0.0 and 0.5, got %f", c.Padding)
	}

	// Validate hue restrictions
	for i, hue := range c.HueRestrictions {
		if hue < 0.0 || hue > 360.0 {
			return fmt.Errorf("hue restriction at index %d must be between 0.0 and 360.0, got %f", i, hue)
		}
	}

	// Validate PNG supersampling
	if c.PNGSupersampling < 1 || c.PNGSupersampling > 16 {
		return fmt.Errorf("PNG supersampling must be between 1 and 16, got %d", c.PNGSupersampling)
	}

	// Validate background color format
	if !isValidHexColor(c.BackgroundColor) {
		return NewErrInvalidInput("background_color", c.BackgroundColor, "must be a valid hex color in #RGB or #RRGGBB format")
	}

	return nil
}

// effectiveMaxIconSize resolves the configured icon size limit, applying the default if necessary.
// Returns -1 if the limit is disabled.
func (c *Config) effectiveMaxIconSize() int {
	if c.MaxIconSize < 0 {
		return -1 // Disabled
	}
	if c.MaxIconSize == 0 {
		return constants.DefaultMaxIconSize
	}
	return c.MaxIconSize
}

// effectiveMaxInputLength resolves the configured input length limit, applying the default if necessary.
// Returns -1 if the limit is disabled.
func (c *Config) effectiveMaxInputLength() int {
	if c.MaxInputLength < 0 {
		return -1 // Disabled
	}
	if c.MaxInputLength == 0 {
		return constants.DefaultMaxInputLength
	}
	return c.MaxInputLength
}

// effectiveMaxComplexity resolves the configured complexity limit, applying the default if necessary.
// Returns -1 if the limit is disabled.
func (c *Config) effectiveMaxComplexity() int {
	if c.MaxComplexity < 0 {
		return -1 // Disabled
	}
	if c.MaxComplexity == 0 {
		return constants.DefaultMaxComplexity
	}
	return c.MaxComplexity
}

// toEngineColorConfig converts the public Config to the internal engine.ColorConfig.
func (c *Config) toEngineColorConfig() (engine.ColorConfig, error) {
	if err := c.Validate(); err != nil {
		return engine.ColorConfig{}, fmt.Errorf("invalid configuration: %w", err)
	}

	colorConfig := engine.ColorConfig{
		ColorSaturation:     c.ColorSaturation,
		GrayscaleSaturation: c.GrayscaleSaturation,
		ColorLightness: engine.LightnessRange{
			Min: c.ColorLightnessRange[0],
			Max: c.ColorLightnessRange[1],
		},
		GrayscaleLightness: engine.LightnessRange{
			Min: c.GrayscaleLightnessRange[0],
			Max: c.GrayscaleLightnessRange[1],
		},
		Hues:        c.HueRestrictions,
		BackColor:   nil, // Will be set below if background color is specified
		IconPadding: c.Padding,
	}

	// Handle background color conversion from hex string to engine.Color
	if c.BackgroundColor != "" {
		// Validation is already handled by c.Validate() above.
		bgColor, err := engine.ParseHexColorToEngine(c.BackgroundColor)
		if err != nil {
			return colorConfig, fmt.Errorf("failed to parse background color: %w", err)
		}
		colorConfig.BackColor = &bgColor
	}

	return colorConfig, nil
}

// ConfigOption represents a functional option for configuring identicon generation.
type ConfigOption func(*Config) error

// Configure applies configuration options to create a new Config.
func Configure(options ...ConfigOption) (Config, error) {
	config := DefaultConfig()

	for _, option := range options {
		if err := option(&config); err != nil {
			return config, fmt.Errorf("configuration option failed: %w", err)
		}
	}

	if err := config.Validate(); err != nil {
		return config, fmt.Errorf("invalid configuration after applying options: %w", err)
	}

	return config, nil
}

// WithColorSaturation sets the color saturation for colored shapes.
func WithColorSaturation(saturation float64) ConfigOption {
	return func(c *Config) error {
		if saturation < 0.0 || saturation > 1.0 {
			return fmt.Errorf("color saturation must be between 0.0 and 1.0, got %f", saturation)
		}
		c.ColorSaturation = saturation
		return nil
	}
}

// WithGrayscaleSaturation sets the saturation for grayscale shapes.
func WithGrayscaleSaturation(saturation float64) ConfigOption {
	return func(c *Config) error {
		if saturation < 0.0 || saturation > 1.0 {
			return fmt.Errorf("grayscale saturation must be between 0.0 and 1.0, got %f", saturation)
		}
		c.GrayscaleSaturation = saturation
		return nil
	}
}

// WithPadding sets the padding as a percentage of the icon size.
func WithPadding(padding float64) ConfigOption {
	return func(c *Config) error {
		if padding < 0.0 || padding > 0.5 {
			return fmt.Errorf("padding must be between 0.0 and 0.5, got %f", padding)
		}
		c.Padding = padding
		return nil
	}
}

// WithBackgroundColor sets the background color (hex format like "#ffffff").
// The color must be in #RGB or #RRGGBB format. An empty string means transparent background.
func WithBackgroundColor(color string) ConfigOption {
	return func(c *Config) error {
		if !isValidHexColor(color) {
			return NewErrInvalidInput("background_color", color, "must be a valid hex color in #RGB or #RRGGBB format")
		}
		c.BackgroundColor = color
		return nil
	}
}

// WithColorLightnessRange sets the lightness range for colored shapes.
func WithColorLightnessRange(min, max float64) ConfigOption {
	return func(c *Config) error {
		if min < 0.0 || min > 1.0 || max < 0.0 || max > 1.0 {
			return fmt.Errorf("lightness values must be between 0.0 and 1.0, got min=%f, max=%f", min, max)
		}
		if min >= max {
			return fmt.Errorf("lightness min must be less than max, got min=%f, max=%f", min, max)
		}
		c.ColorLightnessRange = [2]float64{min, max}
		return nil
	}
}

// WithGrayscaleLightnessRange sets the lightness range for grayscale shapes.
func WithGrayscaleLightnessRange(min, max float64) ConfigOption {
	return func(c *Config) error {
		if min < 0.0 || min > 1.0 || max < 0.0 || max > 1.0 {
			return fmt.Errorf("lightness values must be between 0.0 and 1.0, got min=%f, max=%f", min, max)
		}
		if min >= max {
			return fmt.Errorf("lightness min must be less than max, got min=%f, max=%f", min, max)
		}
		c.GrayscaleLightnessRange = [2]float64{min, max}
		return nil
	}
}

// WithHueRestrictions restricts hues to specific values in degrees (0-360).
func WithHueRestrictions(hues []float64) ConfigOption {
	return func(c *Config) error {
		for i, hue := range hues {
			if hue < 0.0 || hue > 360.0 {
				return fmt.Errorf("hue at index %d must be between 0.0 and 360.0, got %f", i, hue)
			}
		}
		c.HueRestrictions = make([]float64, len(hues))
		copy(c.HueRestrictions, hues)
		return nil
	}
}

// WithPNGSupersampling sets the PNG supersampling factor (1-16).
func WithPNGSupersampling(factor int) ConfigOption {
	return func(c *Config) error {
		if factor < 1 || factor > 16 {
			return fmt.Errorf("PNG supersampling must be between 1 and 16, got %d", factor)
		}
		c.PNGSupersampling = factor
		return nil
	}
}

// WithMaxComplexity sets the maximum geometric complexity limit for resource protection.
// Use -1 to disable complexity limits entirely (use with caution).
func WithMaxComplexity(maxComplexity int) ConfigOption {
	return func(c *Config) error {
		if maxComplexity < -1 {
			return fmt.Errorf("max complexity must be >= -1, got %d", maxComplexity)
		}
		c.MaxComplexity = maxComplexity
		return nil
	}
}

// WithMaxIconSize sets the maximum icon dimension in pixels for DoS protection.
// Use 0 for the library default (4096 pixels), or -1 to disable limits entirely.
func WithMaxIconSize(maxSize int) ConfigOption {
	return func(c *Config) error {
		if maxSize < -1 {
			return fmt.Errorf("max icon size must be >= -1, got %d", maxSize)
		}
		c.MaxIconSize = maxSize
		return nil
	}
}

// WithMaxInputLength sets the maximum input string length in bytes for DoS protection.
// Use 0 for the library default (1MB), or -1 to disable limits entirely.
func WithMaxInputLength(maxLength int) ConfigOption {
	return func(c *Config) error {
		if maxLength < -1 {
			return fmt.Errorf("max input length must be >= -1, got %d", maxLength)
		}
		c.MaxInputLength = maxLength
		return nil
	}
}
