package version

import (
	"fmt"
	"os"
	"strings"
	"time"

	"github.com/go-git/go-git/v5"
	"github.com/go-git/go-git/v5/plumbing"
)

// Version info populated at build time
var (
	Version   string // Git tag or commit hash
	Commit    string // Short commit hash
	BuildDate string // Build timestamp
)

// Info returns version information
type Info struct {
	Version   string `json:"version"`
	Commit    string `json:"commit"`
	BuildDate string `json:"build_date"`
}

// GetInfo returns version information
func GetInfo() Info {
	v := Info{
		Version:   Version,
		Commit:    Commit,
		BuildDate: BuildDate,
	}

	// If not set during build, try to get from git
	if v.Version == "" || v.Version == "unknown" || v.Version == "(devel)" {
		if gitVersion, gitCommit := getGitInfo(); gitVersion != "" {
			v.Version = gitVersion
			v.Commit = gitCommit
		}
	}

	// If build date not set, use current time
	if v.BuildDate == "" {
		v.BuildDate = time.Now().Format(time.RFC3339)
	}

	return v
}

// getGitInfo returns the latest tag or short commit hash and the commit hash
func getGitInfo() (string, string) {
	// Get the current working directory
	dir, err := os.Getwd()
	if err != nil {
		return "", ""
	}

	// Open the git repository
	repo, err := git.PlainOpen(dir)
	if err != nil {
		return "", ""
	}

	// Get the HEAD reference
	head, err := repo.Head()
	if err != nil {
		return "", ""
	}

	commitHash := head.Hash().String()[:7]

	// Get all tags
	tagIter, err := repo.Tags()
	if err != nil {
		return commitHash, commitHash
	}

	// Get the commit for HEAD
	commit, err := repo.CommitObject(head.Hash())
	if err != nil {
		return commitHash, commitHash
	}

	// Build ancestry map
	ancestry := make(map[string]bool)
	c := commit
	for c != nil {
		ancestry[c.Hash.String()] = true
		c, _ = c.Parent(0)
	}

	// Find the most recent tag that's an ancestor of HEAD
	var latestTag string
	err = tagIter.ForEach(func(ref *plumbing.Reference) error {
		// Get the target commit
		targetHash := ref.Hash()

		// Get the target commit
		targetCommit, err := repo.CommitObject(targetHash)
		if err != nil {
			return nil
		}

		// Check if this tag is an ancestor of HEAD
		checkCommit := targetCommit
		for checkCommit != nil {
			if ancestry[checkCommit.Hash.String()] {
				// Extract tag name (remove refs/tags/ prefix)
				tagName := strings.TrimPrefix(ref.Name().String(), "refs/tags/")
				if latestTag == "" || tagName > latestTag {
					latestTag = tagName
				}
				return nil
			}
			checkCommit, _ = checkCommit.Parent(0)
		}
		return nil
	})

	if err != nil {
		return commitHash, commitHash
	}

	if latestTag != "" {
		return latestTag, commitHash
	}

	return commitHash, commitHash
}

// String returns a formatted version string
func String() string {
	info := GetInfo()
	return fmt.Sprintf("Version: %s\nCommit: %s\nBuild Date: %s\n", info.Version, info.Commit, info.BuildDate)
}
