package constants

import (
	"os"
	"path/filepath"
	"runtime"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

// ============================================================================
// XDG Base Directory Tests
// ============================================================================

func TestGetXDGConfigHome(t *testing.T) {
	// Save original env var and restore after test
	originalEnv := os.Getenv(EnvXDGConfigHome)
	defer os.Setenv(EnvXDGConfigHome, originalEnv)

	homeDir, err := os.UserHomeDir()
	require.NoError(t, err)

	t.Run("default without env var", func(t *testing.T) {
		os.Unsetenv(EnvXDGConfigHome)
		got := GetXDGConfigHome()
		want := filepath.Join(homeDir, XDGConfigHomeDefault)
		assert.Equal(t, want, got)
	})

	t.Run("respects XDG_CONFIG_HOME on Linux only", func(t *testing.T) {
		customPath := "/custom/config"
		os.Setenv(EnvXDGConfigHome, customPath)

		got := GetXDGConfigHome()

		if runtime.GOOS == OSLinux {
			// Linux should respect XDG_CONFIG_HOME
			assert.Equal(t, customPath, got)
		} else {
			// macOS and Windows should ignore XDG_CONFIG_HOME
			want := filepath.Join(homeDir, XDGConfigHomeDefault)
			assert.Equal(t, want, got)
		}
	})
}

func TestGetXDGDataHome(t *testing.T) {
	// Save original env var and restore after test
	originalEnv := os.Getenv(EnvXDGDataHome)
	defer os.Setenv(EnvXDGDataHome, originalEnv)

	homeDir, err := os.UserHomeDir()
	require.NoError(t, err)

	t.Run("default without env var", func(t *testing.T) {
		os.Unsetenv(EnvXDGDataHome)
		got := GetXDGDataHome()
		want := filepath.Join(homeDir, XDGDataHomeDefault)
		assert.Equal(t, want, got)
	})

	t.Run("respects XDG_DATA_HOME on Linux only", func(t *testing.T) {
		customPath := "/custom/data"
		os.Setenv(EnvXDGDataHome, customPath)

		got := GetXDGDataHome()

		if runtime.GOOS == OSLinux {
			// Linux should respect XDG_DATA_HOME
			assert.Equal(t, customPath, got)
		} else {
			// macOS and Windows should ignore XDG_DATA_HOME
			want := filepath.Join(homeDir, XDGDataHomeDefault)
			assert.Equal(t, want, got)
		}
	})
}

func TestGetXDGCacheHome(t *testing.T) {
	// Save original env var and restore after test
	originalEnv := os.Getenv(EnvXDGCacheHome)
	defer os.Setenv(EnvXDGCacheHome, originalEnv)

	homeDir, err := os.UserHomeDir()
	require.NoError(t, err)

	t.Run("default without env var", func(t *testing.T) {
		os.Unsetenv(EnvXDGCacheHome)
		got := GetXDGCacheHome()
		want := filepath.Join(homeDir, XDGCacheHomeDefault)
		assert.Equal(t, want, got)
	})

	t.Run("respects XDG_CACHE_HOME on Linux only", func(t *testing.T) {
		customPath := "/custom/cache"
		os.Setenv(EnvXDGCacheHome, customPath)

		got := GetXDGCacheHome()

		if runtime.GOOS == OSLinux {
			// Linux should respect XDG_CACHE_HOME
			assert.Equal(t, customPath, got)
		} else {
			// macOS and Windows should ignore XDG_CACHE_HOME
			want := filepath.Join(homeDir, XDGCacheHomeDefault)
			assert.Equal(t, want, got)
		}
	})
}

func TestGetConfigPathsXDG(t *testing.T) {
	// This test verifies XDG behavior on Linux
	if runtime.GOOS != OSLinux {
		t.Skip("Skipping Linux-specific XDG test")
	}

	// Save original env vars and restore after test
	originalConfigHome := os.Getenv(EnvXDGConfigHome)
	defer os.Setenv(EnvXDGConfigHome, originalConfigHome)

	t.Run("respects XDG_CONFIG_HOME for MPV config", func(t *testing.T) {
		customPath := "/custom/xdg/config"
		os.Setenv(EnvXDGConfigHome, customPath)

		got := GetConfigPaths("/home/user")

		// Installer config is now stored in MPV config directory for convenience
		assert.Equal(t, filepath.Join(customPath, AppNameMPV), got.InstallerConfigDir)
		assert.Equal(t, filepath.Join(customPath, AppNameMPV), got.MPVConfigDir)
		assert.Equal(t, filepath.Join(customPath, AppNameMPV, ConfigBackupsDir), got.ConfigBackupsDir)
	})

	t.Run("default paths when XDG not set", func(t *testing.T) {
		os.Unsetenv(EnvXDGConfigHome)

		got := GetConfigPaths("/home/user")

		// Should use default ~/.config/mpv for everything (installer config in MPV dir)
		// (not the mock "/home/user" since getConfigHome() calls os.UserHomeDir())
		assert.True(t, filepath.IsAbs(got.InstallerConfigDir), "expected absolute path")
		assert.Contains(t, got.InstallerConfigDir, ".config/mpv")
		assert.Contains(t, got.MPVConfigDir, ".config/mpv")
		assert.Contains(t, got.ConfigBackupsDir, ".config/mpv/conf_backups")
	})
}

func TestGetMPVConfigPathWithHomeXDG(t *testing.T) {
	// This test verifies XDG behavior on Linux
	if runtime.GOOS != OSLinux {
		t.Skip("Skipping Linux-specific XDG test")
	}

	// Save original env vars and restore after test
	originalConfigHome := os.Getenv(EnvXDGConfigHome)
	defer os.Setenv(EnvXDGConfigHome, originalConfigHome)

	t.Run("respects XDG_CONFIG_HOME", func(t *testing.T) {
		customPath := "/custom/xdg/config"
		os.Setenv(EnvXDGConfigHome, customPath)

		got, err := GetMPVConfigPathWithHome()
		require.NoError(t, err)

		assert.Equal(t, filepath.Join(customPath, AppNameMPV, MPVConfigFileName), got)
	})

	t.Run("default when XDG not set", func(t *testing.T) {
		os.Unsetenv(EnvXDGConfigHome)

		got, err := GetMPVConfigPathWithHome()
		require.NoError(t, err)

		// Should end with default ~/.config/mpv/mpv.conf
		assert.Contains(t, got, ".config/mpv/mpv.conf")
	})
}

func TestGetInstallerConfigFilePathWithHomeXDG(t *testing.T) {
	// This test verifies XDG behavior on Linux
	if runtime.GOOS != OSLinux {
		t.Skip("Skipping Linux-specific XDG test")
	}

	// Save original env vars and restore after test
	originalConfigHome := os.Getenv(EnvXDGConfigHome)
	defer os.Setenv(EnvXDGConfigHome, originalConfigHome)

	t.Run("respects XDG_CONFIG_HOME", func(t *testing.T) {
		customPath := "/custom/xdg/config"
		os.Setenv(EnvXDGConfigHome, customPath)

		got, err := GetInstallerConfigFilePathWithHome()
		require.NoError(t, err)

		// Installer config is now stored in MPV config directory
		assert.Equal(t, filepath.Join(customPath, AppNameMPV, ConfigFileName), got)
	})

	t.Run("default when XDG not set", func(t *testing.T) {
		os.Unsetenv(EnvXDGConfigHome)

		got, err := GetInstallerConfigFilePathWithHome()
		require.NoError(t, err)

		// Should end with default ~/.config/mpv/mpv-manager.json (installer config in MPV dir)
		assert.Contains(t, got, ".config/mpv/mpv-manager.json")
	})
}

// ============================================================================
// Original Tests
// ============================================================================

func TestGetConfigPaths(t *testing.T) {
	// Save original env var and restore after test
	originalEnv := os.Getenv(EnvXDGConfigHome)
	defer os.Setenv(EnvXDGConfigHome, originalEnv)

	tests := []struct {
		name                   string
		homeDir                string
		wantHome               string
		wantInstallerConfigDir string
		wantMPVConfigDir       string
		wantPortableConfigDir  string
		wantConfigBackupsDir   string
	}{
		{
			name:                   "macOS home directory",
			homeDir:                "/Users/test",
			wantHome:               "/Users/test",
			wantInstallerConfigDir: "/Users/test/.config/mpv", // Installer config in MPV dir
			wantMPVConfigDir:       "/Users/test/.config/mpv",
			wantPortableConfigDir:  "portable_config",
			wantConfigBackupsDir:   "/Users/test/.config/mpv/conf_backups",
		},
		{
			name:                   "Empty home directory defaults to .",
			homeDir:                "",
			wantHome:               "",
			wantInstallerConfigDir: ".config/mpv", // Installer config in MPV dir
			wantMPVConfigDir:       ".config/mpv",
			wantPortableConfigDir:  "portable_config",
			wantConfigBackupsDir:   ".config/mpv/conf_backups",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Note: Can't easily test Windows paths in same test since runtime.GOOS is fixed
			// This test focuses on Unix paths
			if runtime.GOOS == "windows" {
				t.Skip("Skipping Unix path tests on Windows")
			}

			// Clear XDG env var for consistent default behavior
			os.Unsetenv(EnvXDGConfigHome)

			got := GetConfigPaths(tt.homeDir)

			if runtime.GOOS == OSLinux {
				// On Linux, XDG is used so paths use actual home directory
				assert.NotEmpty(t, got.InstallerConfigDir)
				assert.NotEmpty(t, got.MPVConfigDir)
				// Installer config is now in MPV config directory
				assert.Contains(t, got.InstallerConfigDir, ".config/mpv")
				assert.Contains(t, got.MPVConfigDir, ".config/mpv")
				assert.Contains(t, got.ConfigBackupsDir, ".config/mpv/conf_backups")
				assert.Equal(t, tt.wantPortableConfigDir, got.PortableConfigDir)
			} else if tt.homeDir != "" {
				// macOS uses homeDir parameter
				assert.Equal(t, tt.wantHome, got.HomeDir)
				assert.Equal(t, tt.wantInstallerConfigDir, got.InstallerConfigDir)
				assert.Equal(t, tt.wantMPVConfigDir, got.MPVConfigDir)
				assert.Equal(t, tt.wantPortableConfigDir, got.PortableConfigDir)
				assert.Equal(t, tt.wantConfigBackupsDir, got.ConfigBackupsDir)
			} else {
				// For empty homeDir, just verify non-empty paths are returned
				assert.NotEmpty(t, got.HomeDir)
				assert.NotEmpty(t, got.InstallerConfigDir)
				assert.NotEmpty(t, got.MPVConfigDir)
				assert.Equal(t, tt.wantPortableConfigDir, got.PortableConfigDir)
			}
		})
	}
}

func TestGetConfigPathsWindows(t *testing.T) {
	if runtime.GOOS != "windows" {
		t.Skip("Skipping Windows-specific test on Unix")
	}

	homeDir := "C:\\Users\\test"
	got := GetConfigPaths(homeDir)

	assert.Equal(t, "C:\\Users\\test", got.HomeDir)
	// Windows: installer config goes in mpv/portable_config
	assert.Equal(t, "C:\\Users\\test\\mpv\\portable_config", got.InstallerConfigDir)
	assert.Equal(t, "C:\\Users\\test\\mpv\\portable_config", got.MPVConfigDir)
	assert.Equal(t, "portable_config", got.PortableConfigDir)
	assert.Equal(t, "C:\\Users\\test\\mpv\\portable_config", got.ConfigBackupsDir)
}

func TestGetMPVConfigPath(t *testing.T) {
	tests := []struct {
		name      string
		basePath  string
		isWindows bool
		want      string
	}{
		{
			name:      "Unix/Linux MPV config path",
			basePath:  "/home/user/.config/mpv",
			isWindows: false,
			want:      "/home/user/.config/mpv/mpv.conf",
		},
		{
			name:      "Windows portable MPV config path",
			basePath:  "C:\\Program Files\\mpv",
			isWindows: true,
			want:      "C:\\Program Files\\mpv\\portable_config\\mpv.conf",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if tt.isWindows != (runtime.GOOS == "windows") {
				t.Skip("Skipping platform-specific test")
			}

			got := GetMPVConfigPath(tt.basePath)
			assert.Equal(t, tt.want, got)
		})
	}
}

func TestGetMPVConfigDir(t *testing.T) {
	tests := []struct {
		name      string
		basePath  string
		isWindows bool
		want      string
	}{
		{
			name:      "Unix/Linux MPV config dir",
			basePath:  "/home/user/.config/mpv",
			isWindows: false,
			want:      "/home/user/.config/mpv",
		},
		{
			name:      "Windows portable MPV config dir",
			basePath:  "C:\\Program Files\\mpv",
			isWindows: true,
			want:      "C:\\Program Files\\mpv\\portable_config",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if tt.isWindows != (runtime.GOOS == "windows") {
				t.Skip("Skipping platform-specific test")
			}

			got := GetMPVConfigDir(tt.basePath)
			assert.Equal(t, tt.want, got)
		})
	}
}

func TestGetMPVConfigBackupDir(t *testing.T) {
	tests := []struct {
		name         string
		mpvConfigDir string
		isWindows    bool
		want         string
	}{
		{
			name:         "Unix/Linux MPV config backup dir",
			mpvConfigDir: "/home/user/.config/mpv",
			isWindows:    false,
			want:         "/home/user/.config/mpv/conf_backups",
		},
		{
			name:         "Windows portable MPV config backup dir",
			mpvConfigDir: "C:\\Program Files\\mpv\\portable_config",
			isWindows:    true,
			want:         "C:\\Program Files\\mpv\\portable_config",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if tt.isWindows != (runtime.GOOS == "windows") {
				t.Skip("Skipping platform-specific test")
			}

			got := GetMPVConfigBackupDir(tt.mpvConfigDir)
			assert.Equal(t, tt.want, got)
		})
	}
}

func TestGetPortableConfigPath(t *testing.T) {
	tests := []struct {
		name       string
		installDir string
		isWindows  bool
		want       string
	}{
		{
			name:       "Portable config path Unix/Linux",
			installDir: "/opt/mpv",
			isWindows:  false,
			want:       "/opt/mpv/portable_config",
		},
		{
			name:       "Portable config path Windows",
			installDir: "C:\\Program Files\\mpv",
			isWindows:  true,
			want:       "C:\\Program Files\\mpv\\portable_config",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if tt.isWindows && runtime.GOOS != "windows" {
				t.Skip("Skipping Windows-specific test")
			}
			got := GetPortableConfigPath(tt.installDir)
			assert.Equal(t, tt.want, got)
		})
	}
}

func TestGetInstallerConfigPath(t *testing.T) {
	// Save original env var and restore after test
	originalEnv := os.Getenv(EnvXDGConfigHome)
	defer os.Setenv(EnvXDGConfigHome, originalEnv)

	tests := []struct {
		name       string
		homeDir    string
		isWindows  bool
		isLinux    bool
		want       string
		wantSuffix string // Used on Linux where actual home dir is used
	}{
		{
			name:       "Installer config path Unix/macOS",
			homeDir:    "/home/user",
			isWindows:  false,
			isLinux:    false,
			want:       "/home/user/.config/mpv", // Installer config in MPV dir
			wantSuffix: "/.config/mpv",
		},
		{
			name:      "Installer config path Windows",
			homeDir:   "C:\\Users\\test",
			isWindows: true,
			want:      "C:\\Users\\test\\mpv\\portable_config",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if tt.isWindows && runtime.GOOS != "windows" {
				t.Skip("Skipping Windows-specific test")
			}
			if tt.isLinux && runtime.GOOS != OSLinux {
				t.Skip("Skipping Linux-specific test")
			}

			// Clear XDG env var for consistent default behavior
			os.Unsetenv(EnvXDGConfigHome)

			got := GetInstallerConfigPath(tt.homeDir)

			if runtime.GOOS == OSLinux {
				// On Linux, the actual home directory is used via XDG
				assert.True(t, filepath.IsAbs(got), "expected absolute path on Linux")
				assert.Contains(t, got, ".config/mpv", "expected path to contain .config/mpv")
			} else if tt.isWindows && runtime.GOOS == "windows" {
				assert.Equal(t, tt.want, got)
			} else {
				// macOS uses homeDir parameter
				assert.Equal(t, tt.want, got)
			}
		})
	}
}

func TestGetInstallerConfigFilePath(t *testing.T) {
	// Save original env var and restore after test
	originalEnv := os.Getenv(EnvXDGConfigHome)
	defer os.Setenv(EnvXDGConfigHome, originalEnv)

	tests := []struct {
		name       string
		homeDir    string
		isWindows  bool
		want       string
		wantSuffix string
	}{
		{
			name:       "Installer config file path Unix/macOS",
			homeDir:    "/home/user",
			isWindows:  false,
			want:       "/home/user/.config/mpv/mpv-manager.json", // Installer config in MPV dir
			wantSuffix: "/.config/mpv/mpv-manager.json",
		},
		{
			name:      "Installer config file path Windows",
			homeDir:   "C:\\Users\\test",
			isWindows: true,
			want:      "C:\\Users\\test\\mpv\\portable_config\\mpv-manager.json",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if tt.isWindows && runtime.GOOS != "windows" {
				t.Skip("Skipping Windows-specific test")
			}

			// Clear XDG env var for consistent default behavior
			os.Unsetenv(EnvXDGConfigHome)

			got := GetInstallerConfigFilePath(tt.homeDir)

			if runtime.GOOS == OSLinux {
				// On Linux, the actual home directory is used via XDG
				assert.True(t, filepath.IsAbs(got), "expected absolute path on Linux")
				assert.Contains(t, got, ".config/mpv/mpv-manager.json", "expected path to contain config file in MPV dir")
			} else if tt.isWindows && runtime.GOOS == "windows" {
				assert.Equal(t, tt.want, got)
			} else {
				// macOS uses homeDir parameter
				assert.Equal(t, tt.want, got)
			}
		})
	}
}

func TestFormatBackupFileName(t *testing.T) {
	tests := []struct {
		name      string
		timestamp string
		want      string
	}{
		{
			name:      "Simple timestamp",
			timestamp: "2006-01-02",
			want:      "2006-01-02-mpv.conf.bak",
		},
		{
			name:      "Full timestamp",
			timestamp: "2006-01-02-150405",
			want:      "2006-01-02-150405-mpv.conf.bak",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := FormatBackupFileName(tt.timestamp)
			assert.Equal(t, tt.want, got)
		})
	}
}

func TestFormatFullBackupFileName(t *testing.T) {
	tests := []struct {
		name      string
		timestamp string
		want      string
	}{
		{
			name:      "Simple timestamp",
			timestamp: "2006-01-02",
			want:      "2006-01-02_mpv.conf",
		},
		{
			name:      "Full timestamp",
			timestamp: "2006-01-02-150405",
			want:      "2006-01-02-150405_mpv.conf",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := FormatFullBackupFileName(tt.timestamp)
			assert.Equal(t, tt.want, got)
		})
	}
}

func TestGetTimestampedBackupPath(t *testing.T) {
	tests := []struct {
		name      string
		backupDir string
		filename  string
		isWindows bool
		want      string
	}{
		{
			name:      "Timestamped backup path Windows",
			backupDir: "C:\\Users\\test\\.config\\install-mpv",
			filename:  "2006-01-02-150405_mpv.conf",
			isWindows: true,
			want:      "C:\\Users\\test\\.config\\install-mpv\\2006-01-02-150405_mpv.conf",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if tt.isWindows && runtime.GOOS != "windows" {
				t.Skip("Skipping Windows-specific test")
			}
			got := GetTimestampedBackupPath(tt.backupDir, tt.filename)
			assert.Equal(t, tt.want, got)
		})
	}
}
