package webhook

import (
	"context"
	"strings"
	"testing"

	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

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

func newValidator(t *testing.T) *AutoscalerValidator {
	t.Helper()
	return &AutoscalerValidator{}
}

func int32Ptr(v int32) *int32 {
	return &v
}

func validAutoscalerFixture() *v1alpha1.Autoscaler {
	return &v1alpha1.Autoscaler{
		ObjectMeta: metav1.ObjectMeta{Name: "tidb", Namespace: "dp-x"},
		Spec: v1alpha1.AutoscalerSpec{
			TargetRef: v1alpha1.CrossVersionObjectReference{
				APIVersion: "core.pingcap.com/v1alpha1",
				Kind:       "TiDBGroup",
				Name:       "db",
			},
			Mode:     v1alpha1.AutoscalerModeAuto,
			Replicas: &v1alpha1.ReplicasSpec{Min: 2, Max: 8},
			Tiers: v1alpha1.Tiers{
				Template: v1alpha1.TierTemplateRef{Name: "tidb-node"},
				Sets: []v1alpha1.TierSet{{
					Dimensions: []v1alpha1.TierDimension{{
						Resources: []v1alpha1.TierDimensionResource{
							{Name: corev1.ResourceCPU, Variants: []resource.Quantity{resource.MustParse("2"), resource.MustParse("4")}},
							{Name: corev1.ResourceMemory, Variants: []resource.Quantity{resource.MustParse("8Gi"), resource.MustParse("16Gi")}},
						},
					}},
				}},
			},
			Policy: v1alpha1.AutoscalingPolicyCostOptimized,
			Behavior: &v1alpha1.AutoscalerBehavior{
				ScaleDown: &v1alpha1.ScalingBehavior{StabilizationWindowSeconds: int32Ptr(300)},
				ScaleUp:   &v1alpha1.ScalingBehavior{StabilizationWindowSeconds: int32Ptr(300)},
			},
			Metrics: []v1alpha1.MetricSpec{{
				Type: v1alpha1.MetricSourceTypeResource,
				Resource: &v1alpha1.ResourceMetricSource{
					Name: corev1.ResourceCPU,
					Target: v1alpha1.MetricTarget{
						Type:               v1alpha1.MetricTargetTypeUtilization,
						AverageUtilization: int32Ptr(70),
					},
				},
			}},
		},
	}
}

func TestValidateAutoscalerAcceptsStructurallyValidObject(t *testing.T) {
	v := newValidator(t)
	for _, call := range []struct {
		name string
		fn   func(*v1alpha1.Autoscaler) error
	}{
		{"ValidateCreate", func(as *v1alpha1.Autoscaler) error {
			_, err := v.ValidateCreate(context.Background(), as)
			return err
		}},
		{"ValidateUpdate", func(as *v1alpha1.Autoscaler) error {
			_, err := v.ValidateUpdate(context.Background(), nil, as)
			return err
		}},
	} {
		if err := call.fn(validAutoscalerFixture()); err != nil {
			t.Fatalf("%s: unexpected error: %v", call.name, err)
		}
	}
}

func TestValidateAutoscalerAcceptsDryRunMode(t *testing.T) {
	as := validAutoscalerFixture()
	as.Spec.Mode = v1alpha1.AutoscalerModeDryRun

	if _, err := newValidator(t).ValidateCreate(context.Background(), as); err != nil {
		t.Fatalf("DRY_RUN mode rejected: %v", err)
	}
}

func TestValidateDeleteAlwaysAllowed(t *testing.T) {
	v := newValidator(t)
	warnings, err := v.ValidateDelete(context.Background(), nil)
	if err != nil || len(warnings) != 0 {
		t.Fatalf("DELETE must be allowed: warnings=%v err=%v", warnings, err)
	}
}

func TestValidateNilAutoscalerFailsClosed(t *testing.T) {
	v := newValidator(t)
	if _, err := v.ValidateCreate(context.Background(), nil); err == nil {
		t.Fatal("nil object must fail closed")
	}
	if _, err := v.ValidateUpdate(context.Background(), nil, nil); err == nil {
		t.Fatal("nil updated object must fail closed")
	}
}

func TestValidateAutoscalerRejectsInvalidSpec(t *testing.T) {
	tests := []struct {
		name string
		edit func(*v1alpha1.Autoscaler)
		want string
	}{
		{
			name: "invalid replicas",
			edit: func(as *v1alpha1.Autoscaler) {
				as.Spec.Replicas = &v1alpha1.ReplicasSpec{Min: 4, Max: 2}
			},
			want: "replicas.max",
		},
		{
			name: "duplicate tier resource across dimensions",
			edit: func(as *v1alpha1.Autoscaler) {
				as.Spec.Tiers.Sets[0].Dimensions = []v1alpha1.TierDimension{
					{Resources: []v1alpha1.TierDimensionResource{{Name: corev1.ResourceCPU, Variants: []resource.Quantity{resource.MustParse("2")}}}},
					{Resources: []v1alpha1.TierDimensionResource{{Name: corev1.ResourceCPU, Variants: []resource.Quantity{resource.MustParse("4")}}}},
				}
			},
			want: `duplicate resource "cpu" across dimensions`,
		},
		{
			name: "empty metric resource name",
			edit: func(as *v1alpha1.Autoscaler) {
				as.Spec.Metrics[0].Resource.Name = ""
			},
			want: "resource.name is required",
		},
		{
			name: "invalid metric target",
			edit: func(as *v1alpha1.Autoscaler) {
				as.Spec.Metrics[0].Resource.Target.AverageUtilization = int32Ptr(0)
			},
			want: "target.averageUtilization",
		},
		{
			name: "duplicate metric",
			edit: func(as *v1alpha1.Autoscaler) {
				as.Spec.Metrics = append(as.Spec.Metrics, as.Spec.Metrics[0])
			},
			want: `duplicate metric "cpu"`,
		},
	}

	v := newValidator(t)
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			as := validAutoscalerFixture()
			tt.edit(as)
			_, err := v.ValidateCreate(context.Background(), as)
			if err == nil {
				t.Fatal("ValidateCreate() error = nil")
			}
			if !strings.Contains(err.Error(), tt.want) {
				t.Fatalf("ValidateCreate() error = %q, want containing %q", err.Error(), tt.want)
			}
		})
	}
}
