package config

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"runtime"

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

type Config struct {
	LastCheck             string         `json:"last_check"`
	AutoUpdate            bool           `json:"auto_update"`
	InstalledApps         []InstalledApp `json:"installed_apps"`
	ManagerInPATH         bool           `json:"manager_in_path"`
	ManagerBinPath        string         `json:"manager_bin_path"`
	InstallPath           string         `json:"install_path"`
	MPVConfigPath         string         `json:"mpv_config_path"`
	UOSCVersion           string         `json:"uosc_version"`
	ModernZVersion        string         `json:"modernz_version"`
	FFmpegVersion         string         `json:"ffmpeg_version"`
	CurrentUIType         string         `json:"current_ui_type"`
	CreateManagerShortcut bool           `json:"create_manager_shortcut"`
	HWADecoderPreference  string         `json:"hwa_decoder_preference"`
}

type InstalledApp struct {
	AppName         string `json:"app_name"`
	AppType         string `json:"app_type"`
	InstallMethod   string `json:"install_method"`
	InstallPath     string `json:"install_path"`
	InstalledDate   string `json:"installed_date"`
	AppVersion      string `json:"app_version"`
	UpdateAvailable bool   `json:"update_available"`
	UIType          string `json:"ui_type,omitempty"` // "uosc", "modernz", or empty
	Managed         bool   `json:"managed"`           // true if installed/configured by MPV Manager, false if externally detected
}

var globalConfig *Config

func Load() (*Config, error) {
	if globalConfig != nil {
		return globalConfig, nil
	}

	configPath := getDefaultConfigPath()

	config := &Config{
		AutoUpdate:    false,
		InstalledApps: []InstalledApp{},
	}

	fileExists := false
	if _, err := os.Stat(configPath); err == nil {
		fileExists = true
		data, err := os.ReadFile(configPath)
		if err == nil {
			if err := json.Unmarshal(data, config); err == nil {
				// Ensure InstalledApps is initialized (in case old config files don't have it)
				if config.InstalledApps == nil {
					config.InstalledApps = []InstalledApp{}
				}

				// Migrate apps without "managed" field - they were installed by MPV Manager
				// before this field existed, so they should be marked as managed
				needsSave := migrateManagedField(config, data)

				globalConfig = config

				// Save if we made any migrations
				if needsSave {
					if err := Save(); err != nil {
						log.Warn(fmt.Sprintf("Failed to save config after migration: %s", err))
					} else {
						log.Info("Migrated installed apps: set managed=true for pre-existing entries")
					}
				}

				return config, nil
			} else {
				log.Error(fmt.Sprintf("Failed to parse config file: %v", err))
			}
		}
	}

	// Auto-save default config if file doesn't exist
	if !fileExists || globalConfig == nil {
		globalConfig = config

		// Create config directory
		configDir := filepath.Dir(configPath)
		if err := os.MkdirAll(configDir, 0755); err != nil {
			log.Info(fmt.Sprintf("Failed to create config directory: %s", err))
		}

		// Save default config to file
		data, err := json.MarshalIndent(config, "", "  ")
		if err == nil {
			if err := os.WriteFile(configPath, data, 0644); err != nil {
				log.Info(fmt.Sprintf("Failed to create config file: %s", err))
			} else {
				log.Info(fmt.Sprintf("Created default config file: %s", configPath))
			}
		} else {
			log.Info(fmt.Sprintf("Failed to marshal config: %s", err))
		}
	}

	if globalConfig == nil {
		globalConfig = config
	}
	return globalConfig, nil
}

// migrateManagedField checks for apps without the "managed" field in the raw JSON
// and sets Managed=true for them. This handles migration from older versions.
// Returns true if any changes were made.
func migrateManagedField(config *Config, rawData []byte) bool {
	// Parse raw JSON to check which apps have "managed" field
	var rawConfig struct {
		InstalledApps []map[string]interface{} `json:"installed_apps"`
	}

	if err := json.Unmarshal(rawData, &rawConfig); err != nil {
		log.Debug(fmt.Sprintf("Could not parse raw config for migration: %v", err))
		return false
	}

	if len(rawConfig.InstalledApps) == 0 {
		return false
	}

	// Build a set of app install methods that have the "managed" field
	appsWithManagedField := make(map[string]bool)
	for _, rawApp := range rawConfig.InstalledApps {
		if methodID, ok := rawApp["install_method"].(string); ok {
			_, hasManaged := rawApp["managed"]
			if hasManaged {
				appsWithManagedField[methodID] = true
			}
		}
	}

	// Update apps that don't have the "managed" field
	changed := false
	for i := range config.InstalledApps {
		// If this app didn't have "managed" field in JSON, it's from an older version
		// and was installed by MPV Manager, so set Managed=true
		if !appsWithManagedField[config.InstalledApps[i].InstallMethod] {
			if !config.InstalledApps[i].Managed {
				config.InstalledApps[i].Managed = true
				changed = true
				log.Info(fmt.Sprintf("Migrating app %s: setting managed=true (pre-existing install)", config.InstalledApps[i].InstallMethod))
			}
		}
	}

	return changed
}

func Reload() (*Config, error) {
	configPath := getDefaultConfigPath()
	config := &Config{AutoUpdate: false}

	data, err := os.ReadFile(configPath)
	if err != nil {
		log.Info(fmt.Sprintf("Config file not found, using defaults: %s", configPath))
		globalConfig = config
		return config, nil
	}

	if err := json.Unmarshal(data, config); err != nil {
		log.Error(fmt.Sprintf("Failed to parse config file: %v", err))
		globalConfig = config
		return config, nil
	}

	// Apply migration for managed field
	migrateManagedField(config, data)

	globalConfig = config
	log.Info(fmt.Sprintf("Config reloaded from: %s", configPath))
	return config, nil
}

func Save() error {
	if globalConfig == nil {
		return fmt.Errorf("config not loaded")
	}

	configPath := getConfigPath()
	configDir := filepath.Dir(configPath)

	if err := os.MkdirAll(configDir, 0755); err != nil {
		log.Error(fmt.Sprintf("Failed to create config directory: %s", err))
		return err
	}

	data, err := json.MarshalIndent(globalConfig, "", "  ")
	if err != nil {
		log.Error(fmt.Sprintf("Failed to marshal config: %s", err))
		return err
	}

	if err := os.WriteFile(configPath, data, 0644); err != nil {
		log.Error(fmt.Sprintf("Failed to write config file: %s", err))
		return err
	}

	log.Info(fmt.Sprintf("Config file saved to: %s", configPath))
	return nil
}

func SetInstallPath(path string) {
	if globalConfig == nil {
		Load()
	}
	if path != "" {
		globalConfig.InstallPath = path
		log.Info(fmt.Sprintf("Setting install_path: %s", path))
		if err := Save(); err != nil {
			log.Warn(fmt.Sprintf("Failed to save config after setting install_path: %s", err.Error()))
		}
	}
}

func GetInstallPath() string {
	if globalConfig == nil {
		Load()
	}
	if globalConfig.InstallPath != "" {
		return globalConfig.InstallPath
	}
	return getDefaultInstallPath()
}

func SetUOSCVersion(version string) {
	if globalConfig == nil {
		Load()
	}
	globalConfig.UOSCVersion = version
}

func GetUOSCVersion() string {
	if globalConfig == nil {
		Load()
	}
	return globalConfig.UOSCVersion
}

func SetModernZVersion(version string) error {
	if globalConfig == nil {
		Load()
	}
	globalConfig.ModernZVersion = version
	return Save()
}

func GetModernZVersion() string {
	if globalConfig == nil {
		Load()
	}
	return globalConfig.ModernZVersion
}

func SetFFmpegVersion(version string) error {
	if globalConfig == nil {
		Load()
	}
	globalConfig.FFmpegVersion = version
	return Save()
}

func GetFFmpegVersion() string {
	if globalConfig == nil {
		Load()
	}
	return globalConfig.FFmpegVersion
}

func SetCurrentUIType(uiType string) error {
	if globalConfig == nil {
		Load()
	}
	globalConfig.CurrentUIType = uiType
	return Save()
}

func GetCurrentUIType() string {
	if globalConfig == nil {
		Load()
	}
	if globalConfig.CurrentUIType == "" {
		return constants.DefaultUIType
	}
	return globalConfig.CurrentUIType
}

func getConfigPath() string {
	// On Windows, use mpv config directory
	if runtime.GOOS == "windows" {
		installPath := GetInstallPath()
		if installPath != "" {
			return filepath.Join(installPath, "portable_config", constants.ConfigFileName)
		}
	}

	// On Linux/macOS, use constants helper (respects XDG on Linux)
	path, err := constants.GetInstallerConfigFilePathWithHome()
	if err != nil {
		return filepath.Join(".", fmt.Sprintf(".%s", constants.ConfigFileName))
	}
	return path
}

func getDefaultConfigPath() string {
	// On Windows, default to portable config location in detected MPV directory
	if runtime.GOOS == "windows" {
		return filepath.Join(constants.GetWindowsMPVBaseDir(), "portable_config", constants.ConfigFileName)
	}

	// On Linux/macOS, use constants helper (respects XDG on Linux)
	path, err := constants.GetInstallerConfigFilePathWithHome()
	if err != nil {
		return filepath.Join(".", constants.ConfigFileName)
	}
	return path
}

func getDefaultInstallPath() string {
	// On Windows, use detected MPV directory (AppData for fresh installs, legacy for existing)
	if runtime.GOOS == "windows" {
		return constants.GetWindowsMPVBaseDir()
	}

	// On Linux/macOS, use home directory
	homeDir, err := os.UserHomeDir()
	if err != nil {
		return filepath.Join(".", "mpv")
	}

	return filepath.Join(homeDir, "mpv")
}

func GetMPVConfigPath() string {
	if globalConfig == nil {
		Load()
	}
	if globalConfig.MPVConfigPath == "" {
		// Use constants helper which respects XDG on Linux
		path, err := constants.GetMPVConfigPathWithHome()
		if err != nil {
			// Fallback to default
			homeDir, _ := os.UserHomeDir()
			globalConfig.MPVConfigPath = filepath.Join(homeDir, ".config", "mpv")
		} else {
			// Get the directory, not the file
			globalConfig.MPVConfigPath = filepath.Dir(path)
		}
	}
	return globalConfig.MPVConfigPath
}

func GetPortableConfigPath(mpvDir string) string {
	return filepath.Join(mpvDir, "portable_config")
}

func GetConfigDir() string {
	// Use constants helper which respects XDG on Linux
	homeDir, err := os.UserHomeDir()
	if err != nil {
		return "."
	}
	return constants.GetInstallerConfigPath(homeDir)
}

func SetManagerInPATH(inPath bool) error {
	if globalConfig == nil {
		Load()
	}
	log.Info(fmt.Sprintf("Setting manager_in_path: %v", inPath))
	globalConfig.ManagerInPATH = inPath
	if err := Save(); err != nil {
		log.Warn(fmt.Sprintf("Failed to save config after setting manager_in_path: %s", err.Error()))
		return err
	}
	return nil
}

func GetManagerInPATH() bool {
	if globalConfig == nil {
		Load()
	}
	return globalConfig.ManagerInPATH
}

func SetManagerBinPath(path string) error {
	if globalConfig == nil {
		Load()
	}
	if path == "" {
		log.Info("Setting manager_bin_path: (empty)")
	} else {
		log.Info(fmt.Sprintf("Setting manager_bin_path: %s", path))
	}
	globalConfig.ManagerBinPath = path
	if err := Save(); err != nil {
		log.Warn(fmt.Sprintf("Failed to save config after setting manager_bin_path: %s", err.Error()))
		return err
	}
	return nil
}

func GetManagerBinPath() string {
	if globalConfig == nil {
		Load()
	}
	return globalConfig.ManagerBinPath
}

func AddInstalledApp(app InstalledApp) error {
	if globalConfig == nil {
		Load()
	}

	log.Info(fmt.Sprintf("Adding app to installed_apps: %s", app.AppName))
	log.Info(fmt.Sprintf("App type: %s, Method: %s", app.AppType, app.InstallMethod))

	globalConfig.InstalledApps = removeApp(globalConfig.InstalledApps, app.AppName)
	globalConfig.InstalledApps = append(globalConfig.InstalledApps, app)

	if err := Save(); err != nil {
		log.Error(fmt.Sprintf("Failed to save installed app %s: %s", app.AppName, err.Error()))
		return fmt.Errorf("failed to save installed app %s: %w", app.AppName, err)
	}
	return nil
}

func RemoveInstalledApp(appName string) error {
	if globalConfig == nil {
		Load()
	}

	log.Info(fmt.Sprintf("Removing app from installed_apps: %s", appName))

	globalConfig.InstalledApps = removeApp(globalConfig.InstalledApps, appName)
	if err := Save(); err != nil {
		log.Error(fmt.Sprintf("Failed to remove installed app %s: %s", appName, err.Error()))
		return fmt.Errorf("failed to remove installed app %s: %w", appName, err)
	}
	return nil
}

func RemoveInstalledAppByType(appType string) error {
	if globalConfig == nil {
		Load()
	}

	var remaining []InstalledApp
	for _, app := range globalConfig.InstalledApps {
		if app.AppType != appType {
			remaining = append(remaining, app)
		}
	}

	globalConfig.InstalledApps = remaining
	return Save()
}

// RemoveInstalledAppByMethod removes an installed app by its install method ID
// This is useful for cleaning up stale apps that were uninstalled externally
func RemoveInstalledAppByMethod(installMethod string) error {
	if globalConfig == nil {
		Load()
	}

	var remaining []InstalledApp
	for _, app := range globalConfig.InstalledApps {
		if app.InstallMethod != installMethod {
			remaining = append(remaining, app)
		}
	}

	globalConfig.InstalledApps = remaining
	return Save()
}

func GetInstalledApps() []InstalledApp {
	if globalConfig == nil {
		Load()
	}
	return globalConfig.InstalledApps
}

func IsAppInstalled(appName string) bool {
	apps := GetInstalledApps()
	for _, app := range apps {
		if app.AppName == appName {
			return true
		}
	}
	return false
}

func removeApp(apps []InstalledApp, appName string) []InstalledApp {
	for i, app := range apps {
		if app.AppName == appName {
			return append(apps[:i], apps[i+1:]...)
		}
	}
	return apps
}

// GetCreateManagerShortcut returns whether to create desktop shortcuts
func GetCreateManagerShortcut() bool {
	if globalConfig == nil {
		Load()
	}
	if globalConfig == nil {
		return false
	}
	return globalConfig.CreateManagerShortcut
}

// SetCreateManagerShortcut sets whether to create desktop shortcuts
func SetCreateManagerShortcut(create bool) error {
	if globalConfig == nil {
		return fmt.Errorf("config not loaded")
	}
	globalConfig.CreateManagerShortcut = create
	return Save()
}

// SetAudioLanguagePrefs sets the audio language preferences in mpv.conf
func SetAudioLanguagePrefs(langs []string) error {
	return SetConfigValue(constants.ConfigKeyAudioLanguage, langs, true)
}

// GetAudioLanguagePrefs gets the audio language preferences from mpv.conf
func GetAudioLanguagePrefs() []string {
	return GetConfigValue(constants.ConfigKeyAudioLanguage)
}

// SetSubtitleLanguagePrefs sets the subtitle language preferences in mpv.conf
func SetSubtitleLanguagePrefs(langs []string) error {
	return SetConfigValue(constants.ConfigKeySubtitleLanguage, langs, true)
}

// GetSubtitleLanguagePrefs gets the subtitle language preferences from mpv.conf
func GetSubtitleLanguagePrefs() []string {
	return GetConfigValue(constants.ConfigKeySubtitleLanguage)
}

// UpdateInstalledAppUIType updates the UI type for an installed app
// If the app is not found, it returns an error
func UpdateInstalledAppUIType(appName string, uiType string) error {
	if globalConfig == nil {
		Load()
	}

	for i, app := range globalConfig.InstalledApps {
		if app.AppName == appName {
			globalConfig.InstalledApps[i].UIType = uiType
			if err := Save(); err != nil {
				log.Error(fmt.Sprintf("Failed to save app UI type %s: %s", appName, err.Error()))
				return fmt.Errorf("failed to save app UI type %s: %w", appName, err)
			}
			log.Info(fmt.Sprintf("Updated UI type for %s to %s", appName, uiType))
			return nil
		}
	}

	return fmt.Errorf("app not found: %s", appName)
}

// UpdateInstalledAppManaged updates the managed status and UI type for an installed app
// Used when adopting an externally installed app
// If the app is not found, it returns an error
func UpdateInstalledAppManaged(appName string, installMethod string, managed bool, uiType string) error {
	if globalConfig == nil {
		Load()
	}

	for i, app := range globalConfig.InstalledApps {
		if app.AppName == appName && app.InstallMethod == installMethod {
			globalConfig.InstalledApps[i].Managed = managed
			globalConfig.InstalledApps[i].UIType = uiType
			if err := Save(); err != nil {
				log.Error(fmt.Sprintf("Failed to update app %s: %s", appName, err.Error()))
				return fmt.Errorf("failed to update app %s: %w", appName, err)
			}
			log.Info(fmt.Sprintf("Updated app %s: managed=%v, uiType=%s", appName, managed, uiType))
			return nil
		}
	}

	return fmt.Errorf("app not found: %s (%s)", appName, installMethod)
}

// GetHWADecoderPreference returns the stored hardware decoder preference
// Returns empty string if no preference is stored
func GetHWADecoderPreference() string {
	if globalConfig == nil {
		Load()
	}
	return globalConfig.HWADecoderPreference
}

// SetHWADecoderPreference saves the hardware decoder preference to the manager config
// This is stored separately from mpv.conf for backup/restore purposes
func SetHWADecoderPreference(hwdec string) error {
	if globalConfig == nil {
		Load()
	}
	globalConfig.HWADecoderPreference = hwdec
	if err := Save(); err != nil {
		log.Error(fmt.Sprintf("Failed to save HWA decoder preference: %s", err.Error()))
		return fmt.Errorf("failed to save HWA decoder preference: %w", err)
	}
	log.Info(fmt.Sprintf("Saved HWA decoder preference: %s", hwdec))
	return nil
}

// ResetToDefaults resets the config file to default values
// It creates a backup of the existing config before resetting
func ResetToDefaults() error {
	configPath := getDefaultConfigPath()

	// Create a backup before reset if file exists
	if _, err := os.Stat(configPath); err == nil {
		backupPath := configPath + ".backup." + "20060102-150405"
		// Use current time for backup
		backupPath = configPath + ".backup"
		data, err := os.ReadFile(configPath)
		if err == nil {
			// Just create a simple backup
			_ = os.WriteFile(backupPath, data, 0644)
			log.Info("Created backup of config at: " + backupPath)
		}
	}

	// Reset global config to nil to force re-initialization
	globalConfig = nil

	// Create new default config
	newConfig := &Config{
		AutoUpdate:    false,
		InstalledApps: []InstalledApp{},
	}

	// Save the new default config
	configDir := filepath.Dir(configPath)
	if err := os.MkdirAll(configDir, 0755); err != nil {
		log.Error(fmt.Sprintf("Failed to create config directory: %s", err))
		return fmt.Errorf("failed to create config directory: %w", err)
	}

	data, err := json.MarshalIndent(newConfig, "", "  ")
	if err != nil {
		log.Error(fmt.Sprintf("Failed to marshal default config: %s", err))
		return fmt.Errorf("failed to marshal default config: %w", err)
	}

	if err := os.WriteFile(configPath, data, 0644); err != nil {
		log.Error(fmt.Sprintf("Failed to write default config: %s", err))
		return fmt.Errorf("failed to write default config: %w", err)
	}

	// Set global config to the new default
	globalConfig = newConfig

	log.Info("Config reset to defaults successfully")
	return nil
}
