package web

import (
	"bufio"
	"fmt"
	"os"
	"os/exec"
	"runtime"
	"strings"

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

// cleanVersionString removes package name prefix from version string
// e.g., "mpv 1:0.41.0-2.1" → "1:0.41.0-2.1"
func cleanVersionString(version, packageName string) string {
	if version == "" {
		return ""
	}

	// If version starts with package name followed by space, remove it
	prefix := packageName + " "
	if strings.HasPrefix(version, prefix) {
		return strings.TrimPrefix(version, prefix)
	}

	return version
}

// getPackageNameForMethod maps method ID to package name for version checking
func getPackageNameForMethod(methodID string) string {
	switch {
	case strings.Contains(methodID, constants.MethodMPVPackage), strings.Contains(methodID, constants.MethodMPVFlatpak),
		strings.Contains(methodID, constants.MethodMPVSnap), strings.Contains(methodID, constants.MethodMPVBrew):
		return "mpv"
	case strings.Contains(methodID, constants.MethodCelluloidPackage), strings.Contains(methodID, constants.MethodCelluloidFlatpak),
		strings.Contains(methodID, constants.MethodCelluloidSnap):
		return "celluloid"
	default:
		return ""
	}
}

// GetPackageVersion queries the installed version of a package using the appropriate package manager
func GetPackageVersion(methodID, appName string) string {
	// Map method ID to package name
	packageName := getPackageNameForMethod(methodID)

	switch {
	case strings.Contains(methodID, "flatpak"):
		return getFlatpakPackageVersion(packageName)
	case strings.Contains(methodID, "snap"):
		return getSnapPackageVersion(packageName)
	case strings.Contains(methodID, "brew"):
		return getBrewPackageVersion(packageName)
	case strings.Contains(methodID, "package"):
		return getLinuxPackageVersion(packageName)
	default:
		return ""
	}
}

// GetAvailableVersion queries the available version of a package from the appropriate package manager
func GetAvailableVersion(methodID, appName string) string {
	// Map method ID to package name
	packageName := getPackageNameForMethod(methodID)

	switch {
	case strings.Contains(methodID, "flatpak"):
		return getFlatpakAvailableVersion(packageName)
	case strings.Contains(methodID, "package"):
		// For pacman-based systems, query available version
		if runtime.GOOS == "linux" {
			if _, err := os.Stat("/etc/os-release"); err == nil {
				file, _ := os.Open("/etc/os-release")
				var id, idLike string
				scanner := bufio.NewScanner(file)
				for scanner.Scan() {
					line := scanner.Text()
					if strings.HasPrefix(line, "ID=") {
						id = strings.Trim(strings.TrimPrefix(line, "ID="), `"`)
					} else if strings.HasPrefix(line, "ID_LIKE=") {
						idLike = strings.Trim(strings.TrimPrefix(line, "ID_LIKE="), `"`)
					}
				}
				file.Close()

				id = strings.ToLower(id)
				idLike = strings.ToLower(idLike)

				archFamily := []string{"arch", "manjaro", "endeavouros", "garuda", "cachyos"}
				for _, distro := range archFamily {
					if id == distro || strings.Contains(idLike, distro) {
						return getPacmanAvailableVersion(packageName)
					}
				}
			}
		}
		return ""
	default:
		return ""
	}
}

// isPackageManagerMethod checks if the method ID represents a package manager installation
func isPackageManagerMethod(methodID string) bool {
	pmMethods := []string{
		"mpv-package", "mpv-flatpak", "mpv-snap", "mpv-brew",
		"celluloid-package", "celluloid-flatpak", "celluloid-snap",
	}
	for _, m := range pmMethods {
		if strings.HasSuffix(methodID, m) || methodID == m {
			return true
		}
	}
	return false
}

// getFlatpakPackageName returns the flatpak package name for an app
func getFlatpakPackageName(appName string) string {
	appName = strings.ToLower(appName)
	switch appName {
	case "mpv":
		return constants.FlatpakMPV
	case "celluloid":
		return constants.FlatpakCelluloid
	default:
		return appName
	}
}

// getFlatpakAppID returns the full Flatpak app ID for a package
func getFlatpakAppID(packageName string) string {
	packageName = strings.ToLower(packageName)
	switch packageName {
	case "mpv":
		return constants.FlatpakMPV
	case "celluloid":
		return constants.FlatpakCelluloid
	default:
		return packageName
	}
}

// getSnapPackageName returns the snap package name for an app
func getSnapPackageName(appName string) string {
	appName = strings.ToLower(appName)
	switch appName {
	case "mpv":
		return "mpv"
	case "celluloid":
		return "celluloid"
	default:
		return appName
	}
}

// getBrewPackageName returns the brew package name for an app
func getBrewPackageName(appName string) string {
	appName = strings.ToLower(appName)
	switch appName {
	case "mpv":
		return "mpv"
	default:
		return appName
	}
}

// getAptPackageName returns the apt package name for an app
func getAptPackageName(appName string) string {
	appName = strings.ToLower(appName)
	switch appName {
	case "mpv":
		return "mpv"
	case "celluloid":
		return "celluloid"
	default:
		return appName
	}
}

// getLinuxPackageVersion queries the appropriate Linux package manager for the installed version
func getLinuxPackageVersion(packageName string) string {
	if runtime.GOOS != "linux" {
		return ""
	}

	// Try to detect distro and use appropriate package manager
	if _, err := os.Stat("/etc/os-release"); err == nil {
		file, err := os.Open("/etc/os-release")
		if err != nil {
			return ""
		}
		defer file.Close()

		var id, idLike string
		scanner := bufio.NewScanner(file)
		for scanner.Scan() {
			line := scanner.Text()
			if strings.HasPrefix(line, "ID=") {
				id = strings.Trim(strings.TrimPrefix(line, "ID="), `"`)
			} else if strings.HasPrefix(line, "ID_LIKE=") {
				idLike = strings.Trim(strings.TrimPrefix(line, "ID_LIKE="), `"`)
			}
		}

		id = strings.ToLower(id)
		idLike = strings.ToLower(idLike)

		// Check for Arch-based distros
		archFamily := []string{"arch", "manjaro", "endeavouros", "garuda", "cachyos"}
		for _, distro := range archFamily {
			if id == distro || strings.Contains(idLike, distro) {
				return getPacmanPackageVersion(packageName)
			}
		}

		// Check for Debian-based distros
		debianFamily := []string{"debian", "ubuntu", "linuxmint", "mint", "pop", "elementaryos"}
		for _, distro := range debianFamily {
			if id == distro || strings.Contains(idLike, distro) {
				return getAptPackageVersion(packageName)
			}
		}

		// Check for RHEL-based distros
		rhelFamily := []string{"rhel", "fedora", "centos", "redhat", "rocky", "almalinux"}
		for _, distro := range rhelFamily {
			if id == distro || strings.Contains(idLike, distro) {
				return getDnfPackageVersion(packageName)
			}
		}

		// Check for SUSE-based distros
		suseFamily := []string{"opensuse", "suse"}
		for _, distro := range suseFamily {
			if id == distro || strings.Contains(idLike, distro) {
				return getZypperPackageVersion(packageName)
			}
		}

		// Default to trying apt
		return getAptPackageVersion(packageName)
	}

	return ""
}

// tryPacmanVersion attempts to get the installed version of a package using pacman -Qi
// Returns empty string if the package is not found or an error occurs
func tryPacmanVersion(packageName string) string {
	cmd := exec.Command("pacman", "-Qi", packageName)
	output, err := cmd.CombinedOutput()
	if err != nil {
		return ""
	}

	// Parse output for "Version : X.Y.Z" - be more specific to avoid matching lines with "Version" in other contexts
	scanner := bufio.NewScanner(strings.NewReader(string(output)))
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		// Check for "Version" followed by colon to ensure we're parsing the correct line
		if strings.HasPrefix(line, "Version") && strings.Contains(line, ":") {
			parts := strings.SplitN(line, ":", 2)
			if len(parts) == 2 {
				version := strings.TrimSpace(parts[1])
				// Additional check: version should not contain package name
				// e.g., "mpv 0.41.0-2.1" should only return "0.41.0-2.1"
				if strings.Contains(version, " ") {
					// If version contains space, take the last part (actual version number)
					versionParts := strings.Fields(version)
					if len(versionParts) > 0 {
						version = versionParts[len(versionParts)-1]
					}
				}
				// Clean version string by removing package name prefix (defensive measure)
				return cleanVersionString(version, packageName)
			}
		}
	}

	return ""
}

// getPacmanPackageVersion queries pacman for the installed version of a package
// For celluloid, tries alternative package names (celluloid-git, gnome-mpv, gnome-mpv-git)
func getPacmanPackageVersion(packageName string) string {
	// Check if pacman is available
	cmd := exec.Command("which", "pacman")
	if err := cmd.Run(); err != nil {
		log.Info("pacman not available")
		return ""
	}

	// Try primary package name
	version := tryPacmanVersion(packageName)
	if version != "" {
		return version
	}

	// For celluloid, try alternative package names (AUR variants, old name)
	if packageName == "celluloid" {
		alternatives := []string{"celluloid-git", "gnome-mpv", "gnome-mpv-git"}
		for _, alt := range alternatives {
			log.Info(fmt.Sprintf("Celluloid not found under primary package name, trying: %s", alt))
			version = tryPacmanVersion(alt)
			if version != "" {
				log.Info(fmt.Sprintf("Found Celluloid under package name: %s", alt))
				return version
			}
		}
		log.Info("Celluloid not found under any package name (celluloid, celluloid-git, gnome-mpv, gnome-mpv-git)")
	}

	return ""
}

// getPacmanAvailableVersion queries pacman for the available version of a package in the repos
func getPacmanAvailableVersion(packageName string) string {
	// Check if pacman is available
	cmd := exec.Command("which", "pacman")
	if err := cmd.Run(); err != nil {
		log.Info("pacman not available")
		return ""
	}

	// Use pacman -Si to get available version
	cmd = exec.Command("pacman", "-Si", packageName)
	output, err := cmd.CombinedOutput()
	if err != nil {
		log.Debug(fmt.Sprintf("Failed to get pacman available version for %s: %v", packageName, err))
		return ""
	}

	// Parse output for "Version : X.Y.Z"
	scanner := bufio.NewScanner(strings.NewReader(string(output)))
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if strings.HasPrefix(line, "Version") {
			parts := strings.SplitN(line, ":", 2)
			if len(parts) == 2 {
				version := strings.TrimSpace(parts[1])
				// Clean version string by removing package name prefix (defensive measure)
				return cleanVersionString(version, packageName)
			}
		}
	}

	return ""
}

// getDnfPackageVersion queries dnf for the installed version of a package
func getDnfPackageVersion(packageName string) string {
	// Check if dnf is available
	cmd := exec.Command("which", "dnf")
	if err := cmd.Run(); err != nil {
		log.Info("dnf not available")
		return ""
	}

	cmd = exec.Command("dnf", "info", "--installed", packageName)
	output, err := cmd.CombinedOutput()
	if err != nil {
		log.Debug(fmt.Sprintf("Failed to get dnf version for %s: %v", packageName, err))
		return ""
	}

	scanner := bufio.NewScanner(strings.NewReader(string(output)))
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		// Check for "Version" followed by colon to ensure we're parsing correct line
		if strings.HasPrefix(line, "Version") && strings.Contains(line, ":") {
			parts := strings.SplitN(line, ":", 2)
			if len(parts) == 2 {
				version := strings.TrimSpace(parts[1])
				// Additional check: version should not contain package name
				if strings.Contains(version, " ") {
					// If version contains space, take the last part (actual version number)
					versionParts := strings.Fields(version)
					if len(versionParts) > 0 {
						version = versionParts[len(versionParts)-1]
					}
				}
				// Clean version string by removing package name prefix (defensive measure)
				return cleanVersionString(version, packageName)
			}
		}
	}

	return ""
}

// getZypperPackageVersion queries zypper (via rpm) for the installed version of a package
func getZypperPackageVersion(packageName string) string {
	// Check if rpm is available (zypper uses rpm for package queries)
	cmd := exec.Command("which", "rpm")
	if err := cmd.Run(); err != nil {
		log.Info("rpm not available")
		return ""
	}

	cmd = exec.Command("rpm", "-q", "--queryformat", "%{VERSION}-%{RELEASE}", packageName)
	output, err := cmd.Output()
	if err != nil {
		log.Debug(fmt.Sprintf("Failed to get zypper/rpm version for %s: %v", packageName, err))
		return ""
	}

	version := strings.TrimSpace(string(output))
	if version != "" && !strings.Contains(version, "not installed") {
		log.Debug(fmt.Sprintf("Found zypper version for %s: %s", packageName, version))
		return version
	}

	return ""
}

// getAptPackageVersion queries apt for the installed version of a package
func getAptPackageVersion(packageName string) string {
	// Check if apt is available
	cmd := exec.Command("which", "apt-get")
	if err := cmd.Run(); err != nil {
		log.Info("apt-get not available")
		return ""
	}

	cmd = exec.Command("apt-cache", "policy", packageName)
	output, err := cmd.CombinedOutput()
	if err != nil {
		log.Debug(fmt.Sprintf("Failed to get apt version for %s: %v", packageName, err))
		return ""
	}

	scanner := bufio.NewScanner(strings.NewReader(string(output)))
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if strings.HasPrefix(line, "Installed:") && strings.Contains(line, ":") {
			parts := strings.SplitN(line, ":", 2)
			if len(parts) == 2 {
				version := strings.TrimSpace(parts[1])
				if version != "(none)" {
					// Additional check: version should not contain package name
					if strings.Contains(version, " ") {
						// If version contains space, take the last part (actual version number)
						versionParts := strings.Fields(version)
						if len(versionParts) > 0 {
							version = versionParts[len(versionParts)-1]
						}
					}
					// Clean version string by removing package name prefix (defensive measure)
					return cleanVersionString(version, packageName)
				}
			}
		}
	}

	return ""
}

// getFlatpakPackageVersion queries flatpak for the installed version of an app
func getFlatpakPackageVersion(packageName string) string {
	// Check if flatpak is available
	cmd := exec.Command("which", "flatpak")
	if err := cmd.Run(); err != nil {
		log.Info("flatpak not available")
		return ""
	}

	// Map to proper Flatpak app ID
	appID := getFlatpakAppID(packageName)

	// Use flatpak info to get installed version
	cmd = exec.Command("flatpak", "info", appID)
	output, err := cmd.CombinedOutput()
	if err != nil {
		log.Debug(fmt.Sprintf("Failed to get flatpak version for %s: %v", appID, err))
		return ""
	}

	// Parse output for "Version: X.Y.Z" (exact match, not "Runtime Version:" or "Sdk Version:")
	scanner := bufio.NewScanner(strings.NewReader(string(output)))
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		// Exact match for "Version:" line (not Runtime Version or Sdk Version)
		if line == "Version:" || strings.HasPrefix(line, "Version: ") {
			parts := strings.SplitN(line, ":", 2)
			if len(parts) == 2 {
				version := strings.TrimSpace(parts[1])
				// Clean version string by removing package name prefix (defensive measure)
				return cleanVersionString(version, packageName)
			}
		}
	}

	return ""
}

// getFlatpakAvailableVersion queries flatpak remote for the available version of an app
func getFlatpakAvailableVersion(packageName string) string {
	// Check if flatpak is available
	cmd := exec.Command("which", "flatpak")
	if err := cmd.Run(); err != nil {
		log.Info("flatpak not available")
		return ""
	}

	// Map to proper Flatpak app ID
	appID := getFlatpakAppID(packageName)

	// Use flathub remote directly for available version check
	cmd = exec.Command("flatpak", "remote-info", "flathub", appID)
	output, err := cmd.CombinedOutput()
	if err != nil {
		log.Debug(fmt.Sprintf("Failed to get flatpak available version for %s: %v", appID, err))
		return ""
	}

	// Parse output for "Version: X.Y.Z"
	scanner := bufio.NewScanner(strings.NewReader(string(output)))
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if strings.HasPrefix(line, "Version:") {
			parts := strings.SplitN(line, ":", 2)
			if len(parts) == 2 {
				version := strings.TrimSpace(parts[1])
				// Clean version string by removing package name prefix (defensive measure)
				return cleanVersionString(version, packageName)
			}
		}
	}

	return ""
}

// getSnapPackageVersion queries snap for the installed version of a snap
func getSnapPackageVersion(packageName string) string {
	if runtime.GOOS != "linux" {
		return ""
	}

	// Check if snap is available
	cmd := exec.Command("which", "snap")
	if err := cmd.Run(); err != nil {
		log.Info("snap not available")
		return ""
	}

	cmd = exec.Command("snap", "info", packageName)
	output, err := cmd.CombinedOutput()
	if err != nil {
		log.Debug(fmt.Sprintf("Failed to get snap version for %s: %v", packageName, err))
		return ""
	}

	scanner := bufio.NewScanner(strings.NewReader(string(output)))
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if strings.Contains(line, "installed:") && !strings.Contains(line, "summary:") {
			parts := strings.SplitN(line, " ", 2)
			if len(parts) == 2 {
				// Extract version from installed line
				installedInfo := strings.TrimSpace(parts[1])
				versionParts := strings.Fields(installedInfo)
				if len(versionParts) > 1 {
					version := versionParts[1]
					// Clean version string by removing package name prefix (defensive measure)
					return cleanVersionString(version, packageName)
				}
			}
		}
	}

	return ""
}

// getBrewPackageVersion queries brew for the installed version of a formula
func getBrewPackageVersion(packageName string) string {
	if runtime.GOOS != "darwin" {
		return ""
	}

	// Check if brew is available
	cmd := exec.Command("which", "brew")
	if err := cmd.Run(); err != nil {
		log.Info("brew not available")
		return ""
	}

	cmd = exec.Command("brew", "list", "--versions", packageName)
	output, err := cmd.CombinedOutput()
	if err != nil {
		log.Debug(fmt.Sprintf("Failed to get brew version for %s: %v", packageName, err))
		return ""
	}

	scanner := bufio.NewScanner(strings.NewReader(string(output)))
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if strings.HasPrefix(line, packageName) {
			parts := strings.Fields(line)
			if len(parts) >= 2 {
				version := parts[1]
				// Clean version string by removing package name prefix (defensive measure)
				return cleanVersionString(version, packageName)
			}
		}
	}

	return ""
}
