package main

import (
	"fmt"
	"os"
	"path/filepath"
	paths "qulay/config"
	"qulay/database"
	"qulay/helpers"
	pkg "qulay/package"
	"slices"
	"strings"
)

var _name string = filepath.Base(os.Args[0])
var _cmds = []string{"h", "+", "-", "g", "s", "p", "u", "r", "q", "f", "v", "b", "o"}

const _version string = "1.0"
const _help string = `{_name}: Qulay Package Manager
Usage:
    {_name} h       -> help menu
    {_name} + pkg   -> download and install package (or .qpmp file path)
    {_name} - pkg   -> remove package
    {_name} b pkg   -> build package into .tar.zst (or .qpmp on :qpmp)
    {_name} g pkg   -> get package version
    {_name} s pkg   -> search packages by name
    {_name} p repo  -> purge local repo directory
    {_name} u       -> update all repositories
    {_name} r       -> get all repositories configs
    {_name} o       -> upgrade all installed packages
    {_name} q       -> get all installed packages
    {_name} f       -> get files path for destDir
    {_name} v       -> get {_name} version
Options:
    :a    -> ask before doing
    :nl   -> disable recording in databases and logs
    :nd   -> do not download deps
    :r    -> reinstall already installed deps
    :f    -> force install (ignore conflicts)
    :qpmp -> build a .qpmp archive instead of .tar.zst
Tags (for +, - , b and o):
    @all        -> apply to all packages
    repo/@all   -> apply to all packages in repo
    @inst       -> apply to installed (for b: built) packages
    repo/@inst  -> apply to installed (for b: built) packages in repo
    @notinst    -> apply to not installed (for b: not built) packages
    repo/@notinst-> apply to not installed (for b: not built) packages in repo
    @old        -> apply to installed/built packages outdated by version
    repo/@old   -> apply to outdated packages in repo
    o without tags applies @old automatically
Vars:
    DESTDIR  -> path where the package will be installed/removed`

func parseArgs(args []string) ([]string, []string, []string) {
	var cmds, oths, opts []string
	for _, arg := range args {
		if strings.HasPrefix(arg, ":") {
			opts = append(opts, arg)
		} else if slices.Contains(_cmds, arg) && len(cmds) < 1 {
			cmds = append(cmds, arg)
		} else {
			oths = append(oths, arg)
		}
	}
	return cmds, oths, opts
}

func printHelp() {
	r := strings.NewReplacer("{_name}", _name)
	fmt.Println(r.Replace(_help))
}

func main() {
	var args []string = os.Args[1:]
	cmds, oths, opts := parseArgs(args)

	destDir := os.Getenv("DESTDIR")
	if destDir == "" {
		destDir = "/"
	}

	absDestDir, err := filepath.Abs(destDir)
	if err != nil {
		fmt.Fprintf(os.Stderr, "!! failed to get absolute path of destDir: %v\n", err)
		os.Exit(1)
	}

	if len(args) == 0 || len(cmds) == 0 {
		printHelp()
		os.Exit(1)
	}

	// fmt.Println(cmds, oths, opts)
	if cmds[0] == "h" {
		printHelp()
	} else if cmds[0] == "+" {
		if len(oths) < 1 {
			fmt.Println("!! no package(s) received")
			os.Exit(1)
		}
		names, err := pkg.Expand("+", oths, absDestDir)
		if err != nil {
			fmt.Println("!!", err)
			os.Exit(1)
		}
		for _, n := range names {
			err := pkg.Install(n, opts, absDestDir)
			if err != nil {
				fmt.Println("!!", err)
				os.Exit(1)
			}
		}
	} else if cmds[0] == "-" {
		if len(oths) < 1 {
			fmt.Println("!! no package(s) received")
			os.Exit(1)
		}
		names, err := pkg.Expand("-", oths, absDestDir)
		if err != nil {
			fmt.Println("!!", err)
			os.Exit(1)
		}
		for _, n := range names {
			err := pkg.Remove(n, opts, absDestDir)
			if err != nil {
				fmt.Println("!!", err)
				os.Exit(1)
			}
		}
	} else if cmds[0] == "u" {
		database.DownloadRepos(absDestDir)
	} else if cmds[0] == "b" {
		if len(oths) < 1 {
			fmt.Println("!! no package(s) received")
			os.Exit(1)
		}
		names, err := pkg.Expand("b", oths, absDestDir)
		if err != nil {
			fmt.Println("!!", err)
			os.Exit(1)
		}
		for _, n := range names {
			err := pkg.Build(n, opts, absDestDir)
			if err != nil {
				fmt.Println("!!", err)
				os.Exit(1)
			}
		}
	} else if cmds[0] == "g" {
		if len(oths) < 1 {
			fmt.Println("!! no package(s) received")
			os.Exit(1)
		}
		for _, name := range oths {
			fmt.Println(database.GetPackageVersion(name, absDestDir))
		}
	} else if cmds[0] == "r" {
		for _, repo := range database.GetRepos(absDestDir) {
			fmt.Println(repo.Name, "url:", repo.URL)
		}
	} else if cmds[0] == "o" {
		if len(oths) < 1 {
			oths = []string{"@old"}
		}
		names, err := pkg.Expand("o", oths, absDestDir)
		if err != nil {
			fmt.Println("!!", err)
			os.Exit(1)
		}
		err = pkg.Upgrade(names, opts, absDestDir)
		if err != nil {
			fmt.Println("!!", err)
			os.Exit(1)
		}
	} else if cmds[0] == "q" {
		for name, version := range database.ReadInstalled(absDestDir) {
			if len(version) > 0 {
				fmt.Println(name, "-", version[0])
			}
		}
	} else if cmds[0] == "s" {
		if len(oths) < 1 {
			pkgs := pkg.GetPackages(absDestDir)
			for _, p := range pkgs {
				if database.IsInstalled(p.Repo+"/"+p.Name, absDestDir) {
					fmt.Printf("[+] %s/%s - %v - %v\n", p.Repo, p.Name, p.Version, p.Desc)
				} else {
					fmt.Printf("    %s/%s - %v - %v\n", p.Repo, p.Name, p.Version, p.Desc)
				}
			}
		} else {
			for _, n := range oths {
				pkgs := pkg.FindPackages(n, absDestDir)
				for _, p := range pkgs {
					if database.IsInstalled(p.Repo+"/"+p.Name, absDestDir) {
						fmt.Printf("[+] %s/%s - %v - %v\n", p.Repo, p.Name, p.Version, p.Desc)
					} else {
						fmt.Printf("    %s/%s - %v - %v\n", p.Repo, p.Name, p.Version, p.Desc)
					}
				}
			}
		}
	} else if cmds[0] == "p" {
		if slices.Contains(opts, ":a") {
			if !helpers.Ask("do you want purge repo(s)?") {
				os.Exit(1)
			}
		}
		if len(oths) >= 1 {
			for _, n := range oths {
				err := database.PurgeRepository(n, absDestDir)
				if err != nil {
					fmt.Println("!!", err)
					os.Exit(1)
				} else {
					fmt.Println(":: deleted", n, "repository in", paths.ReposDir)
				}
			}
		}
	} else if cmds[0] == "f" {
		fmt.Printf("dest dir: %v\n", destDir)
		fmt.Printf("abs dest dir: %v\n", absDestDir)
		fmt.Printf("is abs: %v\n", filepath.IsAbs(destDir))
		fmt.Printf("repos config dir: %v\n", filepath.Join(absDestDir, paths.ReposConfigDir))
		fmt.Printf("repos dir: %v\n", filepath.Join(absDestDir, paths.ReposDir))
		fmt.Printf("installed pkgs database: %v\n", filepath.Join(absDestDir, paths.InstalledFile))
		fmt.Printf("built pkgs dir: %v\n", filepath.Join(absDestDir, paths.BuiltDir))
		fmt.Printf("build config file: %v\n", filepath.Join(absDestDir, paths.BuildConfigFile))
	} else if cmds[0] == "v" {
		fmt.Println(_version)
	} else {
		fmt.Println("w! unknown command:", cmds[0])
		printHelp()
		os.Exit(1)
	}
}
