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"`
	ManagerShortcutCreated bool           `json:"manager_shortcut_created"`
	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
}

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{}
				}
				globalConfig = config
				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
}

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
	}

	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
}

func SetManagerShortcutCreated(created bool) error {
	if globalConfig == nil {
		Load()
	}
	globalConfig.ManagerShortcutCreated = created
	return Save()
}

func GetManagerShortcutCreated() bool {
	if globalConfig == nil {
		Load()
	}
	return globalConfig.ManagerShortcutCreated
}

// 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)
}

// 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
}
