package config

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

	"gitgud.io/mike/mpv-manager/pkg/constants"
	"github.com/stretchr/testify/assert"
)

func resetGlobalConfig() {
	globalConfig = nil
}

func TestRemoveApp(t *testing.T) {
	tests := []struct {
		name        string
		apps        []InstalledApp
		appName     string
		wantRemoved bool
		wantCount   int
	}{
		{
			name: "remove existing app",
			apps: []InstalledApp{
				{AppName: "MPV"},
				{AppName: "IINA"},
			},
			appName:     "MPV",
			wantRemoved: true,
			wantCount:   1,
		},
		{
			name: "remove non-existent app",
			apps: []InstalledApp{
				{AppName: "MPV"},
				{AppName: "IINA"},
			},
			appName:     "VLC",
			wantRemoved: false,
			wantCount:   2,
		},
		{
			name:        "remove from empty list",
			apps:        []InstalledApp{},
			appName:     "MPV",
			wantRemoved: false,
			wantCount:   0,
		},
		{
			name: "remove last app",
			apps: []InstalledApp{
				{AppName: "MPV"},
			},
			appName:     "MPV",
			wantRemoved: true,
			wantCount:   0,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := removeApp(tt.apps, tt.appName)
			assert.Equal(t, tt.wantCount, len(got))

			if tt.wantRemoved {
				for _, app := range got {
					assert.NotEqual(t, tt.appName, app.AppName)
				}
			}
		})
	}
}

func TestGetPortableConfigPath(t *testing.T) {
	tests := []struct {
		name   string
		mpvDir string
		want   string
	}{
		{
			name:   "absolute path",
			mpvDir: "/home/user/mpv",
			want:   "/home/user/mpv/portable_config",
		},
		{
			name:   "relative path",
			mpvDir: "./mpv",
			want:   "mpv/portable_config",
		},
		{
			name:   "windows path",
			mpvDir: "C:\\Program Files\\mpv",
			want:   "C:\\Program Files\\mpv/portable_config",
		},
		{
			name:   "empty path",
			mpvDir: "",
			want:   "portable_config",
		},
	}

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

func TestGetConfigDir(t *testing.T) {
	originalHome := os.Getenv("HOME")
	defer func() {
		os.Setenv("HOME", originalHome)
	}()

	tests := []struct {
		name          string
		setHome       bool
		homeValue     string
		expectCurrent bool
	}{
		{
			name:          "with valid home directory",
			setHome:       true,
			homeValue:     "/home/user",
			expectCurrent: false,
		},
		{
			name:          "without home directory",
			setHome:       false,
			expectCurrent: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if tt.setHome {
				os.Setenv("HOME", tt.homeValue)
				if runtime.GOOS == "windows" {
					os.Setenv("USERPROFILE", tt.homeValue)
				}
			} else {
				os.Unsetenv("HOME")
				if runtime.GOOS == "windows" {
					os.Unsetenv("USERPROFILE")
				}
			}

			got := GetConfigDir()

			if tt.expectCurrent {
				assert.Equal(t, ".", got)
			} else {
				assert.Contains(t, got, tt.homeValue)
				assert.Contains(t, got, ".config")
				// Installer config is now stored in MPV config directory
				assert.Contains(t, got, constants.AppNameMPV)
			}
		})
	}
}

func TestLoad(t *testing.T) {
	t.Run("load new config", func(t *testing.T) {
		resetGlobalConfig()

		config, err := Load()
		assert.NoError(t, err)
		assert.NotNil(t, config)
		assert.False(t, config.AutoUpdate)
		assert.NotNil(t, config.InstalledApps)
	})

	t.Run("load existing config", func(t *testing.T) {
		resetGlobalConfig()

		// Set HOME before creating config to ensure correct path
		originalHome := os.Getenv("HOME")
		defer os.Setenv("HOME", originalHome)

		tempDir := t.TempDir()
		os.Setenv("HOME", tempDir)

		// Installer config is now stored in MPV config directory
		configDir := filepath.Join(tempDir, ".config", constants.AppNameMPV)
		configFile := filepath.Join(configDir, constants.ConfigFileName)

		err := os.MkdirAll(configDir, 0755)
		assert.NoError(t, err)

		testConfig := Config{
			LastCheck:   "2024-01-01",
			AutoUpdate:  true,
			InstallPath: "/custom/path",
			InstalledApps: []InstalledApp{
				{AppName: "MPV", AppType: "flatpak"},
			},
		}

		data, err := json.MarshalIndent(testConfig, "", "  ")
		assert.NoError(t, err)
		err = os.WriteFile(configFile, data, 0644)
		assert.NoError(t, err)

		config, err := Load()
		assert.NoError(t, err)
		assert.NotNil(t, config)
		assert.Equal(t, "2024-01-01", config.LastCheck)
		assert.True(t, config.AutoUpdate)
		assert.Equal(t, "/custom/path", config.InstallPath)
		assert.Len(t, config.InstalledApps, 1)
	})

	t.Run("return cached config", func(t *testing.T) {
		resetGlobalConfig()

		config1, _ := Load()
		config2, _ := Load()

		assert.Same(t, config1, config2)
	})
}

func TestSave(t *testing.T) {
	t.Run("save config to file", func(t *testing.T) {
		resetGlobalConfig()

		tempDir := t.TempDir()
		originalHome := os.Getenv("HOME")
		defer os.Setenv("HOME", originalHome)
		os.Setenv("HOME", tempDir)

		config, _ := Load()
		config.InstallPath = "/test/path"
		config.AutoUpdate = true

		err := Save()
		assert.NoError(t, err)

		// Installer config is now stored in MPV config directory
		configFile := filepath.Join(tempDir, ".config", constants.AppNameMPV, constants.ConfigFileName)
		assert.FileExists(t, configFile)

		data, err := os.ReadFile(configFile)
		assert.NoError(t, err)

		var loaded Config
		err = json.Unmarshal(data, &loaded)
		assert.NoError(t, err)
		assert.Equal(t, "/test/path", loaded.InstallPath)
		assert.True(t, loaded.AutoUpdate)
	})

	t.Run("error without loaded config", func(t *testing.T) {
		resetGlobalConfig()

		err := Save()
		assert.Error(t, err)
	})
}

func TestSetInstallPath(t *testing.T) {
	t.Run("set install path", func(t *testing.T) {
		resetGlobalConfig()

		tempDir := t.TempDir()
		originalHome := os.Getenv("HOME")
		defer os.Setenv("HOME", originalHome)
		os.Setenv("HOME", tempDir)

		SetInstallPath("/custom/install/path")

		path := GetInstallPath()
		assert.Equal(t, "/custom/install/path", path)
	})

	t.Run("empty install path", func(t *testing.T) {
		resetGlobalConfig()

		tempDir := t.TempDir()
		originalHome := os.Getenv("HOME")
		defer os.Setenv("HOME", originalHome)
		os.Setenv("HOME", tempDir)

		SetInstallPath("")

		path := GetInstallPath()
		assert.NotEmpty(t, path)
	})
}

func TestGetInstallPath(t *testing.T) {
	t.Run("get custom install path", func(t *testing.T) {
		resetGlobalConfig()

		tempDir := t.TempDir()
		originalHome := os.Getenv("HOME")
		defer os.Setenv("HOME", originalHome)
		os.Setenv("HOME", tempDir)

		config, _ := Load()
		config.InstallPath = "/custom/path"

		path := GetInstallPath()
		assert.Equal(t, "/custom/path", path)
	})

	t.Run("get default install path", func(t *testing.T) {
		resetGlobalConfig()

		tempDir := t.TempDir()
		originalHome := os.Getenv("HOME")
		defer os.Setenv("HOME", originalHome)
		os.Setenv("HOME", tempDir)

		path := GetInstallPath()
		assert.Contains(t, path, tempDir)
		assert.Contains(t, path, "mpv")
	})
}

func TestSetUOSCVersion(t *testing.T) {
	resetGlobalConfig()

	SetUOSCVersion("4.0.0")

	version := GetUOSCVersion()
	assert.Equal(t, "4.0.0", version)
}

func TestGetUOSCVersion(t *testing.T) {
	resetGlobalConfig()

	config, _ := Load()
	config.UOSCVersion = "3.5.0"

	version := GetUOSCVersion()
	assert.Equal(t, "3.5.0", version)
}

func TestAddInstalledApp(t *testing.T) {
	resetGlobalConfig()

	tempDir := t.TempDir()
	originalHome := os.Getenv("HOME")
	defer os.Setenv("HOME", originalHome)
	os.Setenv("HOME", tempDir)

	app := InstalledApp{
		AppName:       "MPV",
		AppType:       "flatpak",
		InstallMethod: "mpv-flatpak",
		InstalledDate: "2024-01-01",
		AppVersion:    "0.36.0",
	}

	err := AddInstalledApp(app)
	assert.NoError(t, err)

	apps := GetInstalledApps()
	assert.Len(t, apps, 1)
	assert.Equal(t, "MPV", apps[0].AppName)
	assert.True(t, IsAppInstalled("MPV"))
}

func TestRemoveInstalledApp(t *testing.T) {
	resetGlobalConfig()

	tempDir := t.TempDir()
	originalHome := os.Getenv("HOME")
	defer os.Setenv("HOME", originalHome)
	os.Setenv("HOME", tempDir)

	app := InstalledApp{AppName: "MPV"}
	AddInstalledApp(app)

	err := RemoveInstalledApp("MPV")
	assert.NoError(t, err)

	assert.False(t, IsAppInstalled("MPV"))
	assert.Len(t, GetInstalledApps(), 0)
}

func TestRemoveInstalledAppByType(t *testing.T) {
	resetGlobalConfig()

	tempDir := t.TempDir()
	originalHome := os.Getenv("HOME")
	defer os.Setenv("HOME", originalHome)
	os.Setenv("HOME", tempDir)

	AddInstalledApp(InstalledApp{AppName: "MPV", AppType: "flatpak"})
	AddInstalledApp(InstalledApp{AppName: "Celluloid", AppType: "flatpak"})
	AddInstalledApp(InstalledApp{AppName: "IINA", AppType: "app"})

	err := RemoveInstalledAppByType("flatpak")
	assert.NoError(t, err)

	apps := GetInstalledApps()
	assert.Len(t, apps, 1)
	assert.Equal(t, "IINA", apps[0].AppName)
}

func TestGetInstalledApps(t *testing.T) {
	resetGlobalConfig()

	tempDir := t.TempDir()
	originalHome := os.Getenv("HOME")
	defer os.Setenv("HOME", originalHome)
	os.Setenv("HOME", tempDir)

	config, _ := Load()
	config.InstalledApps = []InstalledApp{
		{AppName: "MPV"},
		{AppName: "IINA"},
	}

	apps := GetInstalledApps()
	assert.Len(t, apps, 2)
}

func TestIsAppInstalled(t *testing.T) {
	resetGlobalConfig()

	tempDir := t.TempDir()
	originalHome := os.Getenv("HOME")
	defer os.Setenv("HOME", originalHome)
	os.Setenv("HOME", tempDir)

	config, _ := Load()
	config.InstalledApps = []InstalledApp{
		{AppName: "MPV"},
		{AppName: "IINA"},
	}

	assert.True(t, IsAppInstalled("MPV"))
	assert.True(t, IsAppInstalled("IINA"))
	assert.False(t, IsAppInstalled("VLC"))
}

func TestAudioLanguagePrefs(t *testing.T) {
	resetGlobalConfig()

	tempDir := t.TempDir()
	originalHome := os.Getenv("HOME")
	defer os.Setenv("HOME", originalHome)
	os.Setenv("HOME", tempDir)

	langs := []string{"en", "es"}
	err := SetAudioLanguagePrefs(langs)
	assert.NoError(t, err)

	prefs := GetAudioLanguagePrefs()
	assert.Equal(t, langs, prefs)
}

func TestSubtitleLanguagePrefs(t *testing.T) {
	resetGlobalConfig()

	tempDir := t.TempDir()
	originalHome := os.Getenv("HOME")
	defer os.Setenv("HOME", originalHome)
	os.Setenv("HOME", tempDir)

	langs := []string{"en", "fr", "de"}
	err := SetSubtitleLanguagePrefs(langs)
	assert.NoError(t, err)

	prefs := GetSubtitleLanguagePrefs()
	assert.Equal(t, langs, prefs)
}

func TestManagerInPATH(t *testing.T) {
	resetGlobalConfig()

	tempDir := t.TempDir()
	originalHome := os.Getenv("HOME")
	defer os.Setenv("HOME", originalHome)
	os.Setenv("HOME", tempDir)

	err := SetManagerInPATH(true)
	assert.NoError(t, err)
	assert.True(t, GetManagerInPATH())

	err = SetManagerInPATH(false)
	assert.NoError(t, err)
	assert.False(t, GetManagerInPATH())
}

func TestManagerBinPath(t *testing.T) {
	resetGlobalConfig()

	tempDir := t.TempDir()
	originalHome := os.Getenv("HOME")
	defer os.Setenv("HOME", originalHome)
	os.Setenv("HOME", tempDir)

	err := SetManagerBinPath("/usr/local/bin/mpv-manager")
	assert.NoError(t, err)
	assert.Equal(t, "/usr/local/bin/mpv-manager", GetManagerBinPath())
}

func TestGetMPVConfigPath(t *testing.T) {
	resetGlobalConfig()

	tempDir := t.TempDir()
	originalHome := os.Getenv("HOME")
	defer os.Setenv("HOME", originalHome)
	os.Setenv("HOME", tempDir)

	path := GetMPVConfigPath()
	assert.Contains(t, path, tempDir)
	assert.Contains(t, path, ".config")
	assert.Contains(t, path, "mpv")

	config, _ := Load()
	config.MPVConfigPath = "/custom/mpv/path"

	path = GetMPVConfigPath()
	assert.Equal(t, "/custom/mpv/path", path)
}
