// Copyright 2023 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package pversion

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

	"github.com/coreos/go-semver/semver"
	apimachineryversion "k8s.io/apimachinery/pkg/version"
	k8sstrings "k8s.io/utils/strings"
)

// readBuildInfo is meant to be overwritten by tests.
//
//nolint:gochecknoglobals // these are swapped during unit tests.
var readBuildInfo = debug.ReadBuildInfo

// gitVersion is set using a linker flag
// -ldflags "-X 'go.pinniped.dev/internal/pversion.gitVersion=v9.8.7'"
// (or set for unit tests).
//
//nolint:gochecknoglobals // these are swapped during unit tests.
var gitVersion string

// Get returns the overall codebase version. It's for detecting
// what code a binary was built from.
//
// This function is very similar to the function defined in k8s.io/component-base (version/version.go)
// but is designed to work with golang's VCS build-time information.
//
// See:
// - https://github.com/kubernetes/component-base/blob/v0.28.0/version/version.go#L26-L42
// - https://tip.golang.org/doc/go1.18#go-version
func Get() apimachineryversion.Info {
	info := apimachineryversion.Info{
		Major:        "0",
		Minor:        "0",
		GitVersion:   "v0.0.0",
		GitTreeState: "dirty",
		GoVersion:    runtime.Version(),
		Compiler:     runtime.Compiler,
		Platform:     fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH),
	}

	gitVersionWithoutLeadingV := gitVersion
	if strings.HasPrefix(gitVersion, "v") {
		gitVersionWithoutLeadingV, _ = strings.CutPrefix(gitVersion, "v")
	}

	gitVersionSemver, err := semver.NewVersion(gitVersionWithoutLeadingV)
	if err == nil && gitVersionSemver != nil {
		info.GitVersion = gitVersion
		info.Major = fmt.Sprintf("%d", gitVersionSemver.Major)
		info.Minor = fmt.Sprintf("%d", gitVersionSemver.Minor)
	}

	if debugBuildInfo, ok := readBuildInfo(); ok {
		for _, buildSetting := range debugBuildInfo.Settings {
			switch buildSetting.Key {
			case "vcs.revision":
				info.GitCommit = buildSetting.Value
			case "vcs.time":
				info.BuildDate = buildSetting.Value
			case "vcs.modified":
				if buildSetting.Value == "false" {
					info.GitTreeState = "clean"
				}
			}
		}
	}

	ensureFakeDevVersion(&info)

	return info
}

func ensureFakeDevVersion(info *apimachineryversion.Info) {
	if info != nil && info.GitVersion == "v0.0.0" && info.GitCommit != "" {
		info.GitVersion += fmt.Sprintf("-%s-%s",
			k8sstrings.ShortenString(info.GitCommit, 8),
			info.GitTreeState)
	}
}