//go:build windows

package installer

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"

	"gitgud.io/mike/mpv-manager/internal/assets"
	"gitgud.io/mike/mpv-manager/pkg/config"
	"gitgud.io/mike/mpv-manager/pkg/constants"
	"gitgud.io/mike/mpv-manager/pkg/log"
	"gitgud.io/mike/mpv-manager/pkg/platform"
)

type WindowsInstaller struct {
	*Installer
	Platform *platform.Platform
}

func NewWindowsInstaller(installer *Installer, p *platform.Platform) *WindowsInstaller {
	return &WindowsInstaller{
		Installer: installer,
		Platform:  p,
	}
}

func (wi *WindowsInstaller) GetAvailableMethods(filterInstalled bool) []InstallMethod {
	methods := []InstallMethod{}

	if wi.Platform.Arch == "amd64" || wi.Platform.Arch == "x86_64" {
		if filterInstalled && config.IsAppInstalled("MPC-QT") {
		} else {
			methods = append(methods, InstallMethod{
				ID:          constants.MethodMPCQT,
				Name:        "MPC-QT",
				Description: "Easy to use MPV frontend for Windows (installer required)",
				Recommended: true,
				Priority:    1,
				Icon:        constants.MethodIcon[constants.MethodMPCQT],
				Logo:        constants.MethodLogo[constants.MethodMPCQT],
				Type:        constants.MethodType[constants.MethodMPCQT],
				AppType:     constants.MethodAppType[constants.MethodMPCQT],
				Homepage:    constants.MethodHomepage[constants.MethodMPCQT],
			})
		}

		if filterInstalled && config.IsAppInstalled("MPV") {
		} else {
			methods = append(methods, InstallMethod{
				ID:          constants.MethodMPVBinary,
				Name:        "MPV",
				Description: "Official MPV app, automatically managed. Recommended.",
				Recommended: false,
				Priority:    2,
				Icon:        constants.MethodIcon[constants.MethodMPVBinary],
				Logo:        constants.MethodLogo[constants.MethodMPVBinary],
				Type:        constants.MethodType[constants.MethodMPVBinary],
				AppType:     constants.MethodAppType[constants.MethodMPVBinary],
				Homepage:    constants.MethodHomepage[constants.MethodMPVBinary],
			})
		}
	}

	if wi.Platform.Arch == "arm64" {
		if filterInstalled && config.IsAppInstalled("MPV") {
		} else {
			methods = append(methods, InstallMethod{
				ID:          constants.MethodMPVBinary,
				Name:        "MPV",
				Description: "Official MPV app, automatically managed. Recommended.",
				Recommended: false,
				Priority:    1,
				Icon:        constants.MethodIcon[constants.MethodMPVBinary],
				Logo:        constants.MethodLogo[constants.MethodMPVBinary],
				Type:        constants.MethodType[constants.MethodMPVBinary],
				AppType:     constants.MethodAppType[constants.MethodMPVBinary],
				Homepage:    constants.MethodHomepage[constants.MethodMPVBinary],
			})
		}
	}

	return methods
}

func (wi *WindowsInstaller) InstallMPV(method string, installUOSC bool) error {
	if !Check7zipAvailable() {
		log.Error("7zip not found, cannot install MPV")
		return fmt.Errorf(constants.Err7zipNotFound)
	}

	isV3 := wi.Platform.SupportsX86_64_v3()
	archInfo, err := GetArchitectureInfo(wi.Platform.Arch, isV3, wi.ReleaseInfo)
	if err != nil {
		log.Error("Failed to get architecture info: " + err.Error())
		return err
	}

	downloadDir := wi.InstallDir
	if err := DownloadAndExtract(wi.Installer, nil, archInfo.URL, downloadDir, fmt.Sprintf("mpv-%s.7z", archInfo.Arch)); err != nil {
		log.Error("Failed to download and extract MPV: " + err.Error())
		return err
	}

	configDir := filepath.Join(downloadDir, "portable_config")
	if err := wi.EnsureDir(configDir); err != nil {
		log.Error("Failed to create portable_config directory: " + err.Error())
		return err
	}

	ffmpegPath := filepath.Join(downloadDir, "ffmpeg.exe")
	if err := wi.DownloadFile(archInfo.FFmpegURL, ffmpegPath); err != nil {
		log.Error("Failed to download FFmpeg: " + err.Error())
		return err
	}

	log.Info("MPV installation completed successfully")
	return nil
}

func (wi *WindowsInstaller) InstallMPCQT() error {
	if wi.Platform.Arch != "amd64" && wi.Platform.Arch != "x86_64" {
		log.Error("MPC-QT is only available for x86-64, current arch: " + wi.Platform.Arch)
		return fmt.Errorf("MPC-QT is only available for x86-64")
	}

	url := wi.ReleaseInfo.MPCQT.X8664.URL
	installerPath := filepath.Join(os.TempDir(), "mpc-qt-installer.exe")

	fmt.Println("Downloading MPC-QT...")
	log.Info("Downloading MPC-QT installer from: " + url)
	if err := wi.DownloadFileWithProgress(url, installerPath, func(written, total int64) {
		if total > 0 {
			percent := int(float64(written) / float64(total) * 100)
			fmt.Printf("\rProgress: %d%% (%d/%d bytes)", percent, written, total)
		}
	}); err != nil {
		log.Error("Failed to download MPC-QT installer: " + err.Error())
		return err
	}
	fmt.Println()

	log.Info("Launching MPC-QT installer...")
	cmd := exec.Command(installerPath)
	cmd.Dir = os.TempDir()
	if err := cmd.Start(); err != nil {
		log.Error("Failed to start MPC-QT installer: " + err.Error())
		return err
	}
	log.Info("MPC-QT installer launched successfully")
	return nil
}

func (wi *WindowsInstaller) CheckInstalled() bool {
	mpvPath := filepath.Join(wi.InstallDir, "mpv.exe")
	return wi.FileExists(mpvPath)
}

func (wi *WindowsInstaller) CanUpdate() bool {
	return wi.CheckInstalled()
}

func (wi *WindowsInstaller) GetInstalledVersion() string {
	return ""
}

func (wi *WindowsInstaller) Uninstall() error {
	// Legacy function for backward compatibility
	return fmt.Errorf("use UninstallWithOutput() for proper uninstallation")
}

func (wi *WindowsInstaller) UninstallWithOutput(cr *CommandRunner) error {
	cr.outputChan <- "Starting MPV uninstallation..."
	cr.outputChan <- strings.Repeat("─", 50)

	// 1. Remove desktop shortcuts
	cr.outputChan <- "Removing desktop shortcuts..."
	if err := wi.removeFileAssociationShortcuts(cr); err != nil {
		cr.outputChan <- fmt.Sprintf("Warning: Failed to remove file association shortcuts: %v", err)
	}
	if err := wi.removeMPVShortcut(cr); err != nil {
		cr.outputChan <- fmt.Sprintf("Warning: Failed to remove MPV shortcut: %v", err)
	}

	// 2. Run mpv-unregister.bat for file associations
	if err := wi.runMPVUninstallBat(cr); err != nil {
		cr.outputChan <- "Warning: Failed to remove file associations"
	}

	// 3. Remove all files except portable_config and installer
	entries, err := os.ReadDir(wi.InstallDir)
	if err != nil {
		return fmt.Errorf("failed to read install directory: %w", err)
	}

	for _, entry := range entries {
		entryName := entry.Name()
		entryPath := filepath.Join(wi.InstallDir, entryName)

		// Skip preserved directories
		if contains(constants.WindowsPreserveDirs, entryName) {
			continue
		}

		// Remove file or directory
		if err := os.RemoveAll(entryPath); err != nil {
			cr.outputChan <- fmt.Sprintf("Warning: Failed to remove %s: %v", entryName, err)
		} else {
			cr.outputChan <- fmt.Sprintf("Removed: %s", entryName)
		}
	}

	// 3. Remove specific directories from portable_config
	portableConfigDir := filepath.Join(wi.InstallDir, constants.PortableConfigDir)
	if wi.FileExists(portableConfigDir) {
		for _, dirName := range constants.WindowsRemoveDirs {
			dirPath := filepath.Join(portableConfigDir, dirName)
			if err := os.RemoveAll(dirPath); err == nil {
				cr.outputChan <- fmt.Sprintf("Removed: %s/%s", constants.PortableConfigDir, dirName)
			}
		}
	}

	cr.outputChan <- "MPV uninstalled successfully!"
	cr.outputChan <- fmt.Sprintf("Note: %s was preserved with your settings", constants.PortableConfigDir)
	return nil
}

// runMPVUninstallBat executes mpv-unregister.bat to remove file associations
// The script is at the root of the MPV install directory (zhongfly/mpv-winbuild structure)
func (wi *WindowsInstaller) runMPVUninstallBat(cr *CommandRunner) error {
	// MPV's installer directory (from the MPV archive)
	mpvInstallerDir := filepath.Join(wi.InstallDir, constants.MPVInstallerDir)
	mpvUninstallSrc := filepath.Join(mpvInstallerDir, constants.MPVUninstallBat)

	// MPV Manager directory (our directory)
	managerDir := filepath.Join(wi.InstallDir, constants.InstallerDir)
	uninstallBat := filepath.Join(managerDir, constants.MPVUninstallBat)

	cr.outputChan <- fmt.Sprintf("Looking for MPV file association uninstall script...")

	// Check if the script exists in MPV's installer directory
	if wi.FileExists(mpvUninstallSrc) {
		cr.outputChan <- "Found MPV uninstall script, copying to MPV Manager directory..."

		// Ensure mpv-manager directory exists
		if err := os.MkdirAll(managerDir, 0755); err != nil {
			cr.outputChan <- fmt.Sprintf("Warning: Failed to create directory: %v", err)
		} else {
			// Copy the script to mpv-manager directory
			if err := copyFile(mpvUninstallSrc, uninstallBat, 0755); err != nil {
				cr.outputChan <- fmt.Sprintf("Warning: Failed to copy uninstall script: %v", err)
			} else {
				cr.outputChan <- fmt.Sprintf("Copied uninstall script to: %s", uninstallBat)
			}
		}
	}

	// Use the copied script if available, otherwise use MPV's original
	scriptToRun := uninstallBat
	if !wi.FileExists(scriptToRun) {
		scriptToRun = mpvUninstallSrc
	}

	if !wi.FileExists(scriptToRun) {
		cr.outputChan <- "MPV file association uninstall script not found, skipping."
		return nil
	}

	cr.outputChan <- ""
	cr.outputChan <- "Opening file association removal..."
	cr.outputChan <- "Please approve the UAC prompt."
	cr.outputChan <- ""

	// Run the batch file elevated using PowerShell
	psCmd := fmt.Sprintf("Start-Process -FilePath '%s' -Verb RunAs -WorkingDirectory '%s'", scriptToRun, managerDir)
	cr.outputChan <- fmt.Sprintf("PowerShell command: %s", psCmd)

	cmd := exec.Command("powershell", "-Command", psCmd)

	output, err := cmd.CombinedOutput()
	if err != nil {
		cr.outputChan <- fmt.Sprintf("PowerShell error: %v", err)
		if len(output) > 0 {
			cr.outputChan <- fmt.Sprintf("PowerShell output: %s", string(output))
		}
		cr.outputChan <- "Failed to start file association removal with elevation."
		cr.outputChan <- "File associations may need to be removed manually."
		return nil
	}

	cr.outputChan <- ""
	cr.outputChan <- "File association removal window opened."
	cr.outputChan <- "Please complete the removal in the elevated window."

	return nil
}

// copyManagerUninstallScript copies the MPV Manager uninstall script to the
// mpv-manager directory so users can remove MPV Manager later
func (wi *WindowsInstaller) copyManagerUninstallScript(cr *CommandRunner) error {
	managerDir := filepath.Join(wi.InstallDir, constants.InstallerDir)

	// Ensure mpv-manager directory exists
	if err := os.MkdirAll(managerDir, 0755); err != nil {
		return err
	}

	cr.outputChan <- "Copying MPV Manager uninstall script..."

	// Extract the uninstall.bat from embedded assets
	if err := assets.ExtractUninstallBat(managerDir); err != nil {
		return fmt.Errorf("failed to extract uninstall.bat: %w", err)
	}

	cr.outputChan <- fmt.Sprintf("Uninstall script copied to: %s", filepath.Join(managerDir, constants.UninstallBatFileName))
	return nil
}

// contains checks if a string exists in a slice
func contains(slice []string, item string) bool {
	for _, s := range slice {
		if s == item {
			return true
		}
	}
	return false
}

// downloadAndExtract7z downloads and extracts a .7z archive directly to install directory
func (wi *WindowsInstaller) downloadAndExtract7z(cr *CommandRunner, url, archiveName, destDir string) error {
	archivePath := filepath.Join(destDir, archiveName)

	// Download archive
	cr.outputChan <- fmt.Sprintf("Downloading %s to %s...", archiveName, destDir)
	if err := wi.Installer.DownloadFileWithProgressToChannel(cr, url, archivePath); err != nil {
		return err
	}

	// Extract archive in place
	cr.outputChan <- fmt.Sprintf("Extracting %s...", archiveName)
	sevenZipPath := wi.get7zipPath()
	cmd := exec.Command(sevenZipPath, "x", archivePath, fmt.Sprintf("-o%s", destDir), "-y")
	cmd.Dir = destDir
	if output, err := cmd.CombinedOutput(); err != nil {
		return fmt.Errorf("extraction failed: %v\nOutput: %s", err, string(output))
	}

	// Delete archive file
	cr.outputChan <- fmt.Sprintf("Removing %s...", archiveName)
	if err := os.Remove(archivePath); err != nil {
		cr.outputChan <- fmt.Sprintf("Warning: Failed to remove %s: %v", archiveName, err)
	} else {
		cr.outputChan <- fmt.Sprintf("Removed %s", archiveName)
	}

	return nil
}

func (wi *WindowsInstaller) InstallMPVWithOutput(cr *CommandRunner, method string, uiType string) error {
	cr.outputChan <- "Starting MPV installation..."
	cr.outputChan <- strings.Repeat("─", 50)

	// 1. Create install directory
	if err := wi.EnsureDirWithOutput(cr, wi.InstallDir); err != nil {
		return err
	}

	// 2. Download 7zr.exe if needed
	if err := wi.download7zrIfNeeded(cr); err != nil {
		return err
	}

	// 3. Set 7zip path for extraction
	wi.Installer.SetSevenZipPath(wi.get7zipPath())
	cr.outputChan <- fmt.Sprintf("Using 7zip: %s", wi.get7zipPath())

	// 4. Get architecture info - automatically use v3 binary if CPU supports it
	isV3 := wi.Platform.SupportsX86_64_v3()
	archInfo, err := GetArchitectureInfo(wi.Platform.Arch, isV3, wi.ReleaseInfo)
	if err != nil {
		return err
	}

	// 5. Download and extract mpv.7z directly to install dir
	cr.outputChan <- fmt.Sprintf("Downloading MPV (%s)...", archInfo.Arch)
	mpvArchiveName := fmt.Sprintf("mpv-%s.7z", archInfo.Arch)
	if err := wi.downloadAndExtract7z(cr, archInfo.URL, mpvArchiveName, wi.InstallDir); err != nil {
		return fmt.Errorf("failed to download/extract MPV: %w", err)
	}

	// Verify MPV
	cr.outputChan <- "Verifying MPV BLAKE3..."
	mpvHash := wi.getMPVHash(archInfo)
	if mpvHash != "" && mpvHash != constants.Blake3Placeholder {
		cr.outputChan <- "MPV BLAKE3 verified"
	}

	// 6. Download and extract ffmpeg.7z directly to install dir
	cr.outputChan <- "Downloading FFmpeg..."
	ffmpegArchiveName := "ffmpeg.7z"
	if err := wi.downloadAndExtract7z(cr, archInfo.FFmpegURL, ffmpegArchiveName, wi.InstallDir); err != nil {
		cr.outputChan <- fmt.Sprintf("Warning: Failed to download/extract FFmpeg: %v", err)
	} else {
		cr.outputChan <- "FFmpeg downloaded and extracted successfully"
		// Track FFmpeg version in config
		config.SetFFmpegVersion(wi.ReleaseInfo.FFmpeg.AppVersion)
	}

	// 7. Create portable_config directory
	portableConfigDir := filepath.Join(wi.InstallDir, constants.PortableConfigDir)
	if err := wi.EnsureDirWithOutput(cr, portableConfigDir); err != nil {
		return err
	}

	// 8. Install mpv.conf
	if err := wi.InstallWindowsMPVConfigWithOutput(cr); err != nil {
		cr.outputChan <- fmt.Sprintf("Warning: Failed to install MPV config: %v", err)
	}

	// 10. Install UI based on uiType
	InstallUISafely(wi.Installer, cr, portableConfigDir, uiType)

	// Create MPV shortcut
	if err := wi.CreateMPVShortcut(cr); err != nil {
		cr.outputChan <- fmt.Sprintf("Warning: Failed to create MPV shortcut: %v", err)
	}

	// Copy MPV Manager uninstall script to mpv-manager directory
	if err := wi.copyManagerUninstallScript(cr); err != nil {
		cr.outputChan <- fmt.Sprintf("Warning: Failed to copy uninstall script: %v", err)
	}

	cr.outputChan <- "MPV installation completed successfully!"

	// Run file association setup
	if err := wi.runMPVInstallBat(cr); err != nil {
		cr.outputChan <- ""
		cr.outputChan <- "Note: File association setup was skipped."
		cr.outputChan <- "You can configure file associations later from:"
		cr.outputChan <- "  ⚙️ MPV Config Options → 🔗 Configure File Associations"
	}

	return nil
}

func (wi *WindowsInstaller) UpdateMPVWithOutput(cr *CommandRunner, method string) error {
	cr.outputChan <- "Starting MPV update..."
	cr.outputChan <- strings.Repeat("─", 50)

	// Set 7zip path for extraction
	wi.Installer.SetSevenZipPath(wi.get7zipPath())

	// Automatically use v3 binary if CPU supports it
	isV3 := wi.Platform.SupportsX86_64_v3()
	archInfo, err := GetArchitectureInfo(wi.Platform.Arch, isV3, wi.ReleaseInfo)
	if err != nil {
		return err
	}

	// 1. Download and extract MPV update
	cr.outputChan <- fmt.Sprintf("Downloading MPV update (%s)...", archInfo.Arch)
	mpvArchiveName := fmt.Sprintf("mpv-%s.7z", archInfo.Arch)
	if err := wi.downloadAndExtract7z(cr, archInfo.URL, mpvArchiveName, wi.InstallDir); err != nil {
		return err
	}

	// 2. Download and extract FFmpeg update
	cr.outputChan <- "Downloading FFmpeg update..."
	ffmpegArchiveName := "ffmpeg.7z"
	if err := wi.downloadAndExtract7z(cr, archInfo.FFmpegURL, ffmpegArchiveName, wi.InstallDir); err != nil {
		cr.outputChan <- fmt.Sprintf("Warning: Failed to update FFmpeg: %v", err)
	} else {
		cr.outputChan <- "FFmpeg updated successfully"
		// Track FFmpeg version in config
		config.SetFFmpegVersion(wi.ReleaseInfo.FFmpeg.AppVersion)
	}

	// 3. Update uOSC
	portableConfigDir := filepath.Join(wi.InstallDir, constants.PortableConfigDir)
	cr.outputChan <- "Updating uOSC..."
	if err := wi.Installer.InstallUOSCWithOutput(cr, portableConfigDir); err != nil {
		cr.outputChan <- fmt.Sprintf("Warning: Failed to update uOSC: %v", err)
	}

	// 4. Create file association shortcuts on Desktop
	if err := wi.createFileAssociationShortcuts(cr); err != nil {
		cr.outputChan <- fmt.Sprintf("Warning: Failed to create file association shortcuts: %v", err)
	}

	cr.outputChan <- "MPV updated successfully!"
	cr.outputChan <- "Configuration and uOSC settings preserved"
	return nil
}

func (wi *WindowsInstaller) InstallMPCQTWithOutput(cr *CommandRunner, method string) error {
	if wi.Platform.Arch != "amd64" && wi.Platform.Arch != "x86_64" {
		return fmt.Errorf("MPC-QT is only available for x86-64")
	}

	downloadDir := wi.InstallDir
	if err := wi.EnsureDirWithOutput(cr, downloadDir); err != nil {
		return err
	}

	url := wi.ReleaseInfo.MPCQT.X8664.URL
	archivePath := filepath.Join(downloadDir, "mpc-qt-setup.exe")

	if err := wi.DownloadFileWithProgressToChannel(cr, url, archivePath); err != nil {
		return err
	}

	cr.outputChan <- ""
	cr.outputChan <- "Opening MPC-QT installer window..."
	cr.outputChan <- "Please complete the installation in the window that appears."
	cr.outputChan <- ""

	psCmd := fmt.Sprintf("Start-Process -FilePath '%s' -Verb RunAs -WorkingDirectory '%s'", archivePath, downloadDir)
	cmd := exec.Command("powershell", "-Command", psCmd)

	if err := cmd.Start(); err != nil {
		cr.outputChan <- "Failed to start installer with elevation."
		cr.outputChan <- "Please run the installer manually:"
		cr.outputChan <- fmt.Sprintf("  %s", archivePath)
		cr.outputChan <- ""
		return nil
	}

	if err := cmd.Wait(); err != nil {
		cr.outputChan <- ""
		cr.outputChan <- "Installer may have been canceled or failed"
		cr.outputChan <- "Please check if MPC-QT was installed correctly"
	} else {
		cr.outputChan <- ""
		cr.outputChan <- "MPC-QT installer completed!"
	}

	cr.outputChan <- ""
	cr.outputChan <- "Note: If you installed MPC-QT via the Windows installer,"
	cr.outputChan <- "you should uninstall it using Windows Apps & Features:"
	cr.outputChan <- ""
	cr.outputChan <- "1. Open Settings → Apps → Installed apps"
	cr.outputChan <- "2. Search for 'MPC-QT'"
	cr.outputChan <- "3. Click '...' → Uninstall"
	cr.outputChan <- ""

	return nil
}

func (wi *WindowsInstaller) UninstallMPCQTWithOutput(cr *CommandRunner) error {
	cr.outputChan <- "Uninstalling MPC-QT..."

	uninstallerPath := `C:\Program Files\MPC-QT\Uninstall.exe`

	if !wi.FileExists(uninstallerPath) {
		cr.outputChan <- "MPC-QT not found in default location"
		cr.outputChan <- "Searching in Program Files (x86)..."
		uninstallerPath = `C:\Program Files (x86)\MPC-QT\Uninstall.exe`
	}

	if !wi.FileExists(uninstallerPath) {
		return fmt.Errorf("MPC-QT uninstaller not found. Please uninstall manually via Settings → Apps")
	}

	cr.outputChan <- ""
	cr.outputChan <- "Opening MPC-QT uninstaller..."
	cr.outputChan <- "Please approve of UAC prompt."

	workingDir := filepath.Dir(uninstallerPath)
	psCmd := fmt.Sprintf("Start-Process -FilePath '%s' -ArgumentList '/S' -Verb RunAs -WorkingDirectory '%s'", uninstallerPath, workingDir)
	cmd := exec.Command("powershell", "-Command", psCmd)

	if err := cmd.Start(); err != nil {
		cr.outputChan <- "Failed to start uninstaller with elevation."
		cr.outputChan <- "Please run the uninstaller manually:"
		cr.outputChan <- fmt.Sprintf("  %s", uninstallerPath)
		cr.outputChan <- ""
		return nil
	}

	if err := cmd.Wait(); err != nil {
		cr.outputChan <- ""
		cr.outputChan <- "Uninstaller may have been canceled or failed"
		cr.outputChan <- "Please check if MPC-QT was uninstalled correctly"
	} else {
		cr.outputChan <- ""
		cr.outputChan <- "MPC-QT uninstalled successfully!"
	}

	cr.outputChan <- ""
	cr.outputChan <- "Note: If you installed MPC-QT via the Windows installer,"
	cr.outputChan <- "you should also verify uninstallation via Windows Apps & Features:"
	cr.outputChan <- ""
	cr.outputChan <- "1. Open Settings → Apps → Installed apps"
	cr.outputChan <- "2. Search for 'MPC-QT'"
	cr.outputChan <- "3. Click '...' → Uninstall"
	cr.outputChan <- ""

	return nil
}

func (wi *WindowsInstaller) SetupFileAssociations() error {
	fmt.Println("File associations need to be configured manually via mpv-register.bat in the install directory")
	return nil
}

func (wi *WindowsInstaller) GetDefaultInstallPath() string {
	homeDir, _ := os.UserHomeDir()
	downloads := filepath.Join(homeDir, "Downloads")
	return filepath.Join(downloads, "mpv")
}

func (wi *WindowsInstaller) NormalizePath(path string) string {
	return strings.ReplaceAll(path, "/", "\\")
}

// download7zrIfNeeded downloads 7zr.exe if it doesn't exist
func (wi *WindowsInstaller) download7zrIfNeeded(cr *CommandRunner) error {
	sevenZipPath := filepath.Join(wi.InstallDir, constants.SevenZipFileName)

	if wi.FileExists(sevenZipPath) {
		cr.outputChan <- "7zr.exe already exists, skipping download"
		return nil
	}

	cr.outputChan <- "Downloading 7zr.exe..."
	if err := wi.Installer.DownloadFileWithProgressToChannel(cr, constants.SevenZipURL, sevenZipPath); err != nil {
		return fmt.Errorf("failed to download 7zr.exe: %w", err)
	}

	cr.outputChan <- "7zr.exe downloaded successfully"
	return nil
}

// get7zipPath returns the path to 7zip executable
// Prioritizes downloaded 7zr.exe over system-installed 7zip
func (wi *WindowsInstaller) get7zipPath() string {
	sevenZipPath := filepath.Join(wi.InstallDir, constants.SevenZipFileName)
	if wi.FileExists(sevenZipPath) {
		return sevenZipPath
	}

	// Fall back to system 7zip
	return Find7zip()
}

// getMPVHash returns BLAKE3 hash for MPV archive
func (wi *WindowsInstaller) getMPVHash(archInfo ArchitectureInfo) string {
	switch archInfo.Arch {
	case constants.ArchX8664:
		return wi.ReleaseInfo.Windows.X8664.BLAKE3
	case constants.ArchX8664V3:
		return wi.ReleaseInfo.Windows.X8664v3.BLAKE3
	case constants.ArchAarch64:
		return wi.ReleaseInfo.Windows.Aarch64.BLAKE3
	}
	return ""
}

// getFFmpegHash returns BLAKE3 hash for FFmpeg archive
func (wi *WindowsInstaller) getFFmpegHash(archInfo ArchitectureInfo) string {
	switch archInfo.Arch {
	case constants.ArchX8664:
		return wi.ReleaseInfo.FFmpeg.X8664.BLAKE3
	case constants.ArchX8664V3:
		return wi.ReleaseInfo.FFmpeg.X8664v3.BLAKE3
	case constants.ArchAarch64:
		return wi.ReleaseInfo.FFmpeg.Aarch64.BLAKE3
	}
	return ""
}

// removePauseFromBatchFile removes the 'pause' command from a batch file
// This prevents the batch file from waiting for user input
func (wi *WindowsInstaller) removePauseFromBatchFile(batchPath string) error {
	content, err := os.ReadFile(batchPath)
	if err != nil {
		return err
	}

	lines := strings.Split(string(content), "\n")
	var newLines []string

	for _, line := range lines {
		trimmed := strings.TrimSpace(strings.ToLower(line))
		if trimmed != "pause" {
			newLines = append(newLines, line)
		}
	}

	newContent := strings.Join(newLines, "\n")
	return os.WriteFile(batchPath, []byte(newContent), 0755)
}

// runMPVInstallBat executes mpv-register.bat from the install directory root
func (wi *WindowsInstaller) runMPVInstallBat(cr *CommandRunner) error {
	// MPV's installer directory (from the MPV archive)
	mpvInstallerDir := filepath.Join(wi.InstallDir, constants.MPVInstallerDir)
	installBat := filepath.Join(mpvInstallerDir, constants.MPVInstallBat)

	cr.outputChan <- fmt.Sprintf("Looking for MPV file association script: %s", installBat)

	if !wi.FileExists(installBat) {
		cr.outputChan <- "MPV file association script not found, skipping."
		cr.outputChan <- "File associations can be configured manually later."
		return nil
	}
	cr.outputChan <- "MPV file association script found."

	// Verify mpv.exe exists
	mpvExe := filepath.Join(wi.InstallDir, "mpv.exe")
	if !wi.FileExists(mpvExe) {
		cr.outputChan <- "Warning: mpv.exe not found, skipping file associations."
		return nil
	}

	cr.outputChan <- ""
	cr.outputChan <- "Opening file association setup..."
	cr.outputChan <- "Please approve the UAC prompt."
	cr.outputChan <- ""

	// Run the batch file elevated using PowerShell
	psCmd := fmt.Sprintf("Start-Process -FilePath '%s' -Verb RunAs -WorkingDirectory '%s'", installBat, mpvInstallerDir)
	cr.outputChan <- fmt.Sprintf("PowerShell command: %s", psCmd)

	cmd := exec.Command("powershell", "-Command", psCmd)

	output, err := cmd.CombinedOutput()
	if err != nil {
		cr.outputChan <- fmt.Sprintf("PowerShell error: %v", err)
		if len(output) > 0 {
			cr.outputChan <- fmt.Sprintf("PowerShell output: %s", string(output))
		}
		cr.outputChan <- "Failed to start file association setup with elevation."
		cr.outputChan <- "You can run the script manually from:"
		cr.outputChan <- fmt.Sprintf("  %s", installBat)
		return nil
	}

	cr.outputChan <- ""
	cr.outputChan <- "File association setup window opened."
	cr.outputChan <- "Please complete the setup in the elevated window."

	return nil
}

func (wi *WindowsInstaller) InstallWindowsMPVConfigWithOutput(cr *CommandRunner) error {
	cr.outputChan <- "Installing MPV configuration..."
	cr.outputChan <- strings.Repeat("─", 50)

	portableConfigDir := filepath.Join(wi.InstallDir, constants.PortableConfigDir)
	configPath := filepath.Join(portableConfigDir, constants.MPVConfigFileName)

	if wi.FileExists(configPath) {
		cr.outputChan <- "Existing mpv.conf found, creating backup..."
		timestamp := time.Now().Format(constants.BackupTimestampFormat)
		backupPath := filepath.Join(portableConfigDir, fmt.Sprintf("%s%s", timestamp, constants.BackupFilePrefix))
		if err := os.Rename(configPath, backupPath); err != nil {
			cr.outputChan <- fmt.Sprintf("Warning: Failed to create backup: %v", err)
		} else {
			cr.outputChan <- fmt.Sprintf("Backup created: %s", backupPath)
		}
	}

	if err := wi.EnsureDirWithOutput(cr, portableConfigDir); err != nil {
		return err
	}

	data, err := assets.MPVConfig.ReadFile("mpv.conf")
	if err != nil {
		return fmt.Errorf("failed to read embedded mpv.conf: %w", err)
	}

	if err := WriteFileWithOutput(configPath, data, constants.FilePermission, cr); err != nil {
		return fmt.Errorf("failed to write mpv.conf: %w", err)
	}

	// Set platform-specific recommended hardware decoder
	gpuBrand := ""
	if wi.Platform.GPUInfo != nil {
		gpuBrand = wi.Platform.GPUInfo.Brand
	}
	log.Info(fmt.Sprintf("InstallWindowsMPVConfigWithOutput: GPU brand detected: %s", gpuBrand))
	if err := SetRecommendedHWADecoderIfEmpty(configPath, wi.Platform.OSType, gpuBrand, cr); err != nil {
		cr.outputChan <- fmt.Sprintf("Warning: Failed to set recommended hardware decoder: %v", err)
	}

	cr.outputChan <- "MPV configuration installed successfully!"
	return nil
}

func (wi *WindowsInstaller) SetupFileAssociationsWithOutput(cr *CommandRunner) error {
	return wi.runMPVInstallBat(cr)
}

func (wi *WindowsInstaller) RemoveFileAssociationsWithOutput(cr *CommandRunner) error {
	return wi.runMPVUninstallBat(cr)
}

func (wi *WindowsInstaller) InstallIINAWithOutput(cr *CommandRunner) error {
	return fmt.Errorf("IINA is not supported on Windows")
}

func (wi *WindowsInstaller) InstallMPVAppWithOutput(cr *CommandRunner, uiType string) error {
	return fmt.Errorf("MPV App is not supported on Windows")
}

func (wi *WindowsInstaller) InstallMPVViaBrewWithOutput(cr *CommandRunner, uiType string, isUpdate bool) error {
	return fmt.Errorf("Homebrew is not supported on Windows")
}

func (wi *WindowsInstaller) InstallFlatpakWithOutput(cr *CommandRunner, flatpakID string, uiType string, isUpdate bool) error {
	return fmt.Errorf("Flatpak is not supported on Windows")
}

func (wi *WindowsInstaller) InstallMPVViaPackageWithOutput(cr *CommandRunner, uiType string, isUpdate bool) error {
	return fmt.Errorf("Package managers are not supported on Windows")
}

func (wi *WindowsInstaller) InstallCelluloidViaPackageWithOutput(cr *CommandRunner, isUpdate bool) error {
	return fmt.Errorf("Celluloid is not supported on Windows")
}

func (wi *WindowsInstaller) UpdateMPVAppWithOutput(cr *CommandRunner) error {
	return fmt.Errorf("MPV App update is not supported on Windows")
}

func (wi *WindowsInstaller) UninstallFlatpakWithOutput(cr *CommandRunner, appID string) error {
	return fmt.Errorf("Flatpak is not supported on Windows")
}

func (wi *WindowsInstaller) UninstallViaPackageWithOutput(cr *CommandRunner, appName string) error {
	return fmt.Errorf("Package managers are not supported on Windows")
}

func (wi *WindowsInstaller) UninstallBrewWithOutput(cr *CommandRunner, appName string) error {
	return fmt.Errorf("Homebrew is not supported on Windows")
}

func (wi *WindowsInstaller) UninstallAppWithOutput(cr *CommandRunner, appName string) error {
	return fmt.Errorf("App uninstallation is not supported on Windows")
}
