/*
Copyright 2017 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package version

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

const (
	unknown                 = "unknown"
	develVersion            = "(devel)"
	kubernetesVendorVersion = "1.36.0"

	vcsKeyRevision = "vcs.revision"
	vcsKeyTime     = "vcs.time"
	vcsKeyModified = "vcs.modified"
	vcsValueTrue   = "true"
)

type Version struct {
	KubeBuilderVersion string `json:"kubeBuilderVersion"`
	KubernetesVendor   string `json:"kubernetesVendor"`
	GitCommit          string `json:"gitCommit"`
	BuildDate          string `json:"buildDate"`
	GoOs               string `json:"goOs"`
	GoArch             string `json:"goArch"`
}

func New() Version {
	v := Version{
		KubeBuilderVersion: develVersion,
		KubernetesVendor:   kubernetesVendorVersion,
		GitCommit:          unknown,
		BuildDate:          unknown,
		GoOs:               runtime.GOOS,
		GoArch:             runtime.GOARCH,
	}

	if info, ok := debug.ReadBuildInfo(); ok {
		v.KubeBuilderVersion = resolveMainVersion(info.Main)
		v.applyVCSMetadata(info.Settings)
	}

	if testVersion := os.Getenv("KUBEBUILDER_TEST_VERSION"); testVersion != "" {
		v.KubeBuilderVersion = testVersion
		v.GitCommit = "test-commit"
		v.BuildDate = "1970-01-01T00:00:00Z"
	}

	return v
}

// GetKubeBuilderVersion returns only the CLI version string.
// Used for the cliVersion field in scaffolded PROJECT files.
func (v Version) GetKubeBuilderVersion() string {
	return strings.TrimPrefix(v.KubeBuilderVersion, "v")
}

func resolveMainVersion(main debug.Module) string {
	if main.Version != "" {
		return main.Version
	}
	return develVersion
}

// isPseudoVersion reports whether a version is a pseudo-version
// (e.g., v0.0.0-20191109021931-daa7c04131f5 or v1.2.4-0.20191109021931-daa7c04131f5)
func isPseudoVersion(v string) bool {
	return strings.Contains(v, "-0.")
}

func (v *Version) applyVCSMetadata(settings []debug.BuildSetting) {
	var isDirty bool

	for _, s := range settings {
		switch s.Key {
		case vcsKeyRevision:
			v.GitCommit = s.Value
		case vcsKeyTime:
			v.BuildDate = s.Value
		case vcsKeyModified:
			isDirty = (s.Value == vcsValueTrue)
		}
	}

	if isDirty {
		// For development builds (not proper releases), use develVersion to avoid
		// polluting PROJECT files with unstable -dirty version strings.
		// For tagged releases, ignore the dirty flag to support GoReleaser builds
		// that may create artifacts during the build process.
		if v.KubeBuilderVersion == develVersion || isPseudoVersion(v.KubeBuilderVersion) {
			v.KubeBuilderVersion = develVersion
		}
		// Note: We don't append -dirty to tagged release versions to support
		// GoReleaser and similar build tools that may modify files during build.

		if !strings.Contains(v.GitCommit, "dirty") {
			v.GitCommit += "-dirty"
		}
	}
}

func (v Version) PrintVersion() string {
	return fmt.Sprintf(`KubeBuilder:          %s
Kubernetes:           %s
Git Commit:           %s
Build Date:           %s
Go OS/Arch:           %s/%s`,
		v.KubeBuilderVersion,
		v.KubernetesVendor,
		v.GitCommit,
		v.BuildDate,
		v.GoOs,
		v.GoArch,
	)
}
