package tidbcluster

import (
	"errors"
	"fmt"
	"strings"

	"github.com/coreos/go-semver/semver"
	"github.com/pingcap/tep/pkg/apis/apiserver"
	"github.com/pingcap/tep/pkg/operator"
	"github.com/pingcap/tidb-operator/pkg/label"
	"k8s.io/apimachinery/pkg/util/validation"
)

var (
	ValidAzSize = map[int]bool{
		1: true,
		3: true,
		5: true,
		7: true,
	}
)

const maxTagSize = 20

func ValidateName(id string) error {
	if operator.NameRegexp.MatchString(id) {
		return nil
	}

	return fmt.Errorf("%s must match /%s/", id, operator.NameRegexp.String())
}

func ValidateClusterName(clusterName string) error {
	if err := validation.IsValidLabelValue(clusterName); err != nil {
		return fmt.Errorf("\"%s\" is not a valid cluster name, regex used for validation is `^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$`, and max length is 63", clusterName)
	}
	return nil
}

func ValidateInstanceTags(tags []string) error {
	if len(tags) > maxTagSize {
		return errors.New(fmt.Sprintf("tag size is greater than %d", maxTagSize))
	}
	for _, tag := range tags {
		err := validation.IsQualifiedName(operator.TagLabelKeyPrefix + tag)
		if len(err) > 0 {
			return errors.New(fmt.Sprintf("invalid tag [%s]: %v", tag, err))
		}
	}
	return nil
}

func ValidateInstancePassword(password string) error {
	if strings.TrimSpace(password) == "" {
		return errors.New("instance password can't be empty")
	}
	return nil
}

func ValidateInstanceVersion(instance, oldObj *apiserver.TiDBCluster, platformSetting *apiserver.PlatformSetting, isCreating bool) error {
	if instance.Version == "" {
		return errors.New("instance version can't be empty")
	}

	foundVersion := false
	for _, allowedVersion := range platformSetting.TiDBVersions {
		if instance.Version == allowedVersion {
			foundVersion = true
			break
		}
	}
	if !foundVersion {
		return errors.New("instance version is invalid: not found in platform settings")
	}

	if !isCreating {
		err := compareVersion(instance.Version, oldObj.Version)
		if err != nil {
			return fmt.Errorf("instance version is invalid, err: %v", err)
		}
	}

	return nil
}

func validateInstanceReplicationFactor(replicationFactor uint32, azSize uint32) error {
	if replicationFactor == 3 || replicationFactor == 5 || replicationFactor == 7 {
		return nil
	}
	if azSize != 1 && replicationFactor != azSize {
		return errors.New("invalid replicationFactor, it should be the same with az size")
	}
	return errors.New("invalid replicationFactor, it should be 3, 5 or 7")
}

func validateCluster(instance, oldObj *apiserver.TiDBCluster, platformSetting *apiserver.PlatformSetting, isCreating bool) error {
	if err := ValidateName(instance.Name); err != nil {
		return err
	}
	if err := ValidateClusterName(instance.ClusterName); err != nil {
		return err
	}
	if err := ValidateInstanceTags(instance.Tags); err != nil {
		return err
	}
	if isCreating {
		if err := ValidateInstancePassword(instance.Password); err != nil {
			return err
		}
	}
	if err := ValidateInstanceVersion(instance, oldObj, platformSetting, isCreating); err != nil {
		return err
	}
	if err := validateAz(instance.AZs, platformSetting); err != nil {
		return err
	}
	if instance.ReplicationFactor > instance.TiKV.Replicas {
		return errors.New("tikv replicas must great than instance replicas")
	}
	if err := validateInstanceReplicationFactor(instance.ReplicationFactor, uint32(len(instance.AZs))); err != nil {
		return err
	}
	if err := validateInstancePD(instance, platformSetting, isCreating); err != nil {
		return err
	}
	if err := validateInstanceTiDB(instance, platformSetting, isCreating); err != nil {
		return err
	}
	if err := validateInstanceTiKV(instance, platformSetting, isCreating); err != nil {
		return err
	}
	return validateInstanceMonitor(instance)
}

func validateResourceTemplate(spec apiserver.ComponentSpec, allowedTemplate []apiserver.ResourceTemplate) error {
	found := false
	for _, item := range allowedTemplate {
		if item.CPU == spec.GetResourceTemplate().CPU && item.Memory == spec.GetResourceTemplate().Memory {
			found = true
		}
	}
	if !found {
		return fmt.Errorf("invalid ResourceTemplate: cpu=%d, memory=%d", spec.GetResourceTemplate().CPU, spec.GetResourceTemplate().Memory)
	}
	return nil
}

func validateAz(azs []string, setting *apiserver.PlatformSetting) error {
	allowedAzSet := make(map[string]bool)
	for _, item := range setting.AZs {
		allowedAzSet[item] = true
	}

	newAzSet := make(map[string]bool)
	for _, item := range azs {
		if newAzSet[item] {
			return errors.New("duplicate az is found")
		}
		if !allowedAzSet[item] {
			return errors.New("invalid az name")
		}
		newAzSet[item] = true
	}

	if !ValidAzSize[len(azs)] {
		return fmt.Errorf("invalid azs size")
	}
	return nil
}

func validateStorageClassName(className string) error {
	if strings.TrimSpace(className) == "" {
		return fmt.Errorf("storageClassName can't be empty")
	}
	return nil
}

func validateInstanceTiDB(instance *apiserver.TiDBCluster, platformSetting *apiserver.PlatformSetting, isCreating bool) error {
	if instance.TiDB.Replicas < 1 {
		return fmt.Errorf("invalid replicas of component[%s], at least 1", label.TiDBLabelVal)
	}
	if isCreating {
		return validateResourceTemplate(instance.TiDB, platformSetting.TiDBResourceTemplates)
	}
	return nil
}

func validateInstancePD(instance *apiserver.TiDBCluster, platformSetting *apiserver.PlatformSetting, isCreating bool) error {
	if instance.PD.Replicas != instance.ReplicationFactor {
		return fmt.Errorf("invalid replicas of component[%s], must be the same with replicationFactor", label.PDLabelVal)
	}
	if isCreating {
		if err := validateResourceTemplate(instance.PD, platformSetting.PDResourceTemplates); err != nil {
			return err
		}
	}
	return validateStorageClassName(instance.PD.StorageClassName)
}

func validateInstanceTiKV(instance *apiserver.TiDBCluster, platformSetting *apiserver.PlatformSetting, isCreating bool) error {
	if instance.TiKV.Replicas < 3 {
		return fmt.Errorf("invalid replicas of component[%s], at least 3", label.TiKVLabelVal)
	}
	if isCreating {
		if err := validateResourceTemplate(instance.TiKV, platformSetting.TiKVResourceTemplates); err != nil {
			return err
		}
	}
	return validateStorageClassName(instance.TiKV.StorageClassName)
}

func validateInstanceMonitor(instance *apiserver.TiDBCluster) error {
	return validateStorageClassName(instance.Monitor.StorageClassName)
}

func compareVersion(a, b string) error {
	va := strings.TrimPrefix(a, "v")
	vb := strings.TrimPrefix(b, "v")

	vA, err := semver.NewVersion(va)
	if err != nil {
		return fmt.Errorf("version[%s] is invalid, err: %v", a, err)
	}

	vB, err := semver.NewVersion(vb)
	if err != nil {
		return fmt.Errorf("version[%s] is invalid, err: %v", b, err)
	}

	if vA.LessThan(*vB) {
		return fmt.Errorf("version[%s] is less than version[%s]", a, b)
	}

	return nil
}
