package webhook

import (
	"context"
	"fmt"

	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"

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

// AutoscalerValidator is the typed semantic validator for Autoscaler
// objects. It validates cross-field semantics at the admission boundary; the
// current capacity planner does not serve this webhook yet.
type AutoscalerValidator struct{}

// Compile-time assertion: AutoscalerValidator satisfies the typed
// controller-runtime validator (later serving wiring uses
// admission.WithValidator; this layer does not create/register a Webhook).
var _ admission.Validator[*v1alpha1.Autoscaler] = (*AutoscalerValidator)(nil)

// ValidateCreate validates the object on creation.
func (v *AutoscalerValidator) ValidateCreate(ctx context.Context, obj *v1alpha1.Autoscaler) (admission.Warnings, error) {
	return nil, validateAutoscaler(obj)
}

// ValidateUpdate validates the new object on update (the old object is not
// part of this contract).
func (v *AutoscalerValidator) ValidateUpdate(ctx context.Context, oldObj, newObj *v1alpha1.Autoscaler) (admission.Warnings, error) {
	return nil, validateAutoscaler(newObj)
}

// ValidateDelete always allows: the validator owns no delete policy.
func (v *AutoscalerValidator) ValidateDelete(ctx context.Context, obj *v1alpha1.Autoscaler) (admission.Warnings, error) {
	return nil, nil
}

func validateAutoscaler(as *v1alpha1.Autoscaler) error {
	if as == nil {
		return fmt.Errorf("webhook: nil Autoscaler (fail closed)")
	}
	if err := validateTargetRef(as.Spec.TargetRef); err != nil {
		return err
	}
	if err := validateMode(as.Spec.Mode); err != nil {
		return err
	}
	if err := validateReplicas(as.Spec.Replicas); err != nil {
		return err
	}
	if err := tier.ValidateTiersSpec(&as.Spec.Tiers); err != nil {
		return fmt.Errorf("webhook: invalid tiers: %w", err)
	}
	if err := validatePolicy(as.Spec.Policy); err != nil {
		return err
	}
	if err := validateMetrics(as.Spec.Metrics); err != nil {
		return err
	}
	return nil
}

func validateTargetRef(ref v1alpha1.CrossVersionObjectReference) error {
	if ref.APIVersion == "" {
		return fmt.Errorf("webhook: targetRef.apiVersion is required")
	}
	if ref.Kind == "" {
		return fmt.Errorf("webhook: targetRef.kind is required")
	}
	if ref.Name == "" {
		return fmt.Errorf("webhook: targetRef.name is required")
	}
	return nil
}

func validateMode(mode v1alpha1.AutoscalerMode) error {
	switch mode {
	case v1alpha1.AutoscalerModeAuto, v1alpha1.AutoscalerModeDryRun, v1alpha1.AutoscalerModeDisabled:
		return nil
	default:
		return fmt.Errorf("webhook: mode %q is unsupported", mode)
	}
}

func validateReplicas(replicas *v1alpha1.ReplicasSpec) error {
	if replicas == nil {
		return nil
	}
	if replicas.Min < 0 {
		return fmt.Errorf("webhook: replicas.min must not be negative")
	}
	if replicas.Max < 1 {
		return fmt.Errorf("webhook: replicas.max must be greater than zero")
	}
	if replicas.Max < replicas.Min {
		return fmt.Errorf("webhook: replicas.max must be greater than or equal to replicas.min")
	}
	return nil
}

func validatePolicy(policy v1alpha1.AutoscalingPolicy) error {
	switch policy {
	case "", v1alpha1.AutoscalingPolicyCostOptimized:
		return nil
	default:
		return fmt.Errorf("webhook: policy %q is unsupported", policy)
	}
}

func validateMetrics(ms []v1alpha1.MetricSpec) error {
	if len(ms) == 0 {
		return fmt.Errorf("webhook: metrics are required")
	}
	names := make(map[string]struct{}, len(ms))
	for i := range ms {
		if err := validateMetric(&ms[i]); err != nil {
			return fmt.Errorf("webhook: metrics[%d]: %w", i, err)
		}
		name := metricName(&ms[i])
		if _, exists := names[name]; exists {
			return fmt.Errorf("webhook: metrics[%d]: duplicate metric %q", i, name)
		}
		names[name] = struct{}{}
	}
	return nil
}

func metricName(metric *v1alpha1.MetricSpec) string {
	switch metric.Type {
	case v1alpha1.MetricSourceTypeResource:
		return string(metric.Resource.Name)
	default:
		return ""
	}
}

func validateMetric(metric *v1alpha1.MetricSpec) error {
	switch metric.Type {
	case v1alpha1.MetricSourceTypeResource:
		return validateResourceMetric(metric.Resource)
	default:
		return fmt.Errorf("type %q is unsupported", metric.Type)
	}
}

func validateResourceMetric(resource *v1alpha1.ResourceMetricSource) error {
	if resource == nil {
		return fmt.Errorf("resource is required")
	}
	if resource.Name == "" {
		return fmt.Errorf("resource.name is required")
	}
	return validateMetricTarget(&resource.Target)
}

func validateMetricTarget(target *v1alpha1.MetricTarget) error {
	switch target.Type {
	case v1alpha1.MetricTargetTypeUtilization:
		if target.AverageUtilization == nil {
			return fmt.Errorf("target.averageUtilization is required")
		}
		if *target.AverageUtilization <= 0 || *target.AverageUtilization > 100 {
			return fmt.Errorf("target.averageUtilization must be between 1 and 100")
		}
		return nil
	default:
		return fmt.Errorf("target.type %q is unsupported", target.Type)
	}
}
