package forgepb

import (
	"encoding/json"
	"strings"
	"time"

	"go.wit.com/lib/gui/shell"
	"go.wit.com/log"
)

// go list -json -m go.wit.com/apps/go-clone@latest

// go list -json -m go.wit.com/apps/go-clone@latest
// {
// 	"Path": "go.wit.com/apps/go-clone",
// 	"Version": "v0.0.6",
// 	"Query": "latest",
// 	"Time": "2024-03-10T04:12:15Z",
// 	"GoMod": "/home/jcarr/go/pkg/mod/cache/download/go.wit.com/apps/go-clone/@v/v0.0.6.mod",
// 	"GoVersion": "1.22.0"
// }

type Module struct {
	Path       string       // module path
	Query      string       // version query corresponding to this version
	Version    string       // module version
	Versions   []string     // available module versions
	Replace    *Module      // replaced by this module
	Time       *time.Time   // time version was created
	Update     *Module      // available update (with -u)
	Main       bool         // is this the main module?
	Indirect   bool         // module is only indirectly needed by main module
	Dir        string       // directory holding local copy of files, if any
	GoMod      string       // path to go.mod file describing module, if any
	GoVersion  string       // go version used in module
	Retracted  []string     // retraction information, if any (with -retracted or -u)
	Deprecated string       // deprecation message, if any (with -u)
	Error      *ModuleError // error loading module
	Sum        string       // checksum for path, version (as in go.sum)
	GoModSum   string       // checksum for go.mod (as in go.sum)
	Reuse      bool         // reuse of old module info is safe
	Origin     Origin
}

type ModuleError struct {
	Err string // the error itself
}

// An Origin describes the provenance of a given repo method result.
// It can be passed to CheckReuse (usually in a different go command invocation)
// to see whether the result remains up-to-date.
type Origin struct {
	VCS    string `json:",omitempty"` // "git" etc
	URL    string `json:",omitempty"` // URL of repository
	Subdir string `json:",omitempty"` // subdirectory in repo

	Hash string `json:",omitempty"` // commit hash or ID

	// If TagSum is non-empty, then the resolution of this module version
	// depends on the set of tags present in the repo, specifically the tags
	// of the form TagPrefix + a valid semver version.
	// If the matching repo tags and their commit hashes still hash to TagSum,
	// the Origin is still valid (at least as far as the tags are concerned).
	// The exact checksum is up to the Repo implementation; see (*gitRepo).Tags.
	TagPrefix string `json:",omitempty"`
	TagSum    string `json:",omitempty"`

	// If Ref is non-empty, then the resolution of this module version
	// depends on Ref resolving to the revision identified by Hash.
	// If Ref still resolves to Hash, the Origin is still valid (at least as far as Ref is concerned).
	// For Git, the Ref is a full ref like "refs/heads/main" or "refs/tags/v1.2.3",
	// and the Hash is the Git object hash the ref maps to.
	// Other VCS might choose differently, but the idea is that Ref is the name
	// with a mutable meaning while Hash is a name with an immutable meaning.
	Ref string `json:",omitempty"`

	// If RepoSum is non-empty, then the resolution of this module version
	// failed due to the repo being available but the version not being present.
	// This depends on the entire state of the repo, which RepoSum summarizes.
	// For Git, this is a hash of all the refs and their hashes.
	RepoSum string `json:",omitempty"`
}

// A Tags describes the available tags in a code repository.

func runGoList(url string) (string, error) {
	ver, err := getLatestVersion(url)
	if err != nil {
		return "", err
	}
	r := shell.Run([]string{"go", "list", "-json", "-m", url + "@" + ver})
	var modInfo Module
	out := strings.Join(r.Stdout, "\n")
	err = json.Unmarshal([]byte(out), &modInfo)
	if err != nil {
		log.Info("runGoList() r.Output =", out)
		log.Info("runGoList() json.Unmarshal() error =", err)
		return "", err
	}
	log.Info("runGoList() Output.Origin.URL =", modInfo.Origin.URL)
	return modInfo.Origin.URL, err
}

func getLatestVersion(url string) (string, error) {
	r := shell.Run([]string{"go", "list", "-json", "-m", url + "@latest"})
	var modInfo Module
	out := strings.Join(r.Stdout, "\n")
	err := json.Unmarshal([]byte(out), &modInfo)
	if err != nil {
		log.Info("runGoList() r.Output =", out)
		log.Info("runGoList() json.Unmarshal() error =", err)
		return "", err
	}
	log.Info("runGoList() Output.Version =", modInfo.Version)
	return modInfo.Version, err
}