package tier

import (
	"fmt"

	"github.com/tidbcloud/cluster-service-ng/internal/capacity-planner/api/v1alpha1"
)

// ValidateTiersSpec validates Autoscaler tier inputs at the API boundary.
func ValidateTiersSpec(spec *v1alpha1.Tiers) error {
	if spec == nil {
		return fmt.Errorf("tier: nil tiers")
	}
	if spec.Template.Name == "" {
		return fmt.Errorf("tier: template name is required")
	}
	if len(spec.Sets) == 0 {
		return fmt.Errorf("tier: sets are required")
	}

	for i := range spec.Sets {
		if err := validateTierSet(&spec.Sets[i]); err != nil {
			return fmt.Errorf("tier set %d: %w", i, err)
		}
	}
	return nil
}

func validateTierSet(set *v1alpha1.TierSet) error {
	if len(set.Dimensions) == 0 {
		return fmt.Errorf("dimensions are required")
	}

	names := map[string]struct{}{}
	for i := range set.Dimensions {
		dimensionResources, err := validateTierDimension(&set.Dimensions[i])
		if err != nil {
			return fmt.Errorf("dimension %d: %w", i, err)
		}
		for _, name := range dimensionResources {
			if _, exists := names[name]; exists {
				return fmt.Errorf("duplicate resource %q across dimensions", name)
			}
			names[name] = struct{}{}
		}
	}
	return nil
}

func validateTierDimension(dimension *v1alpha1.TierDimension) ([]string, error) {
	if len(dimension.Resources) == 0 {
		return nil, fmt.Errorf("resources are required")
	}

	names := make(map[string]struct{}, len(dimension.Resources))
	resourceNames := make([]string, 0, len(dimension.Resources))
	var variantCount int
	for i := range dimension.Resources {
		input := &dimension.Resources[i]
		name := string(input.Name)
		if name == "" {
			return nil, fmt.Errorf("resource %d name is required", i)
		}
		if _, exists := names[name]; exists {
			return nil, fmt.Errorf("duplicate resource %q", name)
		}
		names[name] = struct{}{}
		resourceNames = append(resourceNames, name)

		count, err := validateTierDimensionResource(input)
		if err != nil {
			return nil, fmt.Errorf("resource %q: %w", name, err)
		}
		if i == 0 {
			variantCount = count
		} else if count != variantCount {
			return nil, fmt.Errorf("resource %q has %d variants, want %d", name, count, variantCount)
		}
	}
	return resourceNames, nil
}

func validateTierDimensionResource(input *v1alpha1.TierDimensionResource) (int, error) {
	hasVariants := len(input.Variants) > 0
	hasRange := input.Range != nil
	switch {
	case hasVariants == hasRange:
		return 0, fmt.Errorf("exactly one of variants or range must be set")
	case hasVariants:
		for i := range input.Variants {
			if input.Variants[i].Sign() <= 0 {
				return 0, fmt.Errorf("variant %d must be greater than zero", i)
			}
		}
		return len(input.Variants), nil
	default:
		return validateTierResourceRange(input.Range)
	}
}

func validateTierResourceRange(r *v1alpha1.TierResourceRange) (int, error) {
	min := r.Min.DeepCopy()
	max := r.Max.DeepCopy()
	step := r.Step.DeepCopy()
	if min.Sign() <= 0 {
		return 0, fmt.Errorf("range min must be greater than zero")
	}
	if step.Sign() <= 0 {
		return 0, fmt.Errorf("range step must be greater than zero")
	}
	if min.Cmp(max) > 0 {
		return 0, fmt.Errorf("range min must be less than or equal to max")
	}

	current := min.DeepCopy()
	for count := 1; count <= maxRangeVariants; count++ {
		cmp := current.Cmp(max)
		if cmp > 0 {
			return 0, fmt.Errorf("range step does not reach max")
		}
		if cmp == 0 {
			return count, nil
		}
		current.Add(step)
	}
	return 0, fmt.Errorf("range generated more than %d variants", maxRangeVariants)
}
