package installer

import (
	"bufio"
	"fmt"
	"os"
	"strings"

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

// UserSettings contains settings that should be preserved when overwriting mpv.conf
type UserSettings struct {
	// Language settings
	AudioLanguages    []string
	SubtitleLanguages []string

	// Hardware acceleration
	HardwareDecoder string

	// Additional settings to preserve (can be expanded as needed)
	VideoOutput     string
	ScaleFilter     string
	DitherAlgorithm string
}

// PreserveUserSettings reads the current mpv.conf and extracts user-configurable settings
// that should be preserved when overwriting the config file
func PreserveUserSettings(configPath string, cr *CommandRunner) (*UserSettings, error) {
	if cr != nil {
		cr.outputChan <- "Reading current user settings from mpv.conf..."
	}

	// Check if config file exists
	if _, err := os.Stat(configPath); os.IsNotExist(err) {
		if cr != nil {
			cr.outputChan <- "No existing mpv.conf found, will use defaults"
		}
		return &UserSettings{}, nil
	}

	// Open and read the config file
	file, err := os.Open(configPath)
	if err != nil {
		return nil, fmt.Errorf("failed to open config file: %w", err)
	}
	defer file.Close()

	settings := &UserSettings{}

	// Parse config file line by line
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())

		// Skip comments and empty lines
		if line == "" || strings.HasPrefix(line, "#") {
			continue
		}

		// Parse key=value pairs
		parts := strings.SplitN(line, "=", 2)
		if len(parts) != 2 {
			continue
		}

		key := strings.TrimSpace(parts[0])
		value := strings.TrimSpace(parts[1])

		// Extract user-configurable settings
		switch key {
		case constants.ConfigKeyAudioLanguage:
			// Parse comma-separated languages
			settings.AudioLanguages = parseCommaSeparated(value)

		case constants.ConfigKeySubtitleLanguage:
			// Parse comma-separated languages
			settings.SubtitleLanguages = parseCommaSeparated(value)

		case constants.ConfigKeyHardwareDecoder:
			// Single value for hardware decoder
			settings.HardwareDecoder = value

		case constants.ConfigKeyVideoOutput:
			// Single value for video output driver
			settings.VideoOutput = value

		case constants.ConfigKeyScaleFilter:
			// Single value for scale filter
			settings.ScaleFilter = value

		case constants.ConfigKeyDitherAlgorithm:
			// Single value for dither algorithm
			settings.DitherAlgorithm = value
		}
	}

	if err := scanner.Err(); err != nil {
		return nil, fmt.Errorf("error reading config file: %w", err)
	}

	if cr != nil {
		count := countNonEmptySettings(settings)
		cr.outputChan <- fmt.Sprintf("Preserved %d user setting(s)", count)
		logUserSettings(settings)
	}

	return settings, nil
}

// ApplyUserSettings applies preserved user settings to the mpv.conf file
// This should be called after writing a new config file to restore user preferences
func ApplyUserSettings(configPath string, settings *UserSettings, cr *CommandRunner) error {
	if settings == nil || cr == nil {
		return fmt.Errorf("invalid parameters")
	}

	cr.outputChan <- "Applying preserved user settings..."

	// Read the current config file
	data, err := os.ReadFile(configPath)
	if err != nil {
		return fmt.Errorf("failed to read config file: %w", err)
	}

	lines := strings.Split(string(data), "\n")
	var newLines []string

	// Track which keys have been updated
	keysUpdated := make(map[string]bool)

	// First pass: Update existing settings
	for _, line := range lines {
		trimmed := strings.TrimSpace(line)

		// Preserve comments and empty lines
		if trimmed == "" || strings.HasPrefix(trimmed, "#") {
			newLines = append(newLines, line)
			continue
		}

		// Check if this is a user-configurable setting
		parts := strings.SplitN(trimmed, "=", 2)
		if len(parts) != 2 {
			newLines = append(newLines, line)
			continue
		}

		key := strings.TrimSpace(parts[0])

		switch key {
		case constants.ConfigKeyAudioLanguage:
			if len(settings.AudioLanguages) > 0 {
				newLines = append(newLines, fmt.Sprintf("%s=%s", key, strings.Join(settings.AudioLanguages, ",")))
				keysUpdated[key] = true
				log.Info(fmt.Sprintf("Applied audio language settings: %v", settings.AudioLanguages))
			} else {
				newLines = append(newLines, line)
			}

		case constants.ConfigKeySubtitleLanguage:
			if len(settings.SubtitleLanguages) > 0 {
				newLines = append(newLines, fmt.Sprintf("%s=%s", key, strings.Join(settings.SubtitleLanguages, ",")))
				keysUpdated[key] = true
				log.Info(fmt.Sprintf("Applied subtitle language settings: %v", settings.SubtitleLanguages))
			} else {
				newLines = append(newLines, line)
			}

		case constants.ConfigKeyHardwareDecoder:
			if settings.HardwareDecoder != "" {
				newLines = append(newLines, fmt.Sprintf("%s=%s", key, settings.HardwareDecoder))
				keysUpdated[key] = true
				log.Info(fmt.Sprintf("Applied hardware decoder: %s", settings.HardwareDecoder))
			} else {
				newLines = append(newLines, line)
			}

		case constants.ConfigKeyVideoOutput:
			if settings.VideoOutput != "" {
				newLines = append(newLines, fmt.Sprintf("%s=%s", key, settings.VideoOutput))
				keysUpdated[key] = true
				log.Info(fmt.Sprintf("Applied video output: %s", settings.VideoOutput))
			} else {
				newLines = append(newLines, line)
			}

		case constants.ConfigKeyScaleFilter:
			if settings.ScaleFilter != "" {
				newLines = append(newLines, fmt.Sprintf("%s=%s", key, settings.ScaleFilter))
				keysUpdated[key] = true
				log.Info(fmt.Sprintf("Applied scale filter: %s", settings.ScaleFilter))
			} else {
				newLines = append(newLines, line)
			}

		case constants.ConfigKeyDitherAlgorithm:
			if settings.DitherAlgorithm != "" {
				newLines = append(newLines, fmt.Sprintf("%s=%s", key, settings.DitherAlgorithm))
				keysUpdated[key] = true
				log.Info(fmt.Sprintf("Applied dither algorithm: %s", settings.DitherAlgorithm))
			} else {
				newLines = append(newLines, line)
			}

		default:
			// Keep all other lines as-is
			newLines = append(newLines, line)
		}
	}

	// Second pass: Add missing settings at the end of the file
	// (in case the new config file doesn't have these settings)
	appendMissingSetting := func(key, value string) {
		if !keysUpdated[key] && value != "" {
			newLines = append(newLines, "")
			newLines = append(newLines, fmt.Sprintf("%s=%s", key, value))
			cr.outputChan <- fmt.Sprintf("  Added %s=%s", key, value)
		}
	}

	appendMissingSetting(constants.ConfigKeyAudioLanguage, strings.Join(settings.AudioLanguages, ","))
	appendMissingSetting(constants.ConfigKeySubtitleLanguage, strings.Join(settings.SubtitleLanguages, ","))
	appendMissingSetting(constants.ConfigKeyHardwareDecoder, settings.HardwareDecoder)
	appendMissingSetting(constants.ConfigKeyVideoOutput, settings.VideoOutput)
	appendMissingSetting(constants.ConfigKeyScaleFilter, settings.ScaleFilter)
	appendMissingSetting(constants.ConfigKeyDitherAlgorithm, settings.DitherAlgorithm)

	// Write the modified config back to file
	if err := os.WriteFile(configPath, []byte(strings.Join(newLines, "\n")), 0644); err != nil {
		return fmt.Errorf("failed to write config file: %w", err)
	}

	cr.outputChan <- "User settings applied successfully!"
	cr.outputChan <- strings.Repeat("─", 50)

	return nil
}

// parseCommaSeparated parses a comma-separated value string into a slice
func parseCommaSeparated(value string) []string {
	parts := strings.Split(value, ",")
	var result []string
	for _, part := range parts {
		trimmed := strings.TrimSpace(part)
		if trimmed != "" {
			result = append(result, trimmed)
		}
	}
	return result
}

// countNonEmptySettings counts how many settings have non-empty values
func countNonEmptySettings(settings *UserSettings) int {
	count := 0
	if len(settings.AudioLanguages) > 0 {
		count++
	}
	if len(settings.SubtitleLanguages) > 0 {
		count++
	}
	if settings.HardwareDecoder != "" {
		count++
	}
	if settings.VideoOutput != "" {
		count++
	}
	if settings.ScaleFilter != "" {
		count++
	}
	if settings.DitherAlgorithm != "" {
		count++
	}
	return count
}

// logUserSettings logs the preserved settings for debugging
func logUserSettings(settings *UserSettings) {
	if len(settings.AudioLanguages) > 0 {
		log.Info(fmt.Sprintf("Preserved audio languages: %v", settings.AudioLanguages))
	}
	if len(settings.SubtitleLanguages) > 0 {
		log.Info(fmt.Sprintf("Preserved subtitle languages: %v", settings.SubtitleLanguages))
	}
	if settings.HardwareDecoder != "" {
		log.Info(fmt.Sprintf("Preserved hardware decoder: %s", settings.HardwareDecoder))
	}
	if settings.VideoOutput != "" {
		log.Info(fmt.Sprintf("Preserved video output: %s", settings.VideoOutput))
	}
	if settings.ScaleFilter != "" {
		log.Info(fmt.Sprintf("Preserved scale filter: %s", settings.ScaleFilter))
	}
	if settings.DitherAlgorithm != "" {
		log.Info(fmt.Sprintf("Preserved dither algorithm: %s", settings.DitherAlgorithm))
	}
}

// SetRecommendedHWADecoderIfEmpty sets the hardware decoder to the platform-specific
// recommended value if the current setting in mpv.conf is empty, missing, or set to "auto".
// This is called after applying user settings to ensure fresh installs get optimal defaults.
func SetRecommendedHWADecoderIfEmpty(configPath string, ostype platform.OSType, gpuBrand string, cr *CommandRunner) error {
	// Get the recommended hardware decoder
	recommendedHWA := platform.GetRecommendedHWADecoder(ostype, gpuBrand)

	log.Info(fmt.Sprintf("SetRecommendedHWADecoderIfEmpty: ostype=%s, gpuBrand=%s, recommendedHWA=%s", ostype, gpuBrand, recommendedHWA))

	if recommendedHWA == "" || recommendedHWA == "auto" {
		// No specific recommendation, keep whatever is there
		if cr != nil {
			cr.outputChan <- fmt.Sprintf("No specific hardware decoder recommendation for %s/%s, using default", ostype, gpuBrand)
		}
		return nil
	}

	// Read the current config file
	data, err := os.ReadFile(configPath)
	if err != nil {
		return fmt.Errorf("failed to read config file: %w", err)
	}

	// Check if hwdec needs to be set or replaced
	lines := strings.Split(string(data), "\n")
	hwdecLineIndex := -1
	hwdecCurrentValue := ""
	modified := false

	for i, line := range lines {
		trimmed := strings.TrimSpace(line)

		// Skip comments and empty lines
		if trimmed == "" || strings.HasPrefix(trimmed, "#") {
			continue
		}

		// Check if this is the hwdec setting
		parts := strings.SplitN(trimmed, "=", 2)
		if len(parts) == 2 {
			key := strings.TrimSpace(parts[0])
			if key == constants.ConfigKeyHardwareDecoder {
				hwdecLineIndex = i
				// Get the value, stripping any inline comment
				value := strings.TrimSpace(parts[1])
				// Handle inline comments - split on # and take the first part
				if idx := strings.Index(value, "#"); idx >= 0 {
					value = strings.TrimSpace(value[:idx])
				}
				hwdecCurrentValue = value
				break
			}
		}
	}

	log.Info(fmt.Sprintf("SetRecommendedHWADecoderIfEmpty: hwdecLineIndex=%d, hwdecCurrentValue=%s", hwdecLineIndex, hwdecCurrentValue))

	if hwdecLineIndex >= 0 {
		// hwdec is already set - only replace if it's "auto" (default fallback)
		if hwdecCurrentValue == "auto" {
			lines[hwdecLineIndex] = fmt.Sprintf("%s=%s", constants.ConfigKeyHardwareDecoder, recommendedHWA)
			modified = true
			log.Info(fmt.Sprintf("SetRecommendedHWADecoderIfEmpty: Replacing 'auto' with '%s'", recommendedHWA))
		} else {
			log.Info(fmt.Sprintf("SetRecommendedHWADecoderIfEmpty: Keeping existing hwdec value '%s'", hwdecCurrentValue))
		}
		// Otherwise, user has a custom value - leave it alone
	} else {
		// hwdec is not set at all - add it
		lines = append(lines, "")
		lines = append(lines, fmt.Sprintf("# Platform-specific recommended hardware decoder"))
		lines = append(lines, fmt.Sprintf("%s=%s", constants.ConfigKeyHardwareDecoder, recommendedHWA))
		modified = true
		log.Info(fmt.Sprintf("SetRecommendedHWADecoderIfEmpty: Adding missing hwdec=%s", recommendedHWA))
	}

	if modified {
		// Write the modified config back to file
		if err := os.WriteFile(configPath, []byte(strings.Join(lines, "\n")), 0644); err != nil {
			return fmt.Errorf("failed to write config file: %w", err)
		}

		if cr != nil {
			cr.outputChan <- fmt.Sprintf("Set recommended hardware decoder: %s", recommendedHWA)
		}
		log.Info(fmt.Sprintf("Set recommended hardware decoder for fresh install: %s", recommendedHWA))
	}

	return nil
}

// InstallMPVConfigWithPlatformDefaults installs the MPV config and sets platform-specific
// recommended defaults. This wraps InstallMPVConfigWithOutput and adds the recommended
// hardware decoder setting for fresh installs.
func InstallMPVConfigWithPlatformDefaults(installer *Installer, cr *CommandRunner, ostype platform.OSType, gpuBrand string) error {
	// Install the base config
	if err := installer.InstallMPVConfigWithOutput(cr); err != nil {
		return err
	}

	// Get the config path
	configPath, err := GetMPVConfigPath()
	if err != nil {
		return fmt.Errorf("failed to get mpv config path: %w", err)
	}

	// Set the recommended hardware decoder if not already set
	if err := SetRecommendedHWADecoderIfEmpty(configPath, ostype, gpuBrand, cr); err != nil {
		cr.outputChan <- "Warning: Failed to set recommended hardware decoder..."
		log.Error(fmt.Sprintf("Failed to set recommended hardware decoder: %v", err))
		// Don't fail the installation, just log the warning
	}

	return nil
}
