package v1alpha1

import (
	"os"
	"path/filepath"
	"strings"
	"testing"

	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	"k8s.io/apimachinery/pkg/runtime"
)

func TestSchemeRegistration(t *testing.T) {
	s := runtime.NewScheme()
	if err := AddToScheme(s); err != nil {
		t.Fatalf("AddToScheme: %v", err)
	}
	gvks, _, err := s.ObjectKinds(&Autoscaler{})
	if err != nil {
		t.Fatalf("ObjectKinds: %v", err)
	}
	if len(gvks) != 1 || gvks[0].Kind != "Autoscaler" {
		t.Fatalf("unexpected GVKs: %v", gvks)
	}
}

func TestDeepCopyRoundTrip(t *testing.T) {
	cpuTarget := int32(70)
	stabilizationWindow := int32(300)
	in := &Autoscaler{
		Spec: AutoscalerSpec{
			Mode:     AutoscalerModeAuto,
			Replicas: &ReplicasSpec{Min: 2, Max: 8},
			Tiers: Tiers{
				Template: TierTemplateRef{Name: "tidb-node"},
				Sets: []TierSet{{
					Dimensions: []TierDimension{{
						Resources: []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")}},
						},
					}},
				}},
			},
			Behavior: &AutoscalerBehavior{
				ScaleUp: &ScalingBehavior{StabilizationWindowSeconds: &stabilizationWindow},
			},
			Metrics: []MetricSpec{{
				Type: MetricSourceTypeResource,
				Resource: &ResourceMetricSource{
					Name:   corev1.ResourceCPU,
					Target: MetricTarget{Type: MetricTargetTypeUtilization, AverageUtilization: &cpuTarget},
				},
			}},
		},
	}
	out := in.DeepCopy()
	if out.Spec.Mode != AutoscalerModeAuto || out.Spec.Replicas.Max != 8 {
		t.Fatalf("deep copy mismatch: %+v", out.Spec)
	}
	// Mutating the copy must not affect the source.
	out.Spec.Tiers.Sets[0].Dimensions[0].Resources[0].Variants[0] = resource.MustParse("8")
	*out.Spec.Metrics[0].Resource.Target.AverageUtilization = 90
	*out.Spec.Behavior.ScaleUp.StabilizationWindowSeconds = 600
	if in.Spec.Tiers.Sets[0].Dimensions[0].Resources[0].Variants[0].Cmp(resource.MustParse("2")) != 0 {
		t.Fatal("deep copy shares tier variant value")
	}
	if *in.Spec.Metrics[0].Resource.Target.AverageUtilization != 70 {
		t.Fatal("deep copy shares metric target pointer")
	}
	if *in.Spec.Behavior.ScaleUp.StabilizationWindowSeconds != 300 {
		t.Fatal("deep copy shares behavior pointer")
	}
}

func TestCRDValidationRulesPresent(t *testing.T) {
	// The CRD manifest is generated by `make generate/crd`; assert the
	// discriminated metric contracts are enforced at the schema level, not only
	// in Go code.
	path := filepath.Join("..", "..", "config", "crd", "bases", "autoscaling.pingcap.com_autoscalers.yaml")
	data, err := os.ReadFile(path)
	if err != nil {
		t.Fatalf("read CRD manifest: %v", err)
	}
	if !strings.Contains(string(data), "x-kubernetes-validations") {
		t.Fatal("CRD manifest missing x-kubernetes-validations")
	}
	if !strings.Contains(string(data), "metrics.resource must be set if and only if metrics.type") {
		t.Fatal("CRD manifest missing metric resource CEL rule")
	}
	if !strings.Contains(string(data), "averageUtilization must be set if and only if") {
		t.Fatal("CRD manifest missing metric target CEL rule")
	}
	if !strings.Contains(string(data), "Sets lists independent tier generation sets") {
		t.Fatal("CRD manifest missing tier sets schema")
	}
	if !strings.Contains(string(data), "exactly one of variants or range") {
		t.Fatal("CRD manifest missing tier resource variant/range CEL rule")
	}
	if strings.Contains(string(data), "matchers:") {
		t.Fatal("CRD manifest must not include configmap-loaded tier template schema")
	}
}
