package apiversion

import (
	"fmt"
	"strconv"
	"strings"

	"golang.org/x/xerrors"
)

// New returns an *APIVersion with the given major.minor and
// additional supported major versions.
func New(maj, min int) *APIVersion {
	v := &APIVersion{
		supportedMajor:   maj,
		supportedMinor:   min,
		additionalMajors: make([]int, 0),
	}
	return v
}

type APIVersion struct {
	supportedMajor   int
	supportedMinor   int
	additionalMajors []int
}

func (v *APIVersion) WithBackwardCompat(majs ...int) *APIVersion {
	v.additionalMajors = append(v.additionalMajors, majs...)
	return v
}

func (v *APIVersion) String() string {
	return fmt.Sprintf("%d.%d", v.supportedMajor, v.supportedMinor)
}

// Validate validates the given version against the given constraints:
// A given major.minor version is valid iff:
//  1. The requested major version is contained within v.supportedMajors
//  2. If the requested major version is the 'current major', then
//     the requested minor version must be less than or equal to the supported
//     minor version.
//
// For example, given majors {1, 2} and minor 2, then:
// - 0.x is not supported,
// - 1.x is supported,
// - 2.0, 2.1, and 2.2 are supported,
// - 2.3+ is not supported.
func (v *APIVersion) Validate(version string) error {
	major, minor, err := Parse(version)
	if err != nil {
		return err
	}
	if major > v.supportedMajor {
		return xerrors.Errorf("server is at version %d.%d, behind requested major version %s",
			v.supportedMajor, v.supportedMinor, version)
	}
	if major == v.supportedMajor {
		if minor > v.supportedMinor {
			return xerrors.Errorf("server is at version %d.%d, behind requested minor version %s",
				v.supportedMajor, v.supportedMinor, version)
		}
		return nil
	}
	for _, mjr := range v.additionalMajors {
		if major == mjr {
			return nil
		}
	}
	return xerrors.Errorf("version %s is no longer supported", version)
}

// Parse parses a valid major.minor version string into (major, minor).
// Both major and minor must be valid integers separated by a period '.'.
func Parse(version string) (major int, minor int, err error) {
	parts := strings.Split(version, ".")
	if len(parts) != 2 {
		return 0, 0, xerrors.Errorf("invalid version string: %s", version)
	}
	major, err = strconv.Atoi(parts[0])
	if err != nil {
		return 0, 0, xerrors.Errorf("invalid major version: %s", version)
	}
	minor, err = strconv.Atoi(parts[1])
	if err != nil {
		return 0, 0, xerrors.Errorf("invalid minor version: %s", version)
	}
	return major, minor, nil
}
