package config

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"

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

// GetConfigPath returns the full path to mpv.conf (platform-aware)
func GetConfigPath() string {
	configPath, err := constants.GetMPVConfigPathWithHome()
	if err != nil {
		log.Warn(fmt.Sprintf("Failed to get MPV config path: %v", err))
		return ""
	}
	return configPath
}

// GetConfigValue reads a config value from mpv.conf and returns as array
// Handles comma-separated values, inline comments, and whitespace
// Note: For hwdec, the comma is part of the value (e.g., "nvdec-copy,auto"), not a separator
func GetConfigValue(key string) []string {
	configPath := GetConfigPath()
	if configPath == "" {
		return []string{}
	}

	data, err := os.ReadFile(configPath)
	if err != nil {
		if !os.IsNotExist(err) {
			log.Warn(fmt.Sprintf("Failed to read mpv.conf: %v", err))
		}
		return []string{}
	}

	content := string(data)
	lines := strings.Split(content, "\n")
	prefix := key + "="

	for _, line := range lines {
		trimmed := strings.TrimSpace(line)
		if strings.HasPrefix(trimmed, prefix) {
			// Extract value part
			valuePart := strings.TrimSpace(strings.TrimPrefix(trimmed, prefix))

			// Strip inline comments
			if commentIndex := strings.Index(valuePart, "#"); commentIndex != -1 {
				valuePart = strings.TrimSpace(valuePart[:commentIndex])
			}

			// Handle empty value
			if valuePart == "" {
				return []string{}
			}

			// Strip surrounding quotes if present (mpv.conf allows quoted values)
			if len(valuePart) >= 2 &&
				((valuePart[0] == '"' && valuePart[len(valuePart)-1] == '"') ||
					(valuePart[0] == '\'' && valuePart[len(valuePart)-1] == '\'')) {
				valuePart = valuePart[1 : len(valuePart)-1]
			}

			// For hwdec, don't split on comma - the comma is part of the value (e.g., "nvdec-copy,auto")
			if key == constants.ConfigKeyHardwareDecoder {
				return []string{valuePart}
			}

			// Handle comma-separated arrays for other keys
			values := strings.Split(valuePart, ",")
			for i := range values {
				values[i] = strings.TrimSpace(values[i])
			}

			// Filter out empty values from array
			var result []string
			for _, v := range values {
				if v != "" {
					result = append(result, v)
				}
			}

			return result
		}
	}

	return []string{}
}

// shouldQuoteConfigValue determines if a config value should be wrapped in quotes
// This is needed for paths and templates that contain special characters
func shouldQuoteConfigValue(key, value string) bool {
	// Keys that typically need quoted values
	quotedKeys := map[string]bool{
		"screenshot-directory": true,
		"screenshot-template":  true,
	}

	if !quotedKeys[key] {
		return false
	}

	// Only quote if the value isn't already quoted
	if len(value) >= 2 &&
		((value[0] == '"' && value[len(value)-1] == '"') ||
			(value[0] == '\'' && value[len(value)-1] == '\'')) {
		return false
	}

	return true
}

// SetConfigValue writes a config value to mpv.conf
// preserveComments: if true, preserves existing inline comments
// values: array of values, will be joined with comma for MPV arrays
func SetConfigValue(key string, values []string, preserveComments bool) error {
	configPath := GetConfigPath()
	if configPath == "" {
		return fmt.Errorf("unable to determine MPV config path")
	}

	// Create config directory if it doesn't exist
	configDir := filepath.Dir(configPath)
	if err := os.MkdirAll(configDir, 0755); err != nil {
		return fmt.Errorf("failed to create config directory: %w", err)
	}

	// Create file from embedded asset if it doesn't exist
	if _, err := os.Stat(configPath); os.IsNotExist(err) {
		log.Info("mpv.conf not found, creating from embedded asset")
		embeddedConfig, err := assets.ReadMPVConfig()
		if err != nil {
			return fmt.Errorf("failed to read embedded mpv.conf: %w", err)
		}
		if err := os.WriteFile(configPath, embeddedConfig, 0644); err != nil {
			return fmt.Errorf("failed to create mpv.conf: %w", err)
		}
		log.Info(fmt.Sprintf("Created mpv.conf at: %s", configPath))
	}

	// Read existing content
	data, err := os.ReadFile(configPath)
	if err != nil {
		return fmt.Errorf("failed to read mpv.conf: %w", err)
	}

	// Build new value
	var newValue string
	if len(values) == 0 {
		newValue = ""
	} else if len(values) == 1 {
		newValue = values[0]
	} else {
		newValue = strings.Join(values, ",")
	}

	// Wrap in quotes for keys that typically need them (paths, templates with special chars)
	if newValue != "" && shouldQuoteConfigValue(key, newValue) {
		newValue = `"` + newValue + `"`
	}

	// Process lines to update or append
	lines := strings.Split(string(data), "\n")
	found := false
	prefix := key + "="

	for i, line := range lines {
		trimmed := strings.TrimSpace(line)
		if strings.HasPrefix(trimmed, prefix) {
			if preserveComments && newValue != "" {
				// Preserve inline comment
				equalIndex := strings.Index(line, "=")
				commentIndex := strings.Index(line, "#")
				if commentIndex != -1 && commentIndex > equalIndex {
					comment := line[commentIndex-1:] // Include space before comment
					lines[i] = line[:equalIndex+1] + newValue + comment
				} else {
					lines[i] = line[:equalIndex+1] + newValue
				}
			} else {
				// Replace without comment preservation
				equalIndex := strings.Index(line, "=")
				lines[i] = line[:equalIndex+1] + newValue
			}
			found = true
			break
		}
	}

	// Append new line if key not found
	if !found {
		lines = append(lines, prefix+newValue)
	}

	// Write using atomic file operation
	newContent := strings.Join(lines, "\n")
	return writeFileAtomically(configPath, []byte(newContent))
}

// ParseConfig parses entire config file into map
// Returns map[string][]string where key is the config key and value is array of values
func ParseConfig(path string) (map[string][]string, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		if os.IsNotExist(err) {
			return make(map[string][]string), nil
		}
		return nil, fmt.Errorf("failed to read config file: %w", err)
	}

	config := make(map[string][]string)
	lines := strings.Split(string(data), "\n")

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

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

		// Find key=value separator
		equalIndex := strings.Index(trimmed, "=")
		if equalIndex == -1 {
			continue // Skip lines without equals
		}

		// Extract key and value
		key := strings.TrimSpace(trimmed[:equalIndex])
		valuePart := strings.TrimSpace(trimmed[equalIndex+1:])

		// Strip inline comments from value
		if commentIndex := strings.Index(valuePart, "#"); commentIndex != -1 {
			valuePart = strings.TrimSpace(valuePart[:commentIndex])
		}

		// Handle empty value
		if valuePart == "" {
			config[key] = []string{}
			continue
		}

		// Handle comma-separated arrays
		values := strings.Split(valuePart, ",")
		for i := range values {
			values[i] = strings.TrimSpace(values[i])
		}

		// Filter out empty values
		var result []string
		for _, v := range values {
			if v != "" {
				result = append(result, v)
			}
		}

		config[key] = result
	}

	return config, nil
}

// WriteConfig writes entire config map to file using atomic operation
// Format: Arrays become "key=value1,value2,value3", singles become "key=value"
func WriteConfig(path string, config map[string][]string) error {
	var lines []string

	// Build content from map
	for key, values := range config {
		if len(values) == 0 {
			lines = append(lines, key+"=")
		} else if len(values) == 1 {
			lines = append(lines, key+"="+values[0])
		} else {
			lines = append(lines, key+"="+strings.Join(values, ","))
		}
	}

	// Write using atomic file operation
	newContent := strings.Join(lines, "\n")
	return writeFileAtomically(path, []byte(newContent))
}

// writeFileAtomically writes content to a file using atomic operation
// Creates temp file, writes content, then renames to final path
func writeFileAtomically(path string, content []byte) error {
	// Create temp file with .tmp suffix
	tempPath := path + ".tmp"

	// Write to temp file
	if err := os.WriteFile(tempPath, content, 0644); err != nil {
		return fmt.Errorf("failed to write temp file: %w", err)
	}

	// Rename temp file to actual path (atomic operation)
	if err := os.Rename(tempPath, path); err != nil {
		// Clean up temp file if rename fails
		_ = os.Remove(tempPath)
		return fmt.Errorf("failed to rename temp file: %w", err)
	}

	return nil
}
