package updates

import (
	"bufio"
	"bytes"
	"context"
	"os/exec"
	"strings"
	"time"
)

type PackageUpdate struct {
	Name           string
	CurrentVersion string
	NewVersion     string
}

func detectPM() string {
	for _, pm := range []string{"apt-get", "dnf", "yum", "pacman", "zypper", "apk"} {
		if _, err := exec.LookPath(pm); err == nil {
			if pm == "apt-get" {
				return "apt"
			}
			return pm
		}
	}
	return ""
}



func CheckAvailable() ([]PackageUpdate, error) {
	switch detectPM() {
	case "apt":
		return checkApt()
	case "dnf":
		return checkDnfYum("dnf")
	case "yum":
		return checkDnfYum("yum")
	case "pacman":
		return checkPacman()
	case "zypper":
		return checkZypper()
	case "apk":
		return checkApk()
	default:
		return nil, nil
	}
}


func ApplyAll() error {
	switch detectPM() {
	case "apt":
		
		ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
		defer cancel()
		if err := exec.CommandContext(ctx, "apt-get", "update", "-qq").Run(); err != nil {
			return err
		}
		return exec.CommandContext(ctx, "apt-get", "upgrade", "-y").Run()
	case "dnf":
		return exec.Command("dnf", "upgrade", "-y").Run()
	case "yum":
		return exec.Command("yum", "upgrade", "-y").Run()
	case "pacman":
		return exec.Command("pacman", "-Syu", "--noconfirm").Run()
	case "zypper":
		return exec.Command("zypper", "update", "-y").Run()
	case "apk":
		return exec.Command("apk", "upgrade").Run()
	default:
		return nil
	}
}

func checkApt() ([]PackageUpdate, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
	defer cancel()
	
	exec.CommandContext(ctx, "apt-get", "update", "-qq").Run() 

	out, err := exec.Command("apt", "list", "--upgradable").Output()
	if err != nil {
		return nil, err
	}
	var updates []PackageUpdate
	scanner := bufio.NewScanner(bytes.NewReader(out))
	for scanner.Scan() {
		line := scanner.Text()
		
		if !strings.Contains(line, "[upgradable from:") {
			continue
		}
		parts := strings.Fields(line)
		if len(parts) < 2 {
			continue
		}
		name := strings.SplitN(parts[0], "/", 2)[0]
		newVer := parts[1]
		oldVer := ""
		if idx := strings.Index(line, "upgradable from: "); idx != -1 {
			rest := line[idx+len("upgradable from: "):]
			oldVer = strings.TrimSuffix(strings.TrimSpace(rest), "]")
		}
		updates = append(updates, PackageUpdate{Name: name, CurrentVersion: oldVer, NewVersion: newVer})
	}
	return updates, nil
}

func checkDnfYum(pm string) ([]PackageUpdate, error) {
	cmd := exec.Command(pm, "check-update")
	out, err := cmd.Output()
	if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() == 100 {
		err = nil
	}
	if err != nil {
		return nil, err
	}
	var updates []PackageUpdate
	pastHeader := false
	scanner := bufio.NewScanner(bytes.NewReader(out))
	for scanner.Scan() {
		line := scanner.Text()
		if !pastHeader {
			if strings.TrimSpace(line) == "" {
				pastHeader = true
			}
			continue
		}
		parts := strings.Fields(line)
		if len(parts) < 2 {
			continue
		}
		
		name := strings.SplitN(parts[0], ".", 2)[0]
		updates = append(updates, PackageUpdate{Name: name, NewVersion: parts[1]})
	}
	return updates, nil
}

func checkPacman() ([]PackageUpdate, error) {
	out, _ := exec.Command("pacman", "-Qu").Output()
	var updates []PackageUpdate
	scanner := bufio.NewScanner(bytes.NewReader(out))
	for scanner.Scan() {
		parts := strings.Fields(scanner.Text())
		
		if len(parts) < 4 {
			continue
		}
		updates = append(updates, PackageUpdate{Name: parts[0], CurrentVersion: parts[1], NewVersion: parts[3]})
	}
	return updates, nil
}

func checkZypper() ([]PackageUpdate, error) {
	out, err := exec.Command("zypper", "list-updates").Output()
	if err != nil {
		return nil, err
	}
	var updates []PackageUpdate
	scanner := bufio.NewScanner(bytes.NewReader(out))
	for scanner.Scan() {
		line := scanner.Text()
		
		if !strings.HasPrefix(line, "v |") && !strings.HasPrefix(line, "i |") {
			continue
		}
		parts := strings.Split(line, "|")
		if len(parts) < 5 {
			continue
		}
		updates = append(updates, PackageUpdate{
			Name:           strings.TrimSpace(parts[2]),
			CurrentVersion: strings.TrimSpace(parts[3]),
			NewVersion:     strings.TrimSpace(parts[4]),
		})
	}
	return updates, nil
}

func checkApk() ([]PackageUpdate, error) {
	out, err := exec.Command("apk", "list", "--upgradable").Output()
	if err != nil {
		return nil, err
	}
	var updates []PackageUpdate
	scanner := bufio.NewScanner(bytes.NewReader(out))
	for scanner.Scan() {
		line := scanner.Text()
		if !strings.Contains(line, "[upgradable") {
			continue
		}
		parts := strings.Fields(line)
		if len(parts) < 1 {
			continue
		}
		pkgVer := parts[0]
		name := apkName(pkgVer)
		newVer := apkVersion(pkgVer)
		oldVer := ""
		if idx := strings.Index(line, "upgradable from:"); idx != -1 {
			rest := strings.TrimSpace(line[idx+len("upgradable from:"):])
			rest = strings.TrimSuffix(rest, "]")
			oldVer = apkVersion(strings.TrimSpace(rest))
		}
		updates = append(updates, PackageUpdate{Name: name, CurrentVersion: oldVer, NewVersion: newVer})
	}
	return updates, nil
}

func apkName(pkgVer string) string {
	parts := strings.Split(pkgVer, "-")
	var name []string
	for _, p := range parts {
		if len(p) > 0 && p[0] >= '0' && p[0] <= '9' {
			break
		}
		name = append(name, p)
	}
	return strings.Join(name, "-")
}

func apkVersion(pkgVer string) string {
	parts := strings.Split(pkgVer, "-")
	var ver []string
	inVer := false
	for _, p := range parts {
		if !inVer && len(p) > 0 && p[0] >= '0' && p[0] <= '9' {
			inVer = true
		}
		if inVer {
			ver = append(ver, p)
		}
	}
	return strings.Join(ver, "-")
}
