package integration

import (
	"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 int32Ptr(v int32) *int32 {
	return &v
}

func validAutoscaler() *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),
						},
					},
				},
				{
					Type: v1alpha1.MetricSourceTypeResource,
					Resource: &v1alpha1.ResourceMetricSource{
						Name: corev1.ResourceMemory,
						Target: v1alpha1.MetricTarget{
							Type:               v1alpha1.MetricTargetTypeUtilization,
							AverageUtilization: int32Ptr(90),
						},
					},
				},
			},
		},
	}
}

func TestValidAutoscalerAccepted(t *testing.T) {
	ctx := t.Context()
	as := validAutoscaler()
	if err := testClient.Create(ctx, as); err != nil {
		t.Fatalf("valid Autoscaler rejected: %v", err)
	}
	defer testClient.Delete(ctx, as)
}

func TestDryRunAutoscalerAccepted(t *testing.T) {
	ctx := t.Context()
	as := validAutoscaler()
	as.Name = "dry-run"
	as.Spec.Mode = v1alpha1.AutoscalerModeDryRun
	if err := testClient.Create(ctx, as); err != nil {
		t.Fatalf("DRY_RUN Autoscaler rejected: %v", err)
	}
	defer testClient.Delete(ctx, as)
}

func TestMissingSpecRejected(t *testing.T) {
	ctx := t.Context()
	as := &v1alpha1.Autoscaler{ObjectMeta: metav1.ObjectMeta{Name: "no-spec", Namespace: "dp-x"}}
	if err := testClient.Create(ctx, as); err == nil {
		t.Fatal("Autoscaler without meaningful spec must be rejected")
	}
}

func TestMissingTargetRefRejected(t *testing.T) {
	ctx := t.Context()
	as := validAutoscaler()
	as.Name = "no-target-ref"
	as.Spec.TargetRef = v1alpha1.CrossVersionObjectReference{}
	if err := testClient.Create(ctx, as); err == nil {
		t.Fatal("missing targetRef fields must be rejected")
	}
}

func TestReplicaMaxBelowMinRejected(t *testing.T) {
	ctx := t.Context()
	as := validAutoscaler()
	as.Name = "bad-replicas"
	as.Spec.Replicas = &v1alpha1.ReplicasSpec{Min: 8, Max: 2}
	if err := testClient.Create(ctx, as); err == nil {
		t.Fatal("replicas.max < replicas.min must be rejected")
	}
}

func TestMissingTierTemplateNameRejected(t *testing.T) {
	ctx := t.Context()
	as := validAutoscaler()
	as.Name = "bad-tier-template"
	as.Spec.Tiers.Template.Name = ""
	if err := testClient.Create(ctx, as); err == nil {
		t.Fatal("tier template name must be rejected")
	}
}

func TestMetricResourceRequired(t *testing.T) {
	ctx := t.Context()
	as := validAutoscaler()
	as.Name = "no-resource-metric"
	as.Spec.Metrics[0].Resource = nil
	if err := testClient.Create(ctx, as); err == nil {
		t.Fatal("type=Resource without resource must be rejected")
	}
}

func TestAverageUtilizationRequired(t *testing.T) {
	ctx := t.Context()
	as := validAutoscaler()
	as.Name = "no-average-utilization"
	as.Spec.Metrics[0].Resource.Target.AverageUtilization = nil
	if err := testClient.Create(ctx, as); err == nil {
		t.Fatal("target.type=Utilization without averageUtilization must be rejected")
	}
}

func TestAverageUtilizationOutOfRangeRejected(t *testing.T) {
	ctx := t.Context()
	as := validAutoscaler()
	as.Name = "bad-average-utilization"
	as.Spec.Metrics[0].Resource.Target.AverageUtilization = int32Ptr(101)
	if err := testClient.Create(ctx, as); err == nil {
		t.Fatal("averageUtilization > 100 must be rejected")
	}
}

func TestBehaviorWindowOutOfRangeRejected(t *testing.T) {
	ctx := t.Context()
	as := validAutoscaler()
	as.Name = "bad-window"
	as.Spec.Behavior.ScaleUp.StabilizationWindowSeconds = int32Ptr(3601)
	if err := testClient.Create(ctx, as); err == nil {
		t.Fatal("stabilizationWindowSeconds > 3600 must be rejected")
	}
}
