package boilerplate

import (
	"flag"
	"fmt"
	"sort"

	"github.com/Sirupsen/logrus"
	"github.com/blang/semver"
	"github.com/pkg/errors"

	"code.justin.tv/common/golibs/bininfo"
	"code.justin.tv/kdkly/gokdkly/gittags"
)

var (
	debugBininfoRev string
	debugVersionTag string
)

func init() {
	// XXX: Use build tags to remove this flag from normal builds.
	flag.StringVar(&debugBininfoRev, "debug-bininfo-rev", debugBininfoRev, "[DEBUG USE ONLY] act as though our version is the one given, instead of what is in the binary")
	flag.StringVar(&debugVersionTag, "debug-version-tag", debugVersionTag, "[DEBUG USE ONLY] act as though our hardwired version tag is the one given, instead of what is in the binary")
}

func getCachedData() {
	/*
		var config Config

		configDirs := configdir.New("vendor-name", "application-name")
		// optional: local path has the highest priority
		configDirs.LocalPath, _ = filepath.Abs(".")
		folder := configDirs.QueryFolderContainsFile("setting.json")
		if folder != nil {
			data, _ := folder.ReadFile("setting.json")
			json.Unmarshal(data, &config)
		} else {
			config = DefaultConfig
		}
	*/
}

// TaggedReleases returns annotated tags (available at upstreamRemoteURL) that appear to be tagged releases, sorted from
// newest to oldest (according to semantic version comparison rules).
func TaggedReleases(l *logrus.Logger) ([]gittags.ReleaseTagInfo, error) {
	refs, err := gittags.GetRemoteRefs(upstreamRemoteURL)
	if err != nil {
		return nil, errors.Wrap(err, "failed to get remote refs")
	}
	var releaseTags []gittags.ReleaseTagInfo
	for _, ref := range refs {
		if tag, ok := ref.(gittags.ReleaseTagInfo); ok {
			if !tag.Annotated() {
				l.WithFields(logrus.Fields{"tag": tag.Name()}).Warning(
					"ignoring lightweight tag that otherwise looks like a release tag; release tags must be annotated")
				continue
			}
			releaseTags = append(releaseTags, tag)
		}
	}

	if len(releaseTags) == 0 {
		return nil, fmt.Errorf("no release tags found")
	}

	By(versionGT).Sort(releaseTags) // newest-first

	for _, tag := range releaseTags {
		l.WithFields(logrus.Fields{
			"commit":  tag.CommitOid(),
			"version": tag.Version(),
		}).Debug("found tagged release")
	}
	l.WithFields(logrus.Fields{
		"commit":  releaseTags[0].CommitOid(),
		"version": releaseTags[0].Version(),
	}).Info("newest version")

	return releaseTags, nil
}

func parseBininfoRevision(l *logrus.Logger) (gittags.GitOid, bool, error) {
	var err error
	var oid gittags.GitOid

	currentRev := debugBininfoRev
	if currentRev == "" {
		currentRev = bininfo.Revision()
	}
	if currentRev == "" {
		return oid, false, nil
	}

	l.WithFields(logrus.Fields{"rev": currentRev}).Debug("bininfo")
	// TODO: bininfo revs may have a trailer (e.g. "-dirty")
	oid, err = gittags.GitOidFromString(currentRev)
	if err != nil {
		return oid, false, errors.Wrapf(err, "failed to parse revision from bininfo: %q", currentRev)
	}

	return oid, true, nil
}

func UpdateCheck(l *logrus.Logger) error {
	releaseTags, err := TaggedReleases(l)
	if err != nil {
		return err
	}
	latestReleaseTag := releaseTags[0]
	l.WithFields(logrus.Fields{"latestReleaseVersion": latestReleaseTag.Version()}).Info("latest release (based on annotated tags available remotely)")

	var currentReleaseVersion *semver.Version

	// If we're currently on a tagged release, which one?
	currentCommitOid, ok, err := parseBininfoRevision(l)
	if err != nil {
		return err
	}
	if ok {
		// Attempt to translate the commit oid we've gotten from bininfo into a release tag.
		var currentRelease gittags.ReleaseTagInfo
		for _, tag := range releaseTags {
			if tag.CommitOid() == currentCommitOid {
				currentRelease = tag
				l.WithFields(logrus.Fields{"version": currentRelease.Version()}).Debug("current version is a tagged release")
				FOOTMP := currentRelease.Version()
				currentReleaseVersion = &FOOTMP
				break
			}
		}

		if currentRelease == nil {
			l.WithFields(logrus.Fields{"commit": currentCommitOid}).Info("bininfo version information")
			l.Warning("This version of boilerplate-gen is NOT a tagged release!")
			l.Warning("Support may not be availabe and you will not be prompted to upgrade when new versions are available")
			l.Warning("Consider moving to the latest tagged release")
			return nil
		}
	}
	if currentReleaseVersion == nil {
		versionTag := debugVersionTag
		if versionTag == "" {
			versionTag = VersionTag
		}

		// Fall back to using the hardwired constant that we update before each new release.
		v, ok, err := gittags.ParseReleaseTagName(versionTag)
		if err != nil {
			return errors.Wrap(err, "failed to parse hardwired release version")
		}
		if !ok {
			return fmt.Errorf("hardwired release version is not in expected format")
		}
		currentReleaseVersion = &v
		l.WithFields(logrus.Fields{"releaseVersion": v}).Info("hardwired release tag")
	}

	switch {
	case currentReleaseVersion.EQ(latestReleaseTag.Version()):
		l.Info("boilerplate-gen is up-to-date!")
	case currentReleaseVersion.LT(latestReleaseTag.Version()):
		l.Warning("A newer tagged release of boilerplate-gen is available!")
		l.Warning("With a clean working tree, issue `make upgrade-boilerplate-gen` to upgrade and regenerate your boilerplate")
		l.Warning("Verify that your build is still successful (`make`) and then commit the result")
	default:
		l.Error("Your version of boilerplate-gen seems to be newer than the latest-available tagged release!")
	}

	return err
}

func versionLT(a, b gittags.ReleaseTagInfo) bool {
	return a.Version().LT(b.Version())
}
func versionGT(a, b gittags.ReleaseTagInfo) bool {
	return a.Version().GT(b.Version())
}

// A By function defines a partial order on release tags.
type By func(p1, p2 gittags.ReleaseTagInfo) bool

type tagSorter struct {
	tags []gittags.ReleaseTagInfo
	by   By
}

func (by By) Sort(tags []gittags.ReleaseTagInfo) {
	ts := &tagSorter{
		tags: tags,
		by:   by,
	}
	sort.Sort(ts)
}

func (s *tagSorter) Len() int {
	return len(s.tags)
}

func (s *tagSorter) Swap(i, j int) {
	s.tags[i], s.tags[j] = s.tags[j], s.tags[i]
}

func (s *tagSorter) Less(i, j int) bool {
	return s.by(s.tags[i], s.tags[j])
}
