package modrinth

import (
	modrinthApi "codeberg.org/jmansfield/go-modrinth/modrinth"
	"errors"
	"github.com/Masterminds/semver/v3"
	"github.com/packwiz/packwiz/cmd"
	"github.com/packwiz/packwiz/core"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
	"golang.org/x/exp/slices"
	"net/http"
)

var modrinthCmd = &cobra.Command{
	Use:     "modrinth",
	Aliases: []string{"mr"},
	Short:   "Manage modrinth-based mods",
}

var mrDefaultClient = modrinthApi.NewClient(&http.Client{})

func init() {
	cmd.Add(modrinthCmd)
	core.Updaters["modrinth"] = mrUpdater{}

	mrDefaultClient.UserAgent = core.UserAgent
}

func getModIdsViaSearch(query string, versions []string) ([]*modrinthApi.SearchResult, error) {
	facets := make([]string, 0)
	for _, v := range versions {
		facets = append(facets, "versions:"+v)
	}

	res, err := mrDefaultClient.Projects.Search(&modrinthApi.SearchOptions{
		Limit: 5,
		Index: "relevance",
		// Filters by mod since currently only mods and modpacks are supported by Modrinth
		Facets: [][]string{facets, {"project_type:mod"}},
		Query:  query,
	})

	if err != nil {
		return nil, err
	}
	return res.Hits, nil
}

func getLatestVersion(modID string, pack core.Pack) (*modrinthApi.Version, error) {
	mcVersion, err := pack.GetMCVersion()
	if err != nil {
		return nil, err
	}
	gameVersions := append([]string{mcVersion}, viper.GetStringSlice("acceptable-game-versions")...)

	result, err := mrDefaultClient.Versions.ListVersions(modID, modrinthApi.ListVersionsOptions{
		GameVersions: gameVersions,
		Loaders:      pack.GetLoaders(),
	})

	if len(result) == 0 {
		return nil, errors.New("no valid versions found")
	}

	latestValidVersion := result[0]
	for _, v := range result[1:] {
		currVersion, err1 := semver.NewVersion(*v.VersionNumber)
		latestVersion, err2 := semver.NewVersion(*latestValidVersion.VersionNumber)
		var semverCompare = 0
		// Only compare with semver if both are valid semver - otherwise compare by release date
		if err1 == nil && err2 == nil {
			semverCompare = currVersion.Compare(latestVersion)
		}

		if semverCompare == 0 {
			// Prefer Quilt over Fabric (Modrinth backend handles filtering)
			if slices.Contains(v.Loaders, "quilt") && !slices.Contains(latestValidVersion.Loaders, "quilt") {
				latestValidVersion = v
				continue
			}

			//Semver is equal, compare date instead
			if v.DatePublished.After(*latestValidVersion.DatePublished) {
				latestValidVersion = v
			}
		} else if semverCompare == 1 {
			latestValidVersion = v
		}
	}

	return latestValidVersion, nil
}

func getSide(mod *modrinthApi.Project) string {
	server := shouldDownloadOnSide(*mod.ServerSide)
	client := shouldDownloadOnSide(*mod.ClientSide)

	if server && client {
		return core.UniversalSide
	} else if server {
		return core.ServerSide
	} else if client {
		return core.ClientSide
	} else {
		return ""
	}
}

func shouldDownloadOnSide(side string) bool {
	return side == "required" || side == "optional"
}

func getBestHash(v *modrinthApi.File) (string, string) {
	// Try preferred hashes first; SHA1 is first as it is required for Modrinth pack exporting
	val, exists := v.Hashes["sha1"]
	if exists {
		return "sha1", val
	}
	val, exists = v.Hashes["sha512"]
	if exists {
		return "sha512", val
	}
	val, exists = v.Hashes["sha256"]
	if exists {
		return "sha256", val
	}
	val, exists = v.Hashes["murmur2"] // (not defined in Modrinth pack spec, use with caution)
	if exists {
		return "murmur2", val
	}

	//none of the preferred hashes are present, just get the first one
	for key, val := range v.Hashes {
		return key, val
	}

	//No hashes were present
	return "", ""
}

func getInstalledProjectIDs(index *core.Index) []string {
	var installedProjects []string
	for _, modPath := range index.GetAllMods() {
		mod, err := core.LoadMod(modPath)
		if err == nil {
			data, ok := mod.GetParsedUpdateData("modrinth")
			if ok {
				updateData, ok := data.(mrUpdateData)
				if ok {
					if len(updateData.ModID) > 0 {
						installedProjects = append(installedProjects, updateData.ModID)
					}
				}
			}
		}
	}
	return installedProjects
}