//go:build darwin

package installer

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

	"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 MacOSInstaller struct {
	*Installer
	Platform *platform.Platform
}

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

func (mi *MacOSInstaller) GetAvailableMethods(filterInstalled bool) []InstallMethod {
	methods := []InstallMethod{}

	if mi.Platform.Arch == "arm64" {
		if filterInstalled && config.IsAppInstalled("IINA") {
		} else {
			methods = append(methods, InstallMethod{
				ID:          constants.MethodIINA,
				Name:        "IINA",
				Description: "Easy and modern MPV player for macOS",
				Recommended: true,
				Priority:    1,
				Icon:        constants.MethodIcon[constants.MethodIINA],
				Logo:        constants.MethodLogo[constants.MethodIINA],
				Type:        constants.MethodType[constants.MethodIINA],
				AppType:     constants.MethodAppType[constants.MethodIINA],
				Homepage:    constants.MethodHomepage[constants.MethodIINA],
			})
		}

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

		if mi.CheckBrewInstalled() {
			if filterInstalled && config.IsAppInstalled("MPV") {
			} else {
				methods = append(methods, InstallMethod{
					ID:          constants.MethodMPVBrew,
					Name:        "MPV via Brew (CLI-only)",
					Description: "Install MPV using Homebrew package manager (CLI-only version)",
					Recommended: false,
					Priority:    3,
					Icon:        constants.MethodIcon[constants.MethodMPVBrew],
					Logo:        constants.MethodLogo[constants.MethodMPVBrew],
					Type:        constants.MethodType[constants.MethodMPVBrew],
					AppType:     constants.MethodAppType[constants.MethodMPVBrew],
					Homepage:    constants.MethodHomepage[constants.MethodMPVBrew],
				})
			}
		}
	} else {
		if filterInstalled && config.IsAppInstalled("MPV") {
		} else {
			methods = append(methods, InstallMethod{
				ID:          constants.MethodMPVApp,
				Name:        "MPV",
				Description: "Official MPV application bundle",
				Recommended: false,
				Priority:    2,
				Icon:        constants.MethodIcon[constants.MethodMPVApp],
				Logo:        constants.MethodLogo[constants.MethodMPVApp],
				Type:        constants.MethodType[constants.MethodMPVApp],
				AppType:     constants.MethodAppType[constants.MethodMPVApp],
				Homepage:    constants.MethodHomepage[constants.MethodMPVApp],
			})
		}

		if mi.CheckBrewInstalled() {
			if filterInstalled && config.IsAppInstalled("MPV") {
			} else {
				methods = append(methods, InstallMethod{
					ID:          constants.MethodMPVBrew,
					Name:        "MPV via Brew (CLI-only)",
					Description: "Install MPV using Homebrew package manager (CLI-only version)",
					Recommended: false,
					Priority:    3,
					Icon:        constants.MethodIcon[constants.MethodMPVBrew],
					Logo:        constants.MethodLogo[constants.MethodMPVBrew],
					Type:        constants.MethodType[constants.MethodMPVBrew],
					AppType:     constants.MethodAppType[constants.MethodMPVBrew],
					Homepage:    constants.MethodHomepage[constants.MethodMPVBrew],
				})
			}
		}
	}

	return methods
}

func (mi *MacOSInstaller) CheckBrewInstalled() bool {
	_, err := exec.LookPath("brew")
	return err == nil
}

func (mi *MacOSInstaller) InstallBrew() error {
	log.Info("Starting Homebrew installation...")
	cmd := exec.Command("/bin/bash", "-c", "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)")
	if err := cmd.Run(); err != nil {
		log.Error("Homebrew installation failed: " + err.Error())
		return err
	}
	log.Info("Homebrew installed successfully")
	return nil
}

func (mi *MacOSInstaller) InstallMPVViaBrew(installUOSC bool) error {
	if !mi.CheckBrewInstalled() {
		log.Error("Homebrew is not installed, cannot install MPV via brew")
		return fmt.Errorf("homebrew is not installed")
	}

	log.Info("Installing MPV via Homebrew...")
	cmd := exec.Command("brew", "install", "mpv")
	if err := cmd.Run(); err != nil {
		log.Error("MPV installation via Homebrew failed: " + err.Error())
		return err
	}
	log.Info("MPV installed successfully via Homebrew")
	return nil
}

func (mi *MacOSInstaller) InstallMPVApp(installUOSC bool) error {
	var url string

	if mi.Platform.Arch == "arm64" {
		url = mi.ReleaseInfo.MacOS.ARMLatest.URL
	} else {
		url = mi.ReleaseInfo.MacOS.Intel15.URL
	}

	downloadDir := mi.InstallDir
	if err := DownloadAndExtract(mi.Installer, nil, url, downloadDir, "mpv.zip"); err != nil {
		log.Error("Failed to download and extract MPV: " + err.Error())
		return err
	}

	appPath := filepath.Join(downloadDir, "mpv.app")
	if !mi.FileExists(appPath) {
		log.Error("mpv.app not found in extracted archive")
		return fmt.Errorf("mpv.app not found in extracted archive")
	}

	if err := os.MkdirAll("/Applications", 0755); err != nil {
		log.Error("Failed to create /Applications directory: " + err.Error())
		return err
	}

	log.Info("Copying MPV.app to /Applications...")
	cmd := exec.Command("cp", "-R", appPath, "/Applications/")
	if err := cmd.Run(); err != nil {
		log.Error("Failed to copy MPV.app to /Applications: " + err.Error())
		return err
	}
	log.Info("MPV.app installed successfully")
	return nil
}

func (mi *MacOSInstaller) InstallIINA() error {
	var url string

	if mi.Platform.Arch == "arm64" {
		url = mi.ReleaseInfo.IINA.ARM.URL
	} else {
		url = mi.ReleaseInfo.IINA.Intel.URL
	}

	tempDir, _ := os.MkdirTemp("", "iina-install")
	archivePath := filepath.Join(tempDir, "IINA.dmg")

	fmt.Println("Downloading IINA...")
	if err := mi.DownloadFileWithProgress(url, archivePath, 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 {
		return err
	}
	fmt.Println()

	fmt.Println("Mounting DMG file...")
	cmd := exec.Command("hdiutil", "attach", "-nobrowse", "-noautoopen", "-quiet", archivePath)
	output, err := cmd.CombinedOutput()
	if err != nil {
		fmt.Printf("Failed to mount DMG: %v\n%s\n", err, string(output))
		os.RemoveAll(tempDir)
		return err
	}

	volumePath := "/Volumes/IINA"
	if _, err := os.Stat(volumePath); err != nil {
		fmt.Printf("DMG volume not found at %s\n", volumePath)
		os.RemoveAll(tempDir)
		return fmt.Errorf("DMG volume not found")
	}

	fmt.Println("Copying IINA.app to /Applications...")
	srcAppPath := filepath.Join(volumePath, "IINA.app")
	destAppPath := "/Applications/IINA.app"

	cmd = exec.Command("cp", "-R", srcAppPath, destAppPath)
	if err := cmd.Run(); err != nil {
		fmt.Printf("Failed to copy IINA.app: %v\n", err)
		mi.unmountDMG(volumePath)
		os.RemoveAll(tempDir)
		return err
	}

	fmt.Println("Unmounting DMG...")
	if err := mi.unmountDMG(volumePath); err != nil {
		fmt.Printf("Warning: Failed to unmount DMG: %v\n", err)
	}

	fmt.Println("Cleaning up...")
	os.RemoveAll(tempDir)

	fmt.Println("IINA installed successfully!")
	return nil
}

func (mi *MacOSInstaller) unmountDMG(volumePath string) error {
	cmd := exec.Command("hdiutil", "detach", "-force", volumePath)
	_, err := cmd.CombinedOutput()
	return err
}

func (mi *MacOSInstaller) CheckInstalled() bool {
	appPath := "/Applications/MPV.app"
	return mi.FileExists(appPath)
}

func (mi *MacOSInstaller) CanUpdate() bool {
	return mi.CheckInstalled()
}

func (mi *MacOSInstaller) GetInstalledVersion() string {
	cmd := exec.Command("mpv", "--version")
	output, err := cmd.Output()
	if err != nil {
		return ""
	}
	lines := strings.Split(string(output), "\n")
	if len(lines) > 0 {
		return strings.TrimPrefix(lines[0], "mpv ")
	}
	return ""
}

func (mi *MacOSInstaller) Uninstall() error {
	mpvAppPath := "/Applications/MPV.app"
	if _, err := os.Stat(mpvAppPath); err == nil {
		fmt.Printf("Removing %s...\n", mpvAppPath)
		if err := os.RemoveAll(mpvAppPath); err != nil {
			fmt.Printf("Warning: Failed to remove MPV.app: %v\n", err)
		} else {
			fmt.Println("Successfully removed MPV.app")
		}
	}

	iinaAppPath := "/Applications/IINA.app"
	if _, err := os.Stat(iinaAppPath); err == nil {
		fmt.Printf("Removing %s...\n", iinaAppPath)
		if err := os.RemoveAll(iinaAppPath); err != nil {
			fmt.Printf("Warning: Failed to remove IINA.app: %v\n", err)
		} else {
			fmt.Println("Successfully removed IINA.app")
		}
	}

	if _, err := os.Stat(mi.InstallDir); err == nil {
		fmt.Printf("Removing install directory: %s\n", mi.InstallDir)
		if err := os.RemoveAll(mi.InstallDir); err != nil {
			fmt.Printf("Warning: Failed to remove install directory: %v\n", err)
		}
	}

	return nil
}

func (mi *MacOSInstaller) UninstallFlatpak(appID string) error {
	cmd := exec.Command("flatpak", "uninstall", "-y", appID)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	return cmd.Run()
}

func (mi *MacOSInstaller) UninstallBrew(appName string) error {
	if _, err := exec.LookPath("brew"); err != nil {
		return fmt.Errorf("homebrew is not installed")
	}
	cmd := exec.Command("brew", "uninstall", appName)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	return cmd.Run()
}

func (mi *MacOSInstaller) UninstallApp(appName string) error {
	appPath := fmt.Sprintf("/Applications/%s.app", appName)
	if _, err := os.Stat(appPath); err != nil {
		return fmt.Errorf("%s not found: %w", appPath, err)
	}
	return os.RemoveAll(appPath)
}

func (mi *MacOSInstaller) InstallMPVViaBrewWithOutput(cr *CommandRunner, uiType string, isUpdate bool) error {
	if !mi.CheckBrewInstalled() {
		return fmt.Errorf("homebrew is not installed")
	}

	if isUpdate {
		cr.outputChan <- "Updating Homebrew packages..."
		cmd := cr.RunCommand("brew", "update")
		if cmd != nil {
			log.Warn("Failed to update Homebrew packages, continuing...")
		}

		cr.outputChan <- "Updating MPV via Homebrew..."
		cmd = cr.RunCommand("brew", "upgrade", "mpv")
		if cmd != nil {
			return cmd
		}

		cr.outputChan <- constants.MsgConfigNotOverwritten
		return nil
	}

	cr.outputChan <- "Installing MPV via Homebrew..."
	cmd := cr.RunCommand("brew", "install", "mpv")
	if err := cmd; err != nil {
		return err
	}

	configDir, err := GetMPVConfigDir()
	if err != nil {
		cr.outputChan <- fmt.Sprintf("Warning: Failed to get MPV config dir: %v", err)
	} else {
		InstallUISafely(mi.Installer, cr, configDir, uiType)
	}

	if err := InstallMPVConfigWithPlatformDefaults(mi.Installer, cr, mi.Platform.OSType, mi.Platform.GPUInfo.Brand); err != nil {
		cr.outputChan <- fmt.Sprintf("Warning: Failed to install MPV config: %v", err)
	}

	return nil
}

func (mi *MacOSInstaller) UpdateMPVAppWithOutput(cr *CommandRunner) error {
	var url string

	if mi.Platform.Arch == "arm64" {
		url = mi.ReleaseInfo.MacOS.ARMLatest.URL
	} else {
		url = mi.ReleaseInfo.MacOS.Intel15.URL
	}

	tempDir, _ := os.MkdirTemp("", "mpv-update")
	defer os.RemoveAll(tempDir)

	cr.outputChan <- "Downloading MPV.app update..."
	if err := DownloadAndExtract(mi.Installer, cr, url, tempDir, "mpv.zip"); err != nil {
		return err
	}

	cr.outputChan <- "Removing old MPV.app..."
	os.RemoveAll("/Applications/MPV.app")

	cr.outputChan <- "Copying MPV.app to /Applications..."
	cmd := cr.RunCommand("cp", "-R", filepath.Join(tempDir, "MPV.app"), "/Applications/")
	if err := cmd; err != nil {
		return err
	}

	cr.outputChan <- "MPV.app updated successfully!"
	cr.outputChan <- constants.MsgConfigNotOverwritten
	return nil
}

func (mi *MacOSInstaller) InstallMPVAppWithOutput(cr *CommandRunner, uiType string) error {
	var url string

	if mi.Platform.Arch == "arm64" {
		url = mi.ReleaseInfo.MacOS.ARMLatest.URL
	} else {
		url = mi.ReleaseInfo.MacOS.Intel15.URL
	}

	tempDir, _ := os.MkdirTemp("", "mpv-install")
	defer os.RemoveAll(tempDir)

	if err := DownloadAndExtract(mi.Installer, cr, url, tempDir, "mpv.zip"); err != nil {
		return err
	}

	tarGzPath := filepath.Join(tempDir, "mpv.tar.gz")
	if _, err := os.Stat(tarGzPath); err == nil {
		cr.outputChan <- "Extracting nested tar.gz archive..."
		if err := mi.ExtractArchiveWithOutput(cr, tarGzPath, tempDir); err != nil {
			return err
		}
	}

	appPath := filepath.Join(tempDir, "mpv.app")
	if _, err := os.Stat(appPath); err != nil {
		return fmt.Errorf("mpv.app not found in extracted archive")
	}

	// Remove existing MPV.app if present (avoids permission issues during copy)
	// macOS filesystem is case-insensitive, so this handles both mpv.app and MPV.app
	existingApp := "/Applications/mpv.app"
	if _, err := os.Stat(existingApp); err == nil {
		cr.outputChan <- "Removing existing MPV.app..."
		if err := os.RemoveAll(existingApp); err != nil {
			cr.outputChan <- fmt.Sprintf("Warning: Failed to remove existing MPV.app: %v", err)
			// Try using rm -rf as fallback
			rmCmd := exec.Command("rm", "-rf", existingApp)
			if rmErr := rmCmd.Run(); rmErr != nil {
				cr.outputChan <- fmt.Sprintf("Warning: rm -rf also failed: %v", rmErr)
			}
		}
	}

	cr.outputChan <- "Installing MPV.app to /Applications..."
	cmd := cr.RunCommand("cp", "-R", appPath, "/Applications/")
	if err := cmd; err != nil {
		return err
	}

	configDir, err := GetMPVConfigDir()
	if err != nil {
		cr.outputChan <- fmt.Sprintf("Warning: Failed to get MPV config dir: %v", err)
	} else {
		InstallUISafely(mi.Installer, cr, configDir, uiType)
	}

	// Install MPV config with platform-specific defaults
	if err := InstallMPVConfigWithPlatformDefaults(mi.Installer, cr, mi.Platform.OSType, mi.Platform.GPUInfo.Brand); err != nil {
		cr.outputChan <- fmt.Sprintf("Warning: Failed to install MPV config: %v", err)
	}

	cr.outputChan <- "MPV.app installed to /Applications/"

	return nil
}

func (mi *MacOSInstaller) InstallIINAWithOutput(cr *CommandRunner) error {
	var url string

	if mi.Platform.Arch == "arm64" {
		url = mi.ReleaseInfo.IINA.ARM.URL
	} else {
		url = mi.ReleaseInfo.IINA.Intel.URL
	}

	tempDir, _ := os.MkdirTemp("", "iina-install")
	defer os.RemoveAll(tempDir)
	archivePath := filepath.Join(tempDir, "IINA.dmg")

	cr.outputChan <- "Downloading IINA..."
	if err := mi.DownloadFileWithProgressToChannel(cr, url, archivePath); err != nil {
		return err
	}

	cr.outputChan <- "Mounting DMG file..."
	cmd := exec.Command("hdiutil", "attach", "-nobrowse", "-noautoopen", "-quiet", archivePath)
	output, err := cmd.CombinedOutput()
	if err != nil {
		cr.outputChan <- fmt.Sprintf("Error: Failed to mount DMG: %v", err)
		cr.outputChan <- string(output)
		return err
	}

	volumePath := "/Volumes/IINA"
	if _, err := os.Stat(volumePath); err != nil {
		cr.outputChan <- "Error: DMG volume not found at /Volumes/IINA"
		return fmt.Errorf("DMG volume not found")
	}

	cr.outputChan <- "Copying IINA.app to /Applications..."
	srcAppPath := filepath.Join(volumePath, "IINA.app")
	destAppPath := "/Applications/IINA.app"

	// Remove existing IINA.app if present (avoids permission issues during copy)
	if _, err := os.Stat(destAppPath); err == nil {
		cr.outputChan <- "Removing existing IINA.app..."
		if err := os.RemoveAll(destAppPath); err != nil {
			cr.outputChan <- fmt.Sprintf("Warning: Failed to remove existing IINA.app: %v", err)
		}
	}

	cmd = exec.Command("cp", "-R", srcAppPath, destAppPath)
	if err := cmd.Run(); err != nil {
		cr.outputChan <- fmt.Sprintf("Error: Failed to copy IINA.app: %v", err)
		mi.unmountDMG(volumePath)
		return err
	}

	cr.outputChan <- "Unmounting DMG..."
	if err := mi.unmountDMG(volumePath); err != nil {
		cr.outputChan <- fmt.Sprintf("Warning: Failed to unmount DMG: %v", err)
	}

	cr.outputChan <- "IINA installed successfully!"

	return nil
}

func (mi *MacOSInstaller) GetDefaultInstallPath() string {
	homeDir, _ := os.UserHomeDir()
	return filepath.Join(homeDir, "Downloads")
}

func (mi *MacOSInstaller) NormalizePath(path string) string {
	return filepath.Clean(path)
}

func (mi *MacOSInstaller) UninstallFlatpakWithOutput(cr *CommandRunner, appID string) error {
	cr.outputChan <- fmt.Sprintf("Uninstalling %s via Flatpak...", appID)
	cmd := cr.RunCommand("flatpak", "uninstall", "-y", appID)
	if err := cmd; err != nil {
		return fmt.Errorf("flatpak uninstall failed: %w", err)
	}
	return nil
}

func (mi *MacOSInstaller) UninstallBrewWithOutput(cr *CommandRunner, appName string) error {
	if _, err := exec.LookPath("brew"); err != nil {
		return fmt.Errorf("homebrew is not installed")
	}
	cr.outputChan <- fmt.Sprintf("Uninstalling %s via Homebrew...", appName)
	cmd := cr.RunCommand("brew", "uninstall", appName)
	if err := cmd; err != nil {
		return fmt.Errorf("brew uninstall failed: %w", err)
	}
	return nil
}

func (mi *MacOSInstaller) UninstallAppWithOutput(cr *CommandRunner, appName string) error {
	appPath := fmt.Sprintf("/Applications/%s.app", appName)
	cr.outputChan <- fmt.Sprintf("Uninstalling %s...", appName)

	if _, err := os.Stat(appPath); err != nil {
		return fmt.Errorf("%s not found: %w", appPath, err)
	}

	cr.outputChan <- fmt.Sprintf("Removing: %s", appPath)
	if err := os.RemoveAll(appPath); err != nil {
		return fmt.Errorf("failed to remove %s: %w", appPath, err)
	}

	cr.outputChan <- fmt.Sprintf("Successfully removed %s", appName)
	return nil
}

func (mi *MacOSInstaller) InstallCelluloidViaPackageWithOutput(cr *CommandRunner, isUpdate bool) error {
	return fmt.Errorf("Celluloid is not supported on macOS")
}

func (mi *MacOSInstaller) InstallMPCQTWithOutput(cr *CommandRunner, method string) error {
	return fmt.Errorf("MPC-QT is not supported on macOS")
}

func (mi *MacOSInstaller) InstallMPVWithOutput(cr *CommandRunner, method string, uiType string) error {
	return fmt.Errorf("MPV binary installation is not supported on macOS")
}

func (mi *MacOSInstaller) UpdateMPVWithOutput(cr *CommandRunner, method string) error {
	return fmt.Errorf("MPV binary update is not supported on macOS")
}

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

func (mi *MacOSInstaller) SetupFileAssociationsWithOutput(cr *CommandRunner) error {
	return fmt.Errorf("File associations are not supported on macOS")
}

func (mi *MacOSInstaller) CreateInstallerShortcutWithOutput(cr *CommandRunner) error {
	cr.outputChan <- "Installer shortcut creation not needed on macOS"
	return nil
}

func (mi *MacOSInstaller) CreateInstallerShortcutToBinaryWithOutput(cr *CommandRunner, binaryPath string) error {
	cr.outputChan <- "Installer shortcut creation not needed on macOS"
	return nil
}

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

func (mi *MacOSInstaller) InstallMPVViaPackageWithOutput(cr *CommandRunner, uiType string, isUpdate bool) error {
	return fmt.Errorf("Package manager installation is not supported on macOS")
}

// UninstallWithOutput is a no-op on macOS
func (mi *MacOSInstaller) UninstallWithOutput(cr *CommandRunner) error {
	return fmt.Errorf("MPV binary uninstall is not supported on macOS")
}

// UninstallMPCQTWithOutput is a no-op on macOS (MPC-QT is Windows-only)
func (mi *MacOSInstaller) UninstallMPCQTWithOutput(cr *CommandRunner) error {
	return fmt.Errorf("MPC-QT uninstall is not supported on macOS")
}

// RemoveFileAssociationsWithOutput is a no-op on macOS
func (mi *MacOSInstaller) RemoveFileAssociationsWithOutput(cr *CommandRunner) error {
	return fmt.Errorf("File associations are not supported on macOS")
}

// CreateMPVShortcut is a no-op on macOS (shortcuts are not needed)
func (mi *MacOSInstaller) CreateMPVShortcut(cr *CommandRunner) error {
	return fmt.Errorf("MPV shortcuts are not supported on macOS")
}
