package spm

import (
	"fmt"
	"os"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"

	"gopkg.in/ini.v1"
)

// getInstalledPackagesPath determines the path for the installed.ini file.
func getInstalledPackagesPath(installDir string) string {
	spmDir := filepath.Join(installDir, "spm")
	_ = os.MkdirAll(spmDir, 0755)
	return filepath.Join(spmDir, "installed.ini")
}

// loadInstalledPackages reads the installed.ini file and parses it.
func loadInstalledPackages(installDir string) ([]AppIndexEntry, error) {
	installedFile := getInstalledPackagesPath(installDir)

	cfg, err := ini.LoadSources(ini.LoadOptions{AllowNonUniqueSections: true}, installedFile)
	if err != nil {
		if os.IsNotExist(err) {
			return []AppIndexEntry{}, nil // Return empty slice if file doesn't exist
		}
		return nil, err
	}

	var installed []AppIndexEntry
	for _, section := range cfg.Sections() {
		if section.Name() == "DEFAULT" {
			continue
		}

		// Read fields
		name := section.Key("P").String()
		version := section.Key("V").String()
		release := section.Key("R").String()
		typeVal := section.Key("o").String()
		arch := section.Key("A").MustString(runtime.GOARCH) // Default to system arch
		osName := section.Key("p").MustString(runtime.GOOS) // Default to system OS

		// Append to slice
		installed = append(installed, AppIndexEntry{
			Name:    name,
			Version: version,
			Release: release,
			Type:    typeVal,
			Arch:    arch,
			OS:      osName,
		})
	}

	return installed, nil
}

// saveInstalledPackages writes the installed packages into installed.ini.
func saveInstalledPackages(installDir string, pkgs []AppIndexEntry) error {
	installedFile := getInstalledPackagesPath(installDir)

	cfg := ini.Empty()
	for _, pkg := range pkgs {
		section, err := cfg.NewSection(pkg.Name)
		if err != nil {
			return err
		}
		section.Key("P").SetValue(pkg.Name)
		section.Key("V").SetValue(pkg.Version)
		section.Key("R").SetValue(pkg.Release)
		section.Key("o").SetValue(pkg.Type)

		// Save arch if different from current runtime architecture
		if pkg.Arch != runtime.GOARCH {
			section.Key("A").SetValue(pkg.Arch)
		}

		// Save OS if different from current runtime OS
		if pkg.OS != runtime.GOOS {
			section.Key("p").SetValue(pkg.OS)
		}
	}

	return cfg.SaveTo(installedFile)
}

// isNewerVersion compares two version strings and determines if `newVer` is newer than `oldVer`.
func isNewerVersion(oldVer, newVer string) bool {
	// Handle date-based versions (e.g., nightly: YYYY.MM.DD)
	if isDateVersion(oldVer) && isDateVersion(newVer) {
		return strings.Compare(newVer, oldVer) > 0
	}

	// Handle semantic versions (e.g., stable: v1.0.1)
	if isSemVer(oldVer) && isSemVer(newVer) {
		return compareSemVer(oldVer, newVer) > 0
	}

	// Fallback to lexicographical comparison for unknown formats
	return strings.Compare(newVer, oldVer) > 0
}

// isDateVersion checks if a version string is in the format YYYY.MM.DD.
func isDateVersion(version string) bool {
	parts := strings.Split(version, ".")
	if len(parts) != 3 {
		return false
	}
	for _, part := range parts {
		if _, err := strconv.Atoi(part); err != nil {
			return false
		}
	}
	return true
}

// isSemVer checks if a version string is in the format vMAJOR.MINOR.PATCH.
func isSemVer(version string) bool {
	if !strings.HasPrefix(version, "v") {
		return false
	}
	parts := strings.Split(strings.TrimPrefix(version, "v"), ".")
	if len(parts) != 3 {
		return false
	}
	for _, part := range parts {
		if _, err := strconv.Atoi(part); err != nil {
			return false
		}
	}
	return true
}

// compareSemVer compares two semantic version strings (vMAJOR.MINOR.PATCH).
// Returns:
// - 1 if `v2` is newer than `v1`
// - 0 if `v1` and `v2` are equal
// - -1 if `v1` is newer than `v2`
func compareSemVer(v1, v2 string) int {
	parts1 := strings.Split(strings.TrimPrefix(v1, "v"), ".")
	parts2 := strings.Split(strings.TrimPrefix(v2, "v"), ".")

	for i := 0; i < len(parts1); i++ {
		num1, _ := strconv.Atoi(parts1[i])
		num2, _ := strconv.Atoi(parts2[i])
		if num1 > num2 {
			return 1
		}
		if num1 < num2 {
			return -1
		}
	}
	return 0
}

// IsUpdateNeeded checks if the given package version is newer than what's installed.
func IsUpdateNeeded(installDir, name, release, newVersion, arch, osName string) (bool, error) {
	installed, err := loadInstalledPackages(installDir)
	if err != nil {
		return false, err
	}

	for _, pkg := range installed {
		if pkg.Name == name && pkg.Release == release && pkg.Arch == arch && pkg.OS == osName {
			fmt.Printf("Found installed package: %v\n", pkg)
			if isNewerVersion(pkg.Version, newVersion) {
				fmt.Println("Update is needed")
				return true, nil
			}
			fmt.Println("No update needed")
			return false, nil
		}
	}

	fmt.Println("Package not installed, update needed")
	return true, nil
}

// UpdateInstalledPackage writes/updates the new package version in installed.ini.
func UpdateInstalledPackage(installDir string, pkg AppIndexEntry) error {
	installed, err := loadInstalledPackages(installDir)
	if err != nil {
		return err
	}

	updated := false
	for i, p := range installed {
		if p.Name == pkg.Name && p.Release == pkg.Release && p.Arch == pkg.Arch && p.OS == pkg.OS {
			installed[i].Version = pkg.Version
			updated = true
			break
		}
	}
	if !updated {
		installed = append(installed, pkg)
	}

	return saveInstalledPackages(installDir, installed)
}

// // DebugInstalled prints installed packages for debugging.
// func DebugInstalled(installDir string) {
// 	pkgs, err := loadInstalledPackages(installDir)
// 	if err != nil {
// 		fmt.Println("DebugInstalled error:", err)
// 		return
// 	}
// 	for _, p := range pkgs {
// 		fmt.Printf("Installed: %s v%s [%s] (arch=%s, os=%s)\n", p.Name, p.Version, p.Release, p.Arch, p.OS)
// 	}
// }