package web

import (
	"fmt"
	"regexp"
	"strings"

	"gitgud.io/mike/mpv-manager/pkg/constants"
)

// Valid HWA methods
var validHWAMethods = map[string]bool{
	// Automatic selection methods
	"auto":      true,
	"auto-safe": true,

	// NVIDIA CUDA decoding
	"nvdec":      true,
	"nvdec-copy": true,

	// Video Acceleration API (Linux AMD/Intel)
	"vaapi":      true,
	"vaapi-copy": true,

	// Vulkan video decoding (cross-platform)
	"vulkan":      true,
	"vulkan-copy": true,

	// Direct3D 11 Video Acceleration (Windows)
	"d3d11va":      true,
	"d3d11va-copy": true,

	// Apple VideoToolbox (macOS)
	"videotoolbox":      true,
	"videotoolbox-copy": true,

	// Direct Rendering Manager (Linux AMD/Intel)
	"drm":      true,
	"drm-copy": true,

	// Disable hardware decoding
	"no": true,
}

// Valid MPV config keys and their value patterns
var validConfigKeys = map[string]*regexp.Regexp{
	// Hardware acceleration - supports values with ,auto fallback suffix (e.g., "nvdec-copy,auto")
	constants.ConfigKeyHardwareDecoder: regexp.MustCompile(`^(auto|auto-safe|nvdec|nvdec-copy|vaapi|vaapi-copy|vulkan|vulkan-copy|d3d11va|d3d11va-copy|videotoolbox|videotoolbox-copy|drm|drm-copy|no)(,auto)?$`),

	// Video output driver
	constants.ConfigKeyVideoOutput: regexp.MustCompile(`^[a-zA-Z0-9_-]+$`),

	// Video scaling filter
	constants.ConfigKeyScaleFilter: regexp.MustCompile(`^[a-zA-Z0-9_-]+$`),

	// Dither algorithm
	constants.ConfigKeyDitherAlgorithm: regexp.MustCompile(`^(no|ordered|error-diffusion|floyd-steinberg)$`),

	// Save position on quit
	constants.ConfigKeySavePositionOnQuit: regexp.MustCompile(`^(yes|no)$`),

	// Window border/decoration
	constants.ConfigKeyBorder: regexp.MustCompile(`^(yes|no)$`),

	// Profile/quality preset
	constants.ConfigKeyProfile: regexp.MustCompile(`^(high-quality|fast|low-latency|sw-fast)$`),

	// Screenshot settings
	constants.ConfigKeyScreenshotFormat:         regexp.MustCompile(`^(png|jpg|jpeg|webp|jxl|avif)$`),
	constants.ConfigKeyScreenshotTagColorspace:  regexp.MustCompile(`^(yes|no)$`),
	constants.ConfigKeyScreenshotHighBitDepth:   regexp.MustCompile(`^(yes|no)$`),
	constants.ConfigKeyScreenshotTemplate:       regexp.MustCompile(`^[a-zA-Z0-9_\-\.%:\s\(\)]+$`),
	constants.ConfigKeyScreenshotDirectory:      regexp.MustCompile(`^[\w\/\\\-\.~: ]+$`),
	constants.ConfigKeyScreenshotJpegQuality:    regexp.MustCompile(`^(100|[1-9]?\d)$`),
	constants.ConfigKeyScreenshotPngCompression: regexp.MustCompile(`^[0-9]$`),
	constants.ConfigKeyScreenshotWebpLossless:   regexp.MustCompile(`^(yes|no)$`),
	constants.ConfigKeyScreenshotWebpQuality:    regexp.MustCompile(`^(100|[1-9]?\d)$`),
	constants.ConfigKeyScreenshotJxlDistance:    regexp.MustCompile(`^(15\.0|([0-9]|1[0-4])(\.\d)?|[0-9]|1[0-5])$`),
	constants.ConfigKeyScreenshotAvifEncoder:    regexp.MustCompile(`^[a-zA-Z0-9\-]+$`),
	constants.ConfigKeyScreenshotAvifPixfmt:     regexp.MustCompile(`^[a-zA-Z0-9_\-]*$`),
	constants.ConfigKeyScreenshotAvifOpts:       regexp.MustCompile(`^[a-zA-Z0-9_=,\-\.]+$`),
}

// ValidateConfigValue validates MPV config key-value pairs
func ValidateConfigValue(key, value string) error {
	// Check if key is valid
	regex, ok := validConfigKeys[key]
	if !ok {
		return fmt.Errorf("invalid config key: %s", key)
	}

	// Check if value matches the pattern for this key
	if !regex.MatchString(value) {
		return fmt.Errorf("invalid value '%s' for key '%s'", value, key)
	}

	return nil
}

// ValidateMethodID validates that a method ID is valid
func ValidateMethodID(methodID string) error {
	// Check against all known method IDs
	_, ok := constants.MethodIDToName[methodID]
	if !ok {
		return fmt.Errorf("invalid method ID: %s", methodID)
	}

	return nil
}

// ValidateAppName validates app names (alphanumeric with spaces and hyphens)
func ValidateAppName(appName string) error {
	if appName == "" {
		return fmt.Errorf("app name cannot be empty")
	}

	// Allow alphanumeric, spaces, hyphens, and periods
	matched, err := regexp.MatchString(`^[a-zA-Z0-9\s\-\.]+$`, appName)
	if err != nil {
		return fmt.Errorf("failed to validate app name: %v", err)
	}

	if !matched {
		return fmt.Errorf("invalid app name: %s (must contain only letters, numbers, spaces, hyphens, or periods)", appName)
	}

	// Prevent excessively long names
	if len(appName) > 100 {
		return fmt.Errorf("app name too long: %s (maximum 100 characters)", appName)
	}

	return nil
}

// NormalizeAppType normalizes legacy app types to standard values
func NormalizeAppType(appType string) string {
	// Already standard types
	if appType == "app" || appType == "frontend" {
		return appType
	}

	// Legacy types - normalize to "app"
	legacyTypes := map[string]string{
		"flatpak":         "app",
		"package-manager": "app",
		"executable":      "app",
		"portable":        "app",
		// Old app names (pre-Feb 2026) - normalize appropriately
		"MPV":       "app",
		"IINA":      "app",
		"MPC-QT":    "app",
		"Celluloid": "frontend",
	}

	if normalized, ok := legacyTypes[appType]; ok {
		return normalized
	}

	// Unknown type - return as-is for validation to catch it
	return appType
}

// ValidateAppType validates app type (app, frontend, or legacy types)
func ValidateAppType(appType string) error {
	// Check for empty type
	if appType == "" {
		return fmt.Errorf("invalid app type")
	}

	// Normalized type will always be valid
	normalized := NormalizeAppType(appType)
	if normalized != "app" && normalized != "frontend" {
		return fmt.Errorf("must be 'app' or 'frontend'")
	}

	return nil
}

// ValidateLanguageCode validates language code format (e.g., "en", "en-US", "zh-CN")
func ValidateLanguageCode(code string) error {
	if code == "" {
		// Empty language code is valid (means no preference)
		return nil
	}

	// Accept basic language code (e.g., "en", "zh")
	if matched, _ := regexp.MatchString(`^[a-z]{2}$`, code); matched {
		return nil
	}

	// Accept locale with country (e.g., "en-US", "zh-CN")
	if matched, _ := regexp.MatchString(`^[a-z]{2}-[A-Z]{2}$`, code); matched {
		return nil
	}

	return fmt.Errorf("invalid language code: %s (format must be 'en' or 'en-US')", code)
}

// ValidateHWAValue validates hardware acceleration method
// Accepts both simple values (e.g., "nvdec") and values with fallback (e.g., "nvdec,auto")
func ValidateHWAValue(method string) error {
	if method == "" {
		// Empty value is valid (no change)
		return nil
	}

	// Check for ,auto suffix and strip it for validation
	baseMethod := method
	if strings.HasSuffix(method, ",auto") {
		baseMethod = strings.TrimSuffix(method, ",auto")
	}

	if !validHWAMethods[baseMethod] {
		return fmt.Errorf("invalid hardware acceleration method: %s", method)
	}

	return nil
}

// ValidateInstallMethod validates install method ID and ensures it's valid for current platform
func ValidateInstallMethod(methodID string, isWindows, isDarwin, isLinux bool) error {
	// Check for component method IDs (UOSC, ModernZ, FFmpeg) - valid on all platforms
	switch methodID {
	case constants.MethodUOSC, constants.MethodModernZ:
		// UI components are valid on all platforms
		return nil
	case constants.MethodFFmpeg:
		// FFmpeg is only valid on Windows (bundled with MPV)
		if !isWindows {
			return fmt.Errorf("method %s is only available on Windows", methodID)
		}
		return nil
	}

	// First check if method ID is valid
	if err := ValidateMethodID(methodID); err != nil {
		return err
	}

	// Check platform compatibility
	switch methodID {
	case constants.MethodMPVApp, constants.MethodIINA:
		if !isDarwin {
			return fmt.Errorf("method %s is only available on macOS", methodID)
		}
	case constants.MethodMPCQT:
		if !isWindows {
			return fmt.Errorf("method %s is only available on Windows", methodID)
		}
	case constants.MethodMPVBinary, constants.MethodMPVBinaryV3:
		if isDarwin {
			return fmt.Errorf("method %s is not available on macOS (use %s instead)", methodID, constants.MethodMPVApp)
		}
	}

	// Check package manager methods
	pmMethods := []string{
		constants.MethodMPVBrew,
		constants.MethodMPVFlatpak, constants.MethodCelluloidFlatpak,
		constants.MethodMPVPackage, constants.MethodCelluloidPackage,
	}

	for _, pmMethod := range pmMethods {
		if methodID == pmMethod && isWindows {
			return fmt.Errorf("method %s is not available on Windows", methodID)
		}
	}

	return nil
}

// ValidateScreenshotQuality validates quality values (0-100)
func ValidateScreenshotQuality(quality string) error {
	if quality == "" {
		return nil // Empty is valid (will use default)
	}
	matched, _ := regexp.MatchString(`^(100|[1-9]?\d)$`, quality)
	if !matched {
		return fmt.Errorf("invalid quality value: %s (must be 0-100)", quality)
	}
	return nil
}

// ValidateScreenshotCompression validates PNG compression (0-9)
func ValidateScreenshotCompression(compression string) error {
	if compression == "" {
		return nil // Empty is valid (will use default)
	}
	matched, _ := regexp.MatchString(`^[0-9]$`, compression)
	if !matched {
		return fmt.Errorf("invalid compression value: %s (must be 0-9)", compression)
	}
	return nil
}

// validHotkeyPresets is the set of accepted preset names.
var validHotkeyPresets = map[string]bool{
	"default": true,
	"vlc":     true,
	"mpc-hc":  true,
}

// ValidateHotkeyPreset validates that a hotkey preset name is recognized.
func ValidateHotkeyPreset(preset string) error {
	if preset == "" {
		return fmt.Errorf("preset name cannot be empty")
	}
	if !validHotkeyPresets[preset] {
		return fmt.Errorf("invalid hotkey preset: %s (must be one of: default, vlc, mpc-hc)", preset)
	}
	return nil
}

// ValidateScreenshotJxlDistance validates JXL distance (0.0-15.0)
func ValidateScreenshotJxlDistance(distance string) error {
	if distance == "" {
		return nil // Empty is valid (will use default)
	}
	matched, _ := regexp.MatchString(`^(15\.0|([0-9]|1[0-4])(\.\d)?|[0-9]|1[0-5])$`, distance)
	if !matched {
		return fmt.Errorf("invalid JXL distance: %s (must be 0.0-15.0, note: 0.0 is lossless)", distance)
	}
	return nil
}
