package operate

import (
	"fmt"
	"strings"
	"text/template"

	v1 "github.com/tidbcloud/infra-component/api/v1"
	"golang.org/x/mod/semver"
)

// ValidateComponent validates the provided component.
func ValidateComponent(c v1.Component) error {
	if c.Name == "" {
		return fmt.Errorf("component has no name")
	}
	if err := validateComponentSpec(c.Name, c.Spec); err != nil {
		return err
	}
	return nil
}

func validateComponentSpec(name string, spec v1.ComponentSpec) error {
	if spec.Version == "" {
		return fmt.Errorf("component %s has an empty version", name)
	}
	if !semver.IsValid(spec.Version) {
		return fmt.Errorf("component %s version %s is not a valid semver version", name, spec.Version)
	}
	for _, exp := range spec.Selector.ClusterSelector {
		if err := validateMatchingExpression(exp); err != nil {
			return err
		}
	}
	for _, exp := range spec.Selector.NetworkSelector {
		if err := validateMatchingExpression(exp); err != nil {
			return err
		}
	}
	validDeletePolicies := []string{v1.Delete, v1.Retain}
	for _, object := range spec.Objects {
		if object.Name == "" {
			return fmt.Errorf("object has no name")
		}
		valid := false
		for _, policy := range validDeletePolicies {
			if object.DeletePolicy == policy {
				valid = true
				break
			}
		}
		if !valid {
			return fmt.Errorf("delete policy of the object must be one of %s", strings.Join(validDeletePolicies, "/"))
		}
		if object.Template == "" {
			return fmt.Errorf("object %s has an empty template", object.Name)
		}
		_, err := template.New(object.Name).Parse(object.Template)
		if err != nil {
			return fmt.Errorf("invalid template of object %s: %v", object.Name, err)
		}
	}
	return nil
}

func validateMatchingExpression(exp v1.MatchingExpression) error {
	if exp.Key == "" {
		return fmt.Errorf("empty key in the matching expression")
	}
	validOperators := []string{
		v1.Equal,
		v1.NotEqual,
		v1.Greater,
		v1.GreaterOrEqual,
		v1.Less,
		v1.LessOrEqual,
		v1.Include,
		v1.NotInclude,
	}
	valid := false
	for _, validOperator := range validOperators {
		if exp.Operator == validOperator {
			valid = true
			break
		}
	}
	if !valid {
		return fmt.Errorf("operator must be one of %s", strings.Join(validOperators, "/"))
	}
	return nil
}

// ValidateComponent validates the provided component set.
func ValidateComponentSet(cs v1.ComponentSet) error {
	if cs.Name == "" {
		return fmt.Errorf("component set has no name")
	}
	for _, version := range cs.Spec.Versions {
		if err := validateComponentSpec(cs.Name, version); err != nil {
			return err
		}
	}
	versions := make(map[string]bool)
	for _, version := range cs.Spec.Versions {
		if versions[version.Version] {
			return fmt.Errorf("duplicate version %s", version.Version)
		}
		versions[version.Version] = true
	}
	return nil
}
