package version

import (
	"fmt"
	"log/slog"
	"runtime"
	"runtime/debug"
	"strings"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/spf13/cobra"
	"golang.org/x/mod/semver"
)

// VERSION has the current software version (set in the build process)
var VERSION string
var buildTime string
var gitVersion string
var gitModified bool

var info Info

type Info struct {
	Version     string `json:",omitempty"`
	GitRev      string `json:",omitempty"`
	GitRevShort string `json:",omitempty"`
	BuildTime   string `json:",omitempty"`
}

func init() {

	info.BuildTime = buildTime
	info.GitRev = gitVersion

	if len(VERSION) == 0 {
		VERSION = "dev-snapshot"
	} else {
		if !strings.HasPrefix(VERSION, "v") {
			VERSION = "v" + VERSION
		}
		if !semver.IsValid(VERSION) {
			slog.Default().Warn("invalid version number", "version", VERSION)
		}
	}
	if bi, ok := debug.ReadBuildInfo(); ok {
		for _, h := range bi.Settings {
			// logger.Setup().Info("build info", "k", h.Key, "v", h.Value)
			switch h.Key {
			case "vcs.time":
				if len(buildTime) == 0 {
					buildTime = h.Value
				}
				info.BuildTime = h.Value
			case "vcs.revision":
				// https://blog.carlmjohnson.net/post/2023/golang-git-hash-how-to/
				// todo: use BuildInfo.Main.Version if revision is empty
				info.GitRev = h.Value
				// if gitVersion != h.Value {
				// 	logger.Setup().Warn("gitVersion and info.GitRev differs", "gitVersion", gitVersion, "gitRev", info.GitRev)
				// }
				gitVersion = h.Value
			case "vcs.modified":
				if h.Value == "true" {
					gitModified = true
				}
			}
		}
	}

	info.GitRevShort = info.GitRev

	if len(info.GitRevShort) > 7 {
		info.GitRevShort = info.GitRevShort[:7]
	}

	info.Version = VERSION

	Version()
}

func VersionCmd(name string) *cobra.Command {
	versionCmd := &cobra.Command{
		Use:   "version",
		Short: "Print version and build information",
		Run: func(cmd *cobra.Command, args []string) {
			ver := Version()
			fmt.Printf("%s %s\n", name, ver)
		},
	}
	return versionCmd
}

func RegisterMetric(name string, registry prometheus.Registerer) {
	if len(name) > 0 {
		name = strings.ReplaceAll(name, "-", "_")
		name = name + "_build_info"
	} else {
		name = "build_info"
	}
	buildInfo := prometheus.NewGaugeVec(
		prometheus.GaugeOpts{
			Name: name,
			Help: "Build information",
		},
		[]string{
			"version",
			"buildtime",
			"gittime",
			"git",
		},
	)
	registry.MustRegister(buildInfo)
	info := VersionInfo()
	buildInfo.WithLabelValues(
		fmt.Sprintf("%s/%s",
			info.Version, info.GitRevShort,
		),
		buildTime,
		info.BuildTime,
		info.GitRev,
	).Set(1)
}

var v string

func VersionInfo() Info {
	return info
}

func Version() string {
	if len(v) > 0 {
		return v
	}
	extra := []string{}
	if len(buildTime) > 0 {
		extra = append(extra, buildTime)
	}
	extra = append(extra, runtime.Version())

	v := VERSION
	if len(gitVersion) > 0 {
		g := gitVersion
		if len(g) > 7 {
			g = g[0:7]
		}
		v += "/" + g
		if gitModified {
			v += "-M"
		}

	}

	v = fmt.Sprintf("%s (%s)", v, strings.Join(extra, ", "))
	return v
}

func CheckVersion(version, minimumVersion string) bool {
	if version == "dev-snapshot" {
		return true
	}
	if semver.Compare(version, minimumVersion) < 0 {
		// log.Debug("version too old", "v", cl.Version.Version)
		return false
	}
	return true
}