package cmpver

import (
	"fmt"
	"strings"

	semver "github.com/Masterminds/semver"
)

type Operation string

const (
	Greater        Operation = ">"
	GreaterOrEqual Operation = ">="
	Less           Operation = "<"
	LessOrEqual    Operation = "<="
)

// MustCompareByStr compare ver1 and ver2
//
// See also: Compare
func MustCompare(ver1 string, op Operation, ver2 string) bool {
	ok, err := Compare(ver1, op, ver2)
	if err != nil {
		panic(err)
	}

	return ok
}

// Compare compare ver1 and ver2 by operation
//
// For example:
//
//	Compare(ver1, Greater, ver2) is same as "ver1 > ver2"
//
// Dirty versions and pre versions are regarded as standard version.
// For example: 'v5.1.2-dev' and 'v5.1.2-betav1' are regarded as 'v5.1.2'.
//
// Latest, nightly or master version is larger than any version
func Compare(ver1 string, op Operation, ver2 string) (bool, error) {
	err := validateOperation(op)
	if err != nil {
		return false, err
	}

	c, err := NewConstraint(op, ver2)
	if err != nil {
		return false, err
	}

	return c.Check(ver1)
}

// MustCompareByStr compare ver1 and ver2
//
// See also: CompareByStr
func MustCompareByStr(ver1 string, opstr string, ver2 string) bool {
	ok, err := CompareByStr(ver1, opstr, ver2)
	if err != nil {
		panic(err)
	}

	return ok
}

// CompareByStr compare ver1 and ver2
//
// For example:
//
//	CompareByStr(ver1, ">", ver2) is same as "ver1 > ver2"
//
// Dirty versions and pre versions are regarded as standard version.
// For example: 'v5.1.2-dev' and 'v5.1.2-betav1' are regarded as 'v5.1.2'.
//
// Latest, nightly or master version is larger than any version
func CompareByStr(ver1, opstr, ver2 string) (bool, error) {
	op := Operation(opstr)
	return Compare(ver1, op, ver2)
}

// Constraint is a wrapper of semver.Constraints to support dirty versions and pre versions.
type Constraint struct {
	op         Operation
	version    string
	constraint *semver.Constraints
}

func MustNewConstraint(op Operation, version string) *Constraint {
	c, err := NewConstraint(op, version)
	if err != nil {
		panic(err)
	}

	return c
}

func NewConstraint(op Operation, version string) (*Constraint, error) {
	err := validateOperation(op)
	if err != nil {
		return nil, err
	}

	// default to support dirty version
	constraint, err := semver.NewConstraint(fmt.Sprintf("%s%s", op, version))
	if err != nil {
		return nil, err
	}

	return &Constraint{
		op:         op,
		version:    version,
		constraint: constraint,
	}, nil
}

// Check tests if a version satisfies the constraints.
//
// Dirty versions and pre versions are regarded as standard version.
// For example: 'v5.1.2-dev' and 'v5.1.2-betav1' are regarded as 'v5.1.2'.
//
// Latest, nightly or master version is larger than any version
func (c *Constraint) Check(version string) (bool, error) {
	if isLatest(version) {
		return compareLatest(c.op), nil
	}

	ver, err := semver.NewVersion(version)
	if err != nil {
		return false, err
	}

	// set prerelease to empty str in order to support dirty versions
	ckVer := *ver
	if ver.Prerelease() != "" {
		ckVer, err = ver.SetPrerelease("")
		if err != nil {
			return false, err
		}
	}

	return c.constraint.Check(&ckVer), nil
}

func validateOperation(op Operation) error {
	switch op {
	case Greater, GreaterOrEqual, Less, LessOrEqual:
		return nil
	}

	return fmt.Errorf("not support operation %s", op)
}

func isLatest(version string) bool {
	switch {
	case version == "latest",
		version == "nightly",
		version == "master",
		strings.HasPrefix(version, "latest-"),
		strings.HasPrefix(version, "nightly-"),
		strings.HasPrefix(version, "master-"):
		return true
	}

	return false
}

func compareLatest(op Operation) bool {
	// latest is greater than any version
	switch op {
	case Greater, GreaterOrEqual:
		return true
	case Less, LessOrEqual:
		return false
	}

	return false
}
