package pkg

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"sync"

	"gitlab.com/M0M097/pkg/lib/sboRepo"
	"gitlab.com/M0M097/pkg/lib/utils"
)

// FilterGreylistedPkgs removes packages listed in a file with the given path `greylist`
func filterGreylistedPkgs(rm sboRepo.RepoMap, greylist string, PkgErrors *packageErrors) {
	rm.IterOver(utils.ReadWordsFromFile(greylist), func(pkg *sboRepo.Package) {
		if pkg.IsInList() {
			pkg.Del()
			PkgErrors.add(pkg, "Greylisted")
		}
	})
}

// FilterInstalledPkgs removes packages that are already installed. Parallelized.
func filterInstalledPkgs(h sboRepo.Head, docDir, kernelVersion string) {
	var mu sync.Mutex
	h.IterateConcurrently(func(pkg *sboRepo.Package) {
		if pkg.IsUpToDate(docDir, kernelVersion) {
			mu.Lock()
			pkg.Del()
			mu.Unlock()
		}
	})
}

// LetUserExcludePkgs prompts the user to exclude some packages from the list
func letUserExcludePkgs(h sboRepo.Head, w *bufio.Writer, r io.Reader, eh *packageErrors) {
	// Ask user which packages to exclude
	i := 0
	pkgArray := make([]*sboRepo.Package, 0)
	h.Iterate(func(pkg *sboRepo.Package) {
		pkgArray = append(pkgArray, pkg)
		i++
		fmt.Fprintln(w, i, pkg.Description())
	})
	w.WriteString("Space separated list of packages to exclude:\n")
	w.Flush()
	// Parse user input
	scanner := bufio.NewScanner(r)
	scanner.Scan()
	for _, field := range strings.Fields(scanner.Text()) {
		num, err := strconv.Atoi(field)
		if err != nil || num < 1 || num > i {
			fmt.Fprintf(w, "Invalid input: `%s` Repeat...\n", field)
			letUserExcludePkgs(h, w, r, eh)
			return
		}
		pkg := pkgArray[num-1]
		pkg.Del()
		eh.add(pkg, "Excluded by user")
	}
}

// UpdateBlacklist copies the otherBlacklist file to the blacklist file and
// appends the names of the packages in the list to the blacklist file.
func updateBlacklist(h sboRepo.Head, blacklist, otherBlacklist, DefaultFile string) error {
	dest, err := os.Create(blacklist)
	if err != nil {
		return err
	}
	defer dest.Close()

	src, err := os.Open(otherBlacklist)
	if err != nil {
		return errors.New("File " + otherBlacklist + " does not exist: It will be copied to " +
			blacklist + "\nbefore the packages listed in " + DefaultFile +
			" and their dependencies are\nappended to " + blacklist +
			". Either copy your existing " + blacklist + "\nto " + otherBlacklist +
			" or backup " + blacklist + " and create an empty file\n" +
			otherBlacklist + ".")
	}
	defer src.Close()

	utils.Copy(src, dest)
	h.Iterate(func(pkg *sboRepo.Package) { pkg.Write(dest) })
	return nil
}

// FilesToDownload returns a slice of URLs to download for package sources that
// are not already downloaded or have different MD5 sums.
func filesToDownload(h sboRepo.Head) []string {
	var (
		FilesToDownload []string
		mu              sync.Mutex
	)
	h.IterateConcurrently(func(pkg *sboRepo.Package) {
		md5sums := pkg.Md5sums()
		for i, DownloadName := range pkg.DownloadNames() {
			fullPath := filepath.Join(TMP_DIR, DownloadName)
			if !utils.Exists(fullPath) || !utils.IsMD5SumMatch(md5sums[i], fullPath) {
				mu.Lock()
				FilesToDownload = append(FilesToDownload, pkg.DownloadUrls()[i])
				mu.Unlock()
			}
		}
	})
	return FilesToDownload
}

// PromptIfReadmeRequired prompts the user to read the README file if it is
// listed as required in the info file.
func promptIfReadmeRequired(
	pkg *sboRepo.Package,
	w *bufio.Writer,
	r io.Reader,
	pager *utils.Pager,
	PkgErrors *packageErrors,
) {
	if pkg.IsReadmeRequired() && utils.PromptUser(w, r, pkg.Name()+
		": Requires to read the README file. Do you want "+
		"to read it (and optionally skip the package) now?",
	) {

		pager.Display(pkg.AllReadmes())

		if utils.PromptUser(w, r, "Do you want to skip "+pkg.Name()) {
			PkgErrors.skipPkgAndDependees(pkg, "User skipped")
		}

	}
}

func buildCmd(pkg *sboRepo.Package, buildOptions []string) string {
	if len(buildOptions) == 0 {
		return BUILD_CMD + pkg.SlackBuild()
	}
	return ENV_CMD + utils.Join(buildOptions) + " " + BUILD_CMD + pkg.SlackBuild()
}

func preBuildCmd(pkg *sboRepo.Package, configDir string) (string, error) {
	filename := filepath.Join(configDir, pkg.Name()+PREBUILD_EXT)
	if utils.IsExecutable(filename) {
		return filename + " " + pkg.Path(), nil
	}
	return "", errors.New(filename + " does not exist or is not executable")
}

// fullPkgName2PkgName converts a full package name such as
// - "yt-dlp-2025.05.22"  -> "yt-dlp"
// - "pkg-foo-1.0"        -> "pkg-foo"
// - "pkg-1.0_kernel5.10.0_1.0.0_amd64" -> "pkg"
// It returns an empty string when no version part can be detected.
func fullPkgName2PkgName(fullPkgName string) string {
	// Scan from right to left for a '-'
	for i := len(fullPkgName) - 1; i > 0; i-- {
		if fullPkgName[i] == '-' {
			return fullPkgName[:i]
		}
	}
	return ""
}
