package database

import (
	"context"
	"fmt"
	"io"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"slices"
	"strings"
	"sync"

	"qulay/config"
	"qulay/helpers"

	"codeberg.org/UzbekLinux/uzbekdb-go"
	"github.com/codeclysm/extract/v3"
)

var _name string = filepath.Base(os.Args[0])

type Repo struct {
	Name string
	URL  string
}

func GetRepos(destDir string) []Repo {
	dir := filepath.Join(destDir, paths.ReposConfigDir)

	entries, err := os.ReadDir(dir)
	if err != nil {
		return nil
	}

	var repos []Repo

	for _, entry := range entries {
		if entry.IsDir() {
			continue
		}

		data, err := os.ReadFile(
			filepath.Join(dir, entry.Name()),
		)
		if err != nil {
			continue
		}

		m := uzbekdb.Loads(string(data), "map")
		if m == nil {
			continue
		}

		mp, ok := m.(map[interface{}][]interface{})
		if !ok {
			continue
		}

		repo := Repo{Name: entry.Name()}

		if vals, ok := mp["url"]; ok && len(vals) > 0 {
			repo.URL = fmt.Sprintf("%v", vals[0])
		}

		repos = append(repos, repo)
	}

	slices.SortFunc(repos, func(a, b Repo) int {
		return strings.Compare(a.Name, b.Name)
	})

	return repos
}

func RepoURL(destDir, name string) string {
	for _, repo := range GetRepos(destDir) {
		if repo.Name == name {
			return repo.URL
		}
	}
	return ""
}

func DownloadRepos(destDir string) {
	var wg sync.WaitGroup

	for _, repo := range GetRepos(destDir) {
		wg.Add(1)

		go func(base, name string) {
			defer wg.Done()

			link := base
			if !strings.HasSuffix(link, "/") {
				link += "/"
			}
			link += name + ".tar.zst"

			archiveName := path.Base(link)
			destArchivePath := filepath.Join(destDir, "tmp", archiveName)
			destPath := filepath.Join(destDir, paths.ReposDir)
			fullDestPath := filepath.Join(destPath, name)

			os.MkdirAll(filepath.Dir(destArchivePath), 0755)
			defer os.Remove(destArchivePath)

			client := helpers.HTTPClient()

			resp, err := client.Get(link)
			if err != nil {
				fmt.Println("!! failed to download", link, err)
				return
			}
			defer resp.Body.Close()
			if resp.StatusCode != http.StatusOK {
				fmt.Println("!! http error", resp.StatusCode, link)
				return
			}

			f, err := os.Create(destArchivePath)
			if err != nil {
				fmt.Println("!! failed to create file", err)
				return
			}
			defer f.Close()

			_, err = io.Copy(f, resp.Body)
			if err != nil {
				fmt.Println("!! failed to copy", err)
				return
			}

			fmt.Println(":: downloaded", archiveName, "to", destArchivePath)

			fmt.Println(":: removing old repo", fullDestPath)
			err = os.RemoveAll(fullDestPath)
			if err != nil {
				fmt.Println("w! failed to delete old repo", fullDestPath, err)
			}

			file, err := os.Open(destArchivePath)
			if err != nil {
				fmt.Println("!! failed to open archive file", destArchivePath, err)
				return
			}
			defer file.Close()

			err = extract.Zstd(context.TODO(), file, fullDestPath, nil)
			if err != nil {
				fmt.Println("!! failed to extract", err)
				return
			}
		}(repo.URL, repo.Name)
	}

	wg.Wait()
	fmt.Println(":: repo updating ended")
}

func ReadInstalled(destDir string) map[interface{}][]interface{} {
	installedFullPath := filepath.Join(destDir, paths.InstalledFile)
	file, err := os.OpenFile(installedFullPath, os.O_RDONLY, 0644)
	if err != nil {
		return map[interface{}][]interface{}{}
	}
	defer file.Close()
	text, err := io.ReadAll(file)
	if err != nil {
		return map[interface{}][]interface{}{}
	}
	data := uzbekdb.Loads(string(text), "map")
	if data == nil {
		return map[interface{}][]interface{}{}
	}
	return data.(map[interface{}][]interface{})
}

func WriteInstalled(data map[interface{}][]interface{}, destDir string) error {
	installedFullPath := filepath.Join(destDir, paths.InstalledFile)
	file, err := os.OpenFile(installedFullPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
	if err != nil {
		return err
	}
	defer file.Close()
	_, err = file.WriteString(uzbekdb.Dumps(data))
	return err
}

func IsInstalled(name string, destDir string) bool {
	data := ReadInstalled(destDir)
	if !strings.Contains(name, "/") {
		return false
	}
	_, ok := data[name]
	return ok
}

func GetPackageVersion(name string, destDir string) string {
	data := ReadInstalled(destDir)
	if !strings.Contains(name, "/") {
		for k := range data {
			if kStr, ok := k.(string); ok {
				if strings.HasSuffix(kStr, "/"+name) {
					name = kStr
					break
				}
			}
		}
	}
	alo, _ := data[name]
	if len(alo) > 0 {
		return fmt.Sprintf("%v", alo[0])
	}
	return ""
}

func RegPackage(name string, version string, destDir string) error {
	data := ReadInstalled(destDir)
	val, installed := data[name]

	if installed && len(val) > 0 {
		currentVersion := fmt.Sprintf("%v", val[0])
		if currentVersion != version {
			data[name][0] = version
		}
	} else {
		data[name] = []interface{}{version}
	}
	return WriteInstalled(data, destDir)
}

func UnRegPackage(name string, destDir string) error {
	data := ReadInstalled(destDir)
	_, ok := data[name]
	if !ok {
		return nil
	}
	delete(data, name)
	return WriteInstalled(data, destDir)
}

func PurgeRepository(name string, destDir string) error {
	dirPath := filepath.Join(destDir, paths.ReposDir, name)
	if _, err := os.Stat(dirPath); err != nil {
		return fmt.Errorf("repository '%s' not found", name)
	}
	err := os.RemoveAll(dirPath)
	if err != nil {
		return err
	}
	return nil
}
