package installer

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

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

// ArchitectureInfo contains architecture details and URLs
type ArchitectureInfo struct {
	Arch      string
	URL       string
	FFmpegURL string
}

// Find7zip finds an available 7zip executable
func Find7zip() string {
	for _, exe := range constants.SevenZipExecutables {
		if _, err := exec.LookPath(exe); err == nil {
			return exe
		}
	}
	return ""
}

// Check7zipAvailable returns true if 7zip is available
func Check7zipAvailable() bool {
	return Find7zip() != ""
}

// GetArchitectureInfo returns architecture details for Windows install
func GetArchitectureInfo(platformArch string, isV3 bool, releaseInfo ReleaseInfo) (ArchitectureInfo, error) {
	switch platformArch {
	case constants.ArchAMD64, constants.ArchX8664:
		if isV3 {
			return ArchitectureInfo{
				Arch:      constants.ArchX8664V3,
				URL:       releaseInfo.Windows.X8664v3.URL,
				FFmpegURL: releaseInfo.FFmpeg.X8664v3.URL,
			}, nil
		}
		return ArchitectureInfo{
			Arch:      constants.ArchX8664,
			URL:       releaseInfo.Windows.X8664.URL,
			FFmpegURL: releaseInfo.FFmpeg.X8664.URL,
		}, nil
	case constants.ArchARM64:
		return ArchitectureInfo{
			Arch:      constants.ArchAarch64,
			URL:       releaseInfo.Windows.Aarch64.URL,
			FFmpegURL: releaseInfo.FFmpeg.Aarch64.URL,
		}, nil
	default:
		return ArchitectureInfo{}, fmt.Errorf("unsupported architecture: %s (i686/32-bit builds are no longer available)", platformArch)
	}
}

// GetMacOSURL returns the appropriate URL for macOS based on architecture
func GetMacOSURL(arch string, isMPV bool, releaseInfo ReleaseInfo) (string, error) {
	if isMPV {
		if arch == constants.ArchARM64 {
			return releaseInfo.MacOS.ARMLatest.URL, nil
		}
		return releaseInfo.MacOS.Intel15.URL, nil
	}

	// IINA
	if arch == constants.ArchARM64 {
		return releaseInfo.IINA.ARM.URL, nil
	}
	return releaseInfo.IINA.Intel.URL, nil
}

// GetHomeDir returns the user's home directory
func GetHomeDir() (string, error) {
	return constants.GetHomeDir()
}

// GetMPVConfigPath returns the path to mpv.conf (Unix)
func GetMPVConfigPath() (string, error) {
	return constants.GetMPVConfigPathWithHome()
}

// GetMPVConfigDir returns the path to mpv config directory
func GetMPVConfigDir() (string, error) {
	configPath, err := constants.GetMPVConfigPathWithHome()
	if err != nil {
		return "", err
	}
	return filepath.Dir(configPath), nil
}

// GetConfigBackupDir returns the path to config backups directory
func GetConfigBackupDir() (string, error) {
	mpvDir, err := GetMPVConfigDir()
	if err != nil {
		return "", err
	}
	return filepath.Join(mpvDir, constants.ConfigBackupsDir), nil
}

// CreateBackup creates a timestamped backup of a file
func CreateBackup(filePath string, cr *CommandRunner) error {
	if _, err := os.Stat(filePath); os.IsNotExist(err) {
		return nil
	}

	if cr != nil {
		cr.outputChan <- "Existing config found, creating backup..."
	}

	fileDir := filepath.Dir(filePath)
	timestamp := time.Now().Format(constants.BackupTimestampFormat)
	backupPath := filepath.Join(fileDir, timestamp+constants.BackupFilePrefix)

	if err := os.Rename(filePath, backupPath); err != nil {
		if cr != nil {
			cr.outputChan <- fmt.Sprintf("%s: %v", constants.WarnBackupFailed, err)
		}
		return err
	}

	if cr != nil {
		cr.outputChan <- fmt.Sprintf("Backup created: %s", backupPath)
	}

	return nil
}

// CreateFullBackup creates a backup in the conf_backups directory with full timestamp
func CreateFullBackup(filePath string, cr *CommandRunner) (string, error) {
	backupDir, err := GetConfigBackupDir()
	if err != nil {
		return "", err
	}

	if err := os.MkdirAll(backupDir, constants.DirPermission); err != nil {
		return "", err
	}

	timestamp := time.Now().Format(constants.BackupTimestampFullFormat)
	backupFileName := timestamp + constants.BackupDirFilePrefix
	backupPath := filepath.Join(backupDir, backupFileName)

	if cr != nil {
		cr.outputChan <- fmt.Sprintf("Creating backup: %s", backupPath)
	}

	if err := copyFile(filePath, backupPath, constants.FilePermission); err != nil {
		return "", err
	}

	if cr != nil {
		cr.outputChan <- "Backup created successfully!"
	}

	return backupPath, nil
}

// RestoreBackup restores a backup file to the config location
func RestoreBackup(backupPath, configPath string, cr *CommandRunner) error {
	// Validate backup path before proceeding
	if err := config.ValidateBackupPath(backupPath); err != nil {
		return fmt.Errorf("invalid backup path: %w", err)
	}

	if _, err := os.Stat(backupPath); os.IsNotExist(err) {
		return fmt.Errorf("backup file not found: %s", backupPath)
	}

	// Create backup of current config first
	if _, err := os.Stat(configPath); err == nil {
		if cr != nil {
			cr.outputChan <- "Existing config found, creating backup..."
		}
		backupOfCurrent := filepath.Join(filepath.Dir(configPath),
			time.Now().Format(constants.BackupTimestampFormat)+constants.BackupFilePrefix)
		if err := os.Rename(configPath, backupOfCurrent); err != nil {
			if cr != nil {
				cr.outputChan <- fmt.Sprintf("Warning: Failed to create backup: %v", err)
			}
		} else {
			if cr != nil {
				cr.outputChan <- fmt.Sprintf("Current config backed up to: %s", backupOfCurrent)
			}
		}
	}

	if err := copyFile(backupPath, configPath, constants.FilePermission); err != nil {
		return err
	}

	return nil
}

// copyFile copies a file from src to dst
func copyFile(src, dst string, perm os.FileMode) error {
	data, err := os.ReadFile(src)
	if err != nil {
		return err
	}

	if err := os.WriteFile(dst, data, perm); err != nil {
		return err
	}

	return nil
}

// CommandExists checks if a command is available in PATH
func CommandExists(name string) bool {
	_, err := exec.LookPath(name)
	return err == nil
}

// GetPrivilegePrefix returns sudo prefix if needed for Linux
func GetPrivilegePrefix() []string {
	if os.Geteuid() == 0 {
		return []string{}
	}
	if CommandExists(constants.CommandSudo) {
		return []string{constants.CommandSudo}
	}
	return []string{}
}

// IsPackageNotInstalledError checks if an error indicates a package is not installed
func IsPackageNotInstalledError(err error) bool {
	if err == nil {
		return false
	}

	errMsg := strings.ToLower(err.Error())
	for _, pattern := range constants.PackageNotFoundPatterns {
		if strings.Contains(errMsg, pattern) {
			return true
		}
	}

	return false
}

// IsPackageNotInstalledInOutput checks if output indicates a package is not installed
func IsPackageNotInstalledInOutput(output []string) bool {
	combinedOutput := strings.ToLower(strings.Join(output, " "))
	for _, pattern := range constants.PackageNotFoundPatterns {
		if strings.Contains(combinedOutput, pattern) {
			return true
		}
	}
	return false
}

// IsMPVInstall checks if the given ID is an MPV installation
func IsMPVInstall(installID string) bool {
	return installID == constants.FlatpakMPV ||
		installID == constants.PackageMPV
}

// GetMethodDisplayName returns the display name for a method ID
func GetMethodDisplayName(methodID string) string {
	if name, ok := constants.MethodIDToName[methodID]; ok {
		return name
	}
	return methodID
}

// GetAppIDFromMethodID returns the Flatpak/app ID for a method
func GetAppIDFromMethodID(methodID string) string {
	if appID, ok := constants.MethodIDToAppID[methodID]; ok {
		return appID
	}
	return methodID
}

// EnsureDirectory creates a directory if it doesn't exist
func EnsureDirectory(path string, cr *CommandRunner) error {
	if err := os.MkdirAll(path, constants.DirPermission); err != nil {
		if cr != nil {
			cr.outputChan <- fmt.Sprintf("Error: Failed to create directory: %s", path)
		}
		return err
	}
	if cr != nil {
		cr.outputChan <- fmt.Sprintf("Created directory: %s", path)
	}
	return nil
}

// WriteFileWithOutput writes a file with logging
func WriteFileWithOutput(path string, data []byte, perm os.FileMode, cr *CommandRunner) error {
	if cr != nil {
		cr.outputChan <- fmt.Sprintf("Writing to: %s", path)
	}

	if err := os.WriteFile(path, data, perm); err != nil {
		if cr != nil {
			cr.outputChan <- fmt.Sprintf("Error: Failed to write file: %v", err)
		}
		return err
	}

	if cr != nil {
		cr.outputChan <- "Write successful"
	}

	return nil
}

// CopyFileWithOutput copies a file with logging
func CopyFileWithOutput(src, dst string, cr *CommandRunner) error {
	if cr != nil {
		cr.outputChan <- fmt.Sprintf("Copying: %s -> %s", src, dst)
	}

	if err := copyFile(src, dst, constants.FilePermission); err != nil {
		if cr != nil {
			cr.outputChan <- fmt.Sprintf("Error: Failed to copy: %v", err)
		}
		return err
	}

	if cr != nil {
		cr.outputChan <- "Copied successfully"
	}

	return nil
}

// LogWarning logs a warning to both output channel and log
func LogWarning(message string, cr *CommandRunner) {
	if cr != nil {
		cr.outputChan <- message
	}
	log.Warn(message)
}

// LogError logs an error to both output channel and log
func LogError(err error, cr *CommandRunner) {
	if cr != nil {
		cr.outputChan <- fmt.Sprintf("Error: %v", err)
	}
	log.Error(err.Error())
}

// RemoveAll is a wrapper for os.RemoveAll with logging
func RemoveAll(path string) error {
	if _, err := os.Stat(path); err != nil {
		return nil
	}
	return os.RemoveAll(path)
}

// InstallUOSCSafely installs uOSC with safe error handling
// Logs warning instead of failing if uOSC installation fails
func InstallUOSCSafely(installer *Installer, cr *CommandRunner, configDir string) {
	if err := installer.InstallUOSCWithOutput(cr, configDir); err != nil {
		cr.outputChan <- fmt.Sprintf("Warning: Failed to install uOSC: %v", err)
	}
}

// InstallMPVConfigSafely installs MPV config with safe error handling
// Logs warning instead of failing if config installation fails
func InstallMPVConfigSafely(installer *Installer, cr *CommandRunner) {
	if err := installer.InstallMPVConfigWithOutput(cr); err != nil {
		cr.outputChan <- fmt.Sprintf("Warning: Failed to install MPV config: %v", err)
	}
}

// InstallUISafely installs the specified UI type with safe error handling
// Also sets the appropriate osc= setting in mpv.conf
// - UITypeUOSC: installs uOSC and sets osc=yes
// - UITypeModernZ: installs ModernZ and sets osc=no
// - UITypeNone: removes all UI files and osc= setting
func InstallUISafely(installer *Installer, cr *CommandRunner, configDir string, uiType string) {
	// First, remove all existing UI files to prevent conflicts
	cr.outputChan <- "Removing any existing UI files..."
	RemoveAllUIFiles(configDir)

	switch uiType {
	case constants.UITypeModernZ:
		if err := installer.InstallModernZWithOutput(cr, configDir); err != nil {
			cr.outputChan <- fmt.Sprintf("%s: %v", constants.WarnFailedToInstallModernZ, err)
			return
		}
		// ModernZ requires osc=no
		mpvConfPath := filepath.Join(configDir, constants.MPVConfigFileName)
		if err := installer.SetOSCConfig(cr, mpvConfPath, "no"); err != nil {
			cr.outputChan <- fmt.Sprintf("Warning: Failed to set osc=no for ModernZ: %v", err)
		}
	case constants.UITypeUOSC:
		cr.outputChan <- "Installing uOSC (modern OSC UI for MPV)..."
		if err := installer.InstallUOSCWithOutput(cr, configDir); err != nil {
			cr.outputChan <- fmt.Sprintf("Warning: Failed to install uOSC: %v", err)
			return
		}
		// uOSC requires osc=yes
		mpvConfPath := filepath.Join(configDir, constants.MPVConfigFileName)
		if err := installer.SetOSCConfig(cr, mpvConfPath, "yes"); err != nil {
			cr.outputChan <- fmt.Sprintf("Warning: Failed to set osc=yes for uOSC: %v", err)
		}
	case constants.UITypeNone:
		cr.outputChan <- "UI type set to none - using MPV's default interface"
		// Remove thumbfast.lua since it's only needed for UOSC/ModernZ
		if err := RemoveThumbfast(configDir); err != nil {
			cr.outputChan <- fmt.Sprintf("Note: thumbfast.lua not found (already removed)")
		} else {
			cr.outputChan <- "Removed thumbfast.lua"
		}
		// Set osc=yes to enable MPV's default OSC
		mpvConfPath := filepath.Join(configDir, constants.MPVConfigFileName)
		if err := installer.SetOSCDefault(cr, mpvConfPath); err != nil {
			cr.outputChan <- fmt.Sprintf("Warning: Failed to set osc=yes: %v", err)
		}
	default:
		// Default to ModernZ
		if err := installer.InstallModernZWithOutput(cr, configDir); err != nil {
			cr.outputChan <- fmt.Sprintf("%s: %v", constants.WarnFailedToInstallModernZ, err)
			return
		}
		// ModernZ requires osc=no
		mpvConfPath := filepath.Join(configDir, constants.MPVConfigFileName)
		if err := installer.SetOSCConfig(cr, mpvConfPath, "no"); err != nil {
			cr.outputChan <- fmt.Sprintf("Warning: Failed to set osc=no for ModernZ: %v", err)
		}
	}
}

// RemoveUIFiles removes UI files for the specified UI type
// This should be called before switching to a different UI type
// - UITypeUOSC: removes uosc.lua, uosc.conf, fonts/uosc/
// - UITypeModernZ: removes modernz.lua, modernz.conf, fonts/modernz-icons.ttf
// Also cleans up legacy font files (material-design-icons.ttf, fluent-system-icons.ttf) from pre-0.3.1 installs
func RemoveUIFiles(configDir string, uiType string) error {
	scriptsDir := filepath.Join(configDir, constants.ScriptsDir)
	scriptOptsDir := filepath.Join(configDir, constants.ScriptOptsDir)
	fontsDir := filepath.Join(configDir, constants.FontsDir)

	switch uiType {
	case constants.UITypeUOSC:
		// Remove uOSC files
		filesToRemove := []string{
			filepath.Join(scriptsDir, "uosc.lua"),
			filepath.Join(scriptOptsDir, "uosc.conf"),
		}
		dirsToRemove := []string{
			filepath.Join(scriptsDir, "uosc"),
			filepath.Join(fontsDir, "uosc"),
		}
		for _, f := range filesToRemove {
			os.Remove(f) // Ignore errors - file may not exist
		}
		for _, d := range dirsToRemove {
			os.RemoveAll(d) // Ignore errors - directory may not exist
		}

	case constants.UITypeModernZ:
		// Remove ModernZ files (current)
		filesToRemove := []string{
			filepath.Join(scriptsDir, "modernz.lua"),
			filepath.Join(scriptOptsDir, "modernz.conf"),
			filepath.Join(fontsDir, constants.ModernZFontFile),
			// Legacy font files from pre-0.3.1 ModernZ releases
			filepath.Join(fontsDir, "material-design-icons.ttf"),
			filepath.Join(fontsDir, "fluent-system-icons.ttf"),
		}
		for _, f := range filesToRemove {
			os.Remove(f) // Ignore errors - file may not exist
		}
	}

	return nil
}

// RemoveAllUIFiles removes all known UI files (both uOSC and ModernZ)
// This is useful when switching UI types to ensure a clean slate
func RemoveAllUIFiles(configDir string) error {
	_ = RemoveUIFiles(configDir, constants.UITypeUOSC)
	_ = RemoveUIFiles(configDir, constants.UITypeModernZ)
	return nil
}

// RemoveThumbfast removes the thumbfast.lua script from the scripts directory
// This should be called when switching to "No UI" since thumbfast is only needed for UOSC/ModernZ
func RemoveThumbfast(configDir string) error {
	scriptsDir := filepath.Join(configDir, constants.ScriptsDir)
	thumbfastPath := filepath.Join(scriptsDir, "thumbfast.lua")
	return os.Remove(thumbfastPath) // Ignore error if file doesn't exist
}

// WithTempDir creates a temporary directory, runs the provided function,
// and ensures cleanup happens on both success and error paths
func WithTempDir(prefix string, fn func(string) error) error {
	tempDir, err := os.MkdirTemp("", prefix)
	if err != nil {
		return err
	}
	defer os.RemoveAll(tempDir)
	return fn(tempDir)
}

// GetMPVConfigDirFromHome returns the MPV config directory path from home directory
func GetMPVConfigDirFromHome(homeDir string) string {
	configPaths := constants.GetConfigPaths(homeDir)
	return configPaths.MPVConfigDir
}
