package github

import (
	"errors"
	"fmt"
	"strings"

	"github.com/dlclark/regexp2"
	"github.com/mitchellh/mapstructure"
	"github.com/packwiz/packwiz/core"
)

type ghUpdateData struct {
	Slug   string `mapstructure:"slug"`
	Tag    string `mapstructure:"tag"`
	Branch string `mapstructure:"branch"`
	Regex  string `mapstructure:"regex"`
}

type ghUpdater struct{}

func (u ghUpdater) ParseUpdate(updateUnparsed map[string]interface{}) (interface{}, error) {
	var updateData ghUpdateData
	err := mapstructure.Decode(updateUnparsed, &updateData)
	return updateData, err
}

type cachedStateStore struct {
	Slug    string
	Release Release
}

func (u ghUpdater) CheckUpdate(mods []*core.Mod, pack core.Pack) ([]core.UpdateCheck, error) {
	results := make([]core.UpdateCheck, len(mods))

	for i, mod := range mods {
		rawData, ok := mod.GetParsedUpdateData("github")
		if !ok {
			results[i] = core.UpdateCheck{Error: errors.New("failed to parse update metadata")}
			continue
		}

		data := rawData.(ghUpdateData)

		newRelease, err := getLatestRelease(data.Slug, data.Branch)
		if err != nil {
			results[i] = core.UpdateCheck{Error: fmt.Errorf("failed to get latest release: %v", err)}
			continue
		}

		if newRelease.TagName == data.Tag { // The latest release is the same as the installed one
			results[i] = core.UpdateCheck{UpdateAvailable: false}
			continue
		}

		expr := regexp2.MustCompile(data.Regex, 0)

		if len(newRelease.Assets) == 0 {
			results[i] = core.UpdateCheck{Error: errors.New("new release doesn't have any assets")}
			continue
		}

		var newFiles []Asset

		for _, v := range newRelease.Assets {
			bl, _ := expr.MatchString(v.Name)
			if bl {
				newFiles = append(newFiles, v)
			}
		}

		if len(newFiles) == 0 {
			results[i] = core.UpdateCheck{Error: errors.New("release doesn't have any assets matching regex")}
			continue
		}

		if len(newFiles) > 1 {
			// TODO: also print file names
			results[i] = core.UpdateCheck{Error: errors.New("release has more than one asset matching regex")}
			continue
		}

		newFile := newFiles[0]

		results[i] = core.UpdateCheck{
			UpdateAvailable: true,
			UpdateString:    mod.FileName + " -> " + newFile.Name,
			CachedState:     cachedStateStore{data.Slug, newRelease},
		}
	}

	return results, nil
}

func (u ghUpdater) DoUpdate(mods []*core.Mod, cachedState []interface{}) error {
	for i, mod := range mods {
		modState := cachedState[i].(cachedStateStore)
		var release = modState.Release

		// yes, this is duplicated - i guess we should just cache asset + tag instead of entire release...?
		var file = release.Assets[0]
		for _, v := range release.Assets {
			if strings.HasSuffix(v.Name, ".jar") {
				file = v
			}
		}

		hash, err := file.getSha256()
		if err != nil {
			return err
		}

		mod.FileName = file.Name
		mod.Download = core.ModDownload{
			URL:        file.BrowserDownloadURL,
			HashFormat: "sha256",
			Hash:       hash,
		}
		mod.Update["github"]["tag"] = release.TagName
	}

	return nil
}