package constants

import (
	"fmt"
	"os"
	"path/filepath"
	"runtime"
	"sync"
)

// ============================================================================
// XDG Base Directory Specification Support (Linux only)
// ============================================================================

// XDG environment variable names
const (
	EnvXDGConfigHome = "XDG_CONFIG_HOME"
	EnvXDGDataHome   = "XDG_DATA_HOME"
	EnvXDGCacheHome  = "XDG_CACHE_HOME"
)

// XDG default paths (relative to home directory)
const (
	XDGConfigHomeDefault = ".config"
	XDGDataHomeDefault   = ".local/share"
	XDGCacheHomeDefault  = ".cache"
)

// ============================================================================
// Windows Config Path Migration Support
// ============================================================================

// WindowsPathPreference tracks which path the user is using on Windows
type WindowsPathPreference int

const (
	WindowsPathLegacy  WindowsPathPreference = iota // Using USER\mpv (legacy)
	WindowsPathAppData                              // Using %APPDATA%\mpv (new)
)

var (
	windowsPathOnce       sync.Once
	windowsPathPreference WindowsPathPreference
	windowsMPVBaseDir     string
)

// getWindowsMPVBaseDir determines the MPV config directory on Windows.
// It checks for existing installations in this order:
// 1. %APPDATA%\mpv - if exists, use it (migrated user)
// 2. %USERPROFILE%\mpv - if exists, use it (legacy user)
// 3. %APPDATA%\mpv - default for fresh installs
func getWindowsMPVBaseDir() string {
	windowsPathOnce.Do(func() {
		appData := os.Getenv("APPDATA")
		homeDir, _ := os.UserHomeDir()

		appDataPath := filepath.Join(appData, InstallDirName)
		homePath := filepath.Join(homeDir, InstallDirName)

		// Check if new AppData path exists
		if _, err := os.Stat(appDataPath); err == nil {
			windowsPathPreference = WindowsPathAppData
			windowsMPVBaseDir = appDataPath
			return
		}

		// Check if legacy home path exists
		if _, err := os.Stat(homePath); err == nil {
			windowsPathPreference = WindowsPathLegacy
			windowsMPVBaseDir = homePath
			return
		}

		// Fresh install - use AppData path
		windowsPathPreference = WindowsPathAppData
		windowsMPVBaseDir = appDataPath
	})

	return windowsMPVBaseDir
}

// GetWindowsPathPreference returns whether the user is using the legacy or AppData path
// This is primarily for debugging/logging purposes
func GetWindowsPathPreference() WindowsPathPreference {
	getWindowsMPVBaseDir() // Ensure path detection has run
	return windowsPathPreference
}

// GetWindowsMPVBaseDir returns the base MPV directory for Windows
// This is the public function for getting the Windows MPV path
func GetWindowsMPVBaseDir() string {
	return getWindowsMPVBaseDir()
}

// getConfigHome returns XDG_CONFIG_HOME or the default ~/.config
// This function only applies XDG on Linux; on macOS it uses the default.
func getConfigHome() string {
	// Only apply XDG on Linux, not on macOS or Windows
	if runtime.GOOS != OSLinux {
		homeDir, _ := os.UserHomeDir()
		return filepath.Join(homeDir, XDGConfigHomeDefault)
	}

	if xdgConfig := os.Getenv(EnvXDGConfigHome); xdgConfig != "" {
		return xdgConfig
	}
	homeDir, _ := os.UserHomeDir()
	return filepath.Join(homeDir, XDGConfigHomeDefault)
}

// getDataHome returns XDG_DATA_HOME or the default ~/.local/share
// This function only applies XDG on Linux; on macOS it uses the default.
func getDataHome() string {
	// Only apply XDG on Linux, not on macOS or Windows
	if runtime.GOOS != OSLinux {
		homeDir, _ := os.UserHomeDir()
		return filepath.Join(homeDir, XDGDataHomeDefault)
	}

	if xdgData := os.Getenv(EnvXDGDataHome); xdgData != "" {
		return xdgData
	}
	homeDir, _ := os.UserHomeDir()
	return filepath.Join(homeDir, XDGDataHomeDefault)
}

// getCacheHome returns XDG_CACHE_HOME or the default ~/.cache
// This function only applies XDG on Linux; on macOS it uses the default.
func getCacheHome() string {
	// Only apply XDG on Linux, not on macOS or Windows
	if runtime.GOOS != OSLinux {
		homeDir, _ := os.UserHomeDir()
		return filepath.Join(homeDir, XDGCacheHomeDefault)
	}

	if xdgCache := os.Getenv(EnvXDGCacheHome); xdgCache != "" {
		return xdgCache
	}
	homeDir, _ := os.UserHomeDir()
	return filepath.Join(homeDir, XDGCacheHomeDefault)
}

// GetXDGConfigHome returns the XDG_CONFIG_HOME path (public for testing)
// On Linux: respects XDG_CONFIG_HOME environment variable, defaults to ~/.config
// On macOS/Windows: always uses default ~/.config
func GetXDGConfigHome() string {
	return getConfigHome()
}

// GetXDGDataHome returns the XDG_DATA_HOME path (public for testing)
// On Linux: respects XDG_DATA_HOME environment variable, defaults to ~/.local/share
// On macOS/Windows: always uses default ~/.local/share
func GetXDGDataHome() string {
	return getDataHome()
}

// GetXDGCacheHome returns the XDG_CACHE_HOME path (public for testing)
// On Linux: respects XDG_CACHE_HOME environment variable, defaults to ~/.cache
// On macOS/Windows: always uses default ~/.cache
func GetXDGCacheHome() string {
	return getCacheHome()
}

// ============================================================================
// Config Path Handling
// ============================================================================

// ConfigPaths provides unified config path handling
type ConfigPaths struct {
	HomeDir            string
	InstallerConfigDir string
	MPVConfigDir       string
	PortableConfigDir  string
	ConfigBackupsDir   string
}

// GetConfigPaths returns config paths for the current platform
// On Linux, respects XDG_CONFIG_HOME environment variable.
// On Windows, uses %APPDATA%\mpv for fresh installs, legacy path for existing users.
// On macOS, uses traditional hardcoded paths.
// Note: The installer config file is stored in the MPV config directory for convenience.
func GetConfigPaths(homeDir string) *ConfigPaths {
	if homeDir == "" {
		var err error
		homeDir, err = filepath.Abs(".")
		if err != nil {
			homeDir = "."
		}
	}

	cp := &ConfigPaths{
		HomeDir: homeDir,
	}

	if runtime.GOOS == OSWindows {
		// Windows: use detected MPV directory (AppData for fresh installs, legacy for existing)
		mpvBaseDir := getWindowsMPVBaseDir()
		cp.InstallerConfigDir = filepath.Join(mpvBaseDir, PortableConfigDir)
		cp.MPVConfigDir = cp.InstallerConfigDir
		cp.PortableConfigDir = PortableConfigDir
		cp.ConfigBackupsDir = cp.InstallerConfigDir
	} else if runtime.GOOS == OSLinux {
		// Linux: use XDG Base Directory Specification
		// Installer config goes in the MPV config directory for convenience
		xdgConfigHome := getConfigHome()
		cp.InstallerConfigDir = filepath.Join(xdgConfigHome, AppNameMPV)
		cp.MPVConfigDir = filepath.Join(xdgConfigHome, AppNameMPV)
		cp.PortableConfigDir = PortableConfigDir
		cp.ConfigBackupsDir = filepath.Join(cp.MPVConfigDir, ConfigBackupsDir)
	} else {
		// macOS and other Unix: use traditional ~/.config/mpv paths
		// Installer config goes in the MPV config directory for convenience
		cp.InstallerConfigDir = filepath.Join(homeDir, MpvConfDir)
		cp.MPVConfigDir = filepath.Join(homeDir, MpvConfDir)
		cp.PortableConfigDir = PortableConfigDir
		cp.ConfigBackupsDir = filepath.Join(cp.MPVConfigDir, ConfigBackupsDir)
	}

	return cp
}

// GetMPVConfigPath returns the path to mpv.conf based on platform and base path
func GetMPVConfigPath(basePath string) string {
	if runtime.GOOS == OSWindows {
		// Windows: portable_config/mpv.conf relative to MPV binary
		return filepath.Join(basePath, PortableConfigDir, MPVConfigFileName)
	}
	// Unix: ~/.config/mpv/mpv.conf
	return filepath.Join(basePath, MPVConfigFileName)
}

// GetMPVConfigDir returns the directory containing mpv.conf
func GetMPVConfigDir(basePath string) string {
	if runtime.GOOS == OSWindows {
		// Windows: portable_config relative to MPV binary
		return filepath.Join(basePath, PortableConfigDir)
	}
	// Unix: ~/.config/mpv
	return basePath
}

// GetMPVConfigBackupDir returns the directory for MPV config backups
func GetMPVConfigBackupDir(mpvConfigDir string) string {
	if runtime.GOOS == OSWindows {
		// Windows: backups in portable_config
		return mpvConfigDir
	}
	// Unix: ~/.config/mpv/conf_backups
	return filepath.Join(mpvConfigDir, ConfigBackupsDir)
}

// GetPortableConfigPath returns the portable config directory path
func GetPortableConfigPath(installDir string) string {
	return filepath.Join(installDir, PortableConfigDir)
}

// GetInstallerConfigPath returns the installer's config directory (same as MPV config directory)
// On Linux: uses $XDG_CONFIG_HOME/mpv
// On macOS: uses ~/.config/mpv
// On Windows: uses detected MPV path (%APPDATA%\mpv for fresh installs, legacy path for existing)
func GetInstallerConfigPath(homeDir string) string {
	if runtime.GOOS == OSWindows {
		return filepath.Join(getWindowsMPVBaseDir(), PortableConfigDir)
	}
	if runtime.GOOS == OSLinux {
		xdgConfigHome := getConfigHome()
		return filepath.Join(xdgConfigHome, AppNameMPV)
	}
	// macOS and other Unix
	return filepath.Join(homeDir, MpvConfDir)
}

// GetInstallerConfigFilePath returns the path to mpv-manager.json
// On Linux: uses $XDG_CONFIG_HOME/mpv/mpv-manager.json
// On macOS: uses ~/.config/mpv/mpv-manager.json
// On Windows: uses ~/mpv/portable_config/mpv-manager.json
func GetInstallerConfigFilePath(homeDir string) string {
	return filepath.Join(GetInstallerConfigPath(homeDir), ConfigFileName)
}

// FormatBackupFileName creates a backup filename with timestamp
func FormatBackupFileName(timestamp string) string {
	return timestamp + BackupFilePrefix
}

// FormatFullBackupFileName creates a backup filename with full timestamp
func FormatFullBackupFileName(timestamp string) string {
	return timestamp + BackupDirFilePrefix
}

// GetTimestampedBackupPath returns the full path to a timestamped backup
func GetTimestampedBackupPath(backupDir, filename string) string {
	return filepath.Join(backupDir, filename)
}

// GetHomeDir returns the user's home directory with enhanced error handling.
// Returns an error if the home directory cannot be determined.
func GetHomeDir() (string, error) {
	homeDir, err := os.UserHomeDir()
	if err != nil {
		return "", fmt.Errorf("failed to get home directory: %w", err)
	}
	return homeDir, nil
}

// GetMPVConfigPathWithHome returns the full path to mpv.conf with automatic home directory detection.
// For Windows: Uses detected MPV path (%APPDATA%\mpv for fresh installs, legacy path for existing)
// For Linux: $XDG_CONFIG_HOME/mpv/mpv.conf (defaults to ~/.config/mpv/mpv.conf)
// For macOS: ~/.config/mpv/mpv.conf
// Returns an error if the home directory cannot be determined.
func GetMPVConfigPathWithHome() (string, error) {
	homeDir, err := GetHomeDir()
	if err != nil {
		return "", err
	}

	if runtime.GOOS == OSWindows {
		return filepath.Join(getWindowsMPVBaseDir(), PortableConfigDir, MPVConfigFileName), nil
	}

	if runtime.GOOS == OSLinux {
		// Linux: use XDG_CONFIG_HOME
		xdgConfigHome := getConfigHome()
		return filepath.Join(xdgConfigHome, AppNameMPV, MPVConfigFileName), nil
	}

	// macOS and other Unix: use traditional ~/.config/mpv/mpv.conf
	return filepath.Join(homeDir, MpvConfDir, MPVConfigFileName), nil
}

// GetMPVConfigBackupDirWithHome returns the directory for MPV config backups with automatic home directory detection.
// For Windows: Uses detected MPV path (%APPDATA%\mpv for fresh installs, legacy path for existing)
// For Linux: $XDG_CONFIG_HOME/mpv/conf_backups (defaults to ~/.config/mpv/conf_backups)
// For macOS: ~/.config/mpv/conf_backups
// Returns an error if the home directory cannot be determined.
func GetMPVConfigBackupDirWithHome() (string, error) {
	homeDir, err := GetHomeDir()
	if err != nil {
		return "", err
	}

	if runtime.GOOS == OSWindows {
		return filepath.Join(getWindowsMPVBaseDir(), PortableConfigDir, ConfigBackupsDir), nil
	}

	if runtime.GOOS == OSLinux {
		// Linux: use XDG_CONFIG_HOME
		xdgConfigHome := getConfigHome()
		return filepath.Join(xdgConfigHome, AppNameMPV, ConfigBackupsDir), nil
	}

	// macOS and other Unix: use traditional ~/.config/mpv/conf_backups
	return filepath.Join(homeDir, MpvConfDir, ConfigBackupsDir), nil
}

// GetInstallerConfigFilePathWithHome returns the path to mpv-manager.json with automatic home directory detection.
// On Linux: uses $XDG_CONFIG_HOME/mpv/mpv-manager.json (defaults to ~/.config/mpv/mpv-manager.json)
// On macOS: uses ~/.config/mpv/mpv-manager.json
// On Windows: uses ~/mpv/portable_config/mpv-manager.json
// Returns an error if the home directory cannot be determined.
func GetInstallerConfigFilePathWithHome() (string, error) {
	homeDir, err := GetHomeDir()
	if err != nil {
		return "", err
	}

	return GetInstallerConfigFilePath(homeDir), nil
}
