package build

import (
	"fmt"
	"runtime/debug"
	"strconv"
	"sync"
	"time"

	"golang.org/x/mod/semver"
)

type Info struct {
	ProjectName string
	Version     string

	Branch      string
	Tag         string
	ShortCommit string
	FullCommit  string
	CommitTime  time.Time
	Summary     string

	Major      int64
	Minor      int64
	Patch      int64
	Prerelease string
	IsSnapshot bool
	BuildTime  time.Time
}

var info Info

var once sync.Once

const DefaultSemver = "0.0.0-dev"

// getDefaultBuildVersion uses build information stored by Go itself
// to synthesize a build version if one wasn't set.
// This is necessary if the binary was not built through goreleaser.
func getDefaultBuildVersion() string {
	bi, ok := debug.ReadBuildInfo()
	if !ok {
		panic("unable to read build info")
	}

	m := make(map[string]string)
	for _, s := range bi.Settings {
		m[s.Key] = s.Value
	}

	out := DefaultSemver

	// Append revision as build metadata.
	if v, ok := m["vcs.revision"]; ok {
		// First 12 characters of the commit SHA is plenty to identify one.
		out = fmt.Sprintf("%s+%s", out, v[0:12])
	}

	return out
}

func initialize() {
	// If buildVersion is empty it means the binary was NOT built through goreleaser.
	// We try to pull version information from debug.BuildInfo().
	if buildVersion == "" {
		buildVersion = getDefaultBuildVersion()
	}

	// Confirm that buildVersion is valid semver.
	// Note that the semver package requires a leading 'v'.
	if !semver.IsValid("v" + buildVersion) {
		panic(fmt.Sprintf(`version is not a valid semver string: "%s"`, buildVersion))
	}

	info = Info{
		ProjectName: buildProjectName,
		Version:     buildVersion,

		Branch:      buildBranch,
		Tag:         buildTag,
		ShortCommit: buildShortCommit,
		FullCommit:  buildFullCommit,
		CommitTime:  parseTime(buildCommitTimestamp),
		Summary:     buildSummary,

		Major:      parseInt(buildMajor),
		Minor:      parseInt(buildMinor),
		Patch:      parseInt(buildPatch),
		Prerelease: buildPrerelease,
		IsSnapshot: parseBool(buildIsSnapshot),
		BuildTime:  parseTime(buildTimestamp),
	}
}

func GetInfo() Info {
	once.Do(initialize)
	return info
}

func parseInt(s string) int64 {
	i, err := strconv.ParseInt(s, 10, 64)
	if err != nil {
		panic(err)
	}
	return i
}

func parseBool(s string) bool {
	b, err := strconv.ParseBool(s)
	if err != nil {
		panic(err)
	}
	return b
}

func parseTime(s string) time.Time {
	return time.Unix(parseInt(s), 0)
}