package action

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

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	apiv1 "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"
	"github.com/tidbcloud/cluster-service-ng/domain/entity"
	"github.com/tidbcloud/cluster-service-ng/pkg/metricsprovider"
	"github.com/tidbcloud/cluster-service-ng/pkg/template/render"
	templateversion "github.com/tidbcloud/cluster-service-ng/pkg/template/version"
	autoscalingv2 "k8s.io/api/autoscaling/v2"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/utils/ptr"
	"sigs.k8s.io/yaml"
)

func TestWorkerHPANameMatchesTemplate(t *testing.T) {
	templatePath := "../../../deployments/templates"
	tests := []struct {
		name          string
		templateName  string
		componentType apiv1.ComponentType
	}{
		{
			name:          "TiKV worker",
			templateName:  "tikv_worker_hpa",
			componentType: apiv1.ComponentType_TIKV_WORKER,
		},
		{
			name:          "coprocessor worker",
			templateName:  "copr_worker_hpa",
			componentType: apiv1.ComponentType_COPROCESSOR_WORKER,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			comp := &entity.Component{
				Id:            "cluster-1",
				ComponentType: tt.componentType,
			}
			componentGroup := entity.GetComponentGroupName(comp)

			data, err := os.ReadFile(filepath.Join(templatePath, tt.templateName+".templ"))
			require.NoError(t, err)
			templateLib := render.NewTemplateLibrary()
			templateLib.SafeNewTemplate(tt.templateName, string(data))

			yamlStr, err := templateLib.Render(tt.templateName, map[string]string{
				"ComponentGroup":        componentGroup,
				"ComponentType":         entity.GetComponentTypeName(comp),
				"ClusterId":             "123",
				"MinNodeCount":          "1",
				"MaxNodeCount":          "3",
				"ScaleDownPolicy":       string(autoscalingv2.DisabledPolicySelect),
				"ScaleUpPolicy":         string(autoscalingv2.MaxChangePolicySelect),
				"CPUAverageUtilization": "70",
			})
			require.NoError(t, err)

			var hpa autoscalingv2.HorizontalPodAutoscaler
			require.NoError(t, yaml.Unmarshal([]byte(yamlStr), &hpa))
			require.Equal(t, hpa.Name, getHPAName(comp))
		})
	}
}

func TestHPACompatibilityTemplatesRenderAcrossExecutorVersions(t *testing.T) {
	templatePath := "../../../deployments/templates"
	templates := []string{
		"copr_worker_hpa",
		"tidb_group_hpa",
		"tidb_worker_hpa",
		"tikv_worker_hpa",
		"tiproxy_group_hpa",
	}
	contracts := []struct {
		name          string
		scaleUpPolicy string
		wantPolicy    *autoscalingv2.ScalingPolicySelect
	}{
		{
			name:          "feature executor can disable scale-up",
			scaleUpPolicy: string(autoscalingv2.DisabledPolicySelect),
			wantPolicy:    ptr.To(autoscalingv2.DisabledPolicySelect),
		},
		{
			name:          "candidate executor keeps explicit max",
			scaleUpPolicy: string(autoscalingv2.MaxChangePolicySelect),
			wantPolicy:    ptr.To(autoscalingv2.MaxChangePolicySelect),
		},
		{
			name:       "older executor can omit scale-up policy",
			wantPolicy: nil,
		},
	}

	for _, templateName := range templates {
		t.Run(templateName, func(t *testing.T) {
			data, err := os.ReadFile(filepath.Join(templatePath, templateName+".templ"))
			require.NoError(t, err)
			templateLib := render.NewTemplateLibrary()
			templateLib.SafeNewTemplate(templateName, string(data))

			for _, contract := range contracts {
				t.Run(contract.name, func(t *testing.T) {
					args := map[string]string{
						"ComponentGroup":        "db-component",
						"ComponentType":         "component",
						"ClusterId":             "123",
						"MinNodeCount":          "1",
						"MaxNodeCount":          "3",
						"ScaleDownPolicy":       string(autoscalingv2.DisabledPolicySelect),
						"CPUAverageUtilization": "70",
					}
					if contract.scaleUpPolicy != "" {
						args["ScaleUpPolicy"] = contract.scaleUpPolicy
					}

					yamlStr, err := templateLib.Render(templateName, args)
					require.NoError(t, err)
					require.NotContains(t, yamlStr, "{{!")

					var hpa autoscalingv2.HorizontalPodAutoscaler
					require.NoError(t, yaml.Unmarshal([]byte(yamlStr), &hpa))
					require.NotNil(t, hpa.Spec.Behavior)
					require.NotNil(t, hpa.Spec.Behavior.ScaleUp)
					require.NotNil(t, hpa.Spec.Behavior.ScaleUp.StabilizationWindowSeconds)
					require.NotEmpty(t, hpa.Spec.Behavior.ScaleUp.Policies)
					if contract.wantPolicy == nil {
						require.Nil(t, hpa.Spec.Behavior.ScaleUp.SelectPolicy)
					} else {
						require.NotNil(t, hpa.Spec.Behavior.ScaleUp.SelectPolicy)
						require.Equal(t, *contract.wantPolicy, *hpa.Spec.Behavior.ScaleUp.SelectPolicy)
					}
				})
			}
		})
	}
}

func TestHPACompatibilityTemplateVersions(t *testing.T) {
	templatePath := "../../../deployments/templates"
	expectedVersions := map[string]string{
		"copr_worker_hpa":   "3740fd3940573e292aea38a12af85ee91961ecfdcd9ccbcfbc934ce691714148",
		"tidb_group_hpa":    "5949ff0f757c3f5d51aa1d8c1a2e0a3678f900dde2664182bb8ad31dfdc37071",
		"tidb_worker_hpa":   "6fa6f32bb9505cc95ac28f0f4a36c385caa0ff88067fabc0db9fff2e4870b2f3",
		"tikv_worker_hpa":   "bbc6f393dbe603c7e4a62873323e9f7e18e1f362e7e6c03fe1004d7acadb3092",
		"tiproxy_group_hpa": "29600cd3a9ff98a59f8e534ed66af76f5f47ff12fd5d55f09dba27c53b595fc2",
	}

	for templateName, expectedVersion := range expectedVersions {
		t.Run(templateName, func(t *testing.T) {
			data, err := os.ReadFile(filepath.Join(templatePath, templateName+".templ"))
			require.NoError(t, err)
			content := templateversion.NormalizeTemplateContent(string(data))
			require.Equal(t, expectedVersion, templateversion.ComputeTemplateVersion(content))
		})
	}
}

func TestRenderTiProxyGroupHPA_ScaleUpSelectPolicyMax(t *testing.T) {
	templateLib := render.NewTemplateLibrary()
	tplPath := "../../../deployments/templates"

	data, err := os.ReadFile(filepath.Join(tplPath, "tiproxy_group_hpa.templ"))
	require.NoError(t, err)
	templateLib.SafeNewTemplate("tiproxy_group_hpa", string(data))

	yamlStr, err := templateLib.Render("tiproxy_group_hpa", map[string]string{
		"ComponentGroup":  "db-tiproxy",
		"ComponentType":   "tiproxy",
		"ClusterId":       "123",
		"MinNodeCount":    "1",
		"MaxNodeCount":    "3",
		"ScaleDownPolicy": "Min",
		"ScaleUpPolicy":   "Max",
	})
	require.NoError(t, err)

	var hpa autoscalingv2.HorizontalPodAutoscaler
	require.NoError(t, yaml.Unmarshal([]byte(yamlStr), &hpa))
	require.NotNil(t, hpa.Spec.Behavior)
	require.NotNil(t, hpa.Spec.Behavior.ScaleUp)
	require.NotNil(t, hpa.Spec.Behavior.ScaleUp.SelectPolicy)
	require.Equal(t, autoscalingv2.MaxChangePolicySelect, *hpa.Spec.Behavior.ScaleUp.SelectPolicy)
	require.NotNil(t, hpa.Spec.Behavior.ScaleDown)
	require.NotNil(t, hpa.Spec.Behavior.ScaleDown.SelectPolicy)
	require.Equal(t, autoscalingv2.MinChangePolicySelect, *hpa.Spec.Behavior.ScaleDown.SelectPolicy)

	require.Len(t, hpa.Spec.Metrics, 3)
	require.Equal(t, autoscalingv2.ResourceMetricSourceType, hpa.Spec.Metrics[0].Type)
	require.Equal(t, corev1.ResourceCPU, hpa.Spec.Metrics[0].Resource.Name)
	require.Equal(t, int32(70), *hpa.Spec.Metrics[0].Resource.Target.AverageUtilization)
	require.Equal(t, autoscalingv2.ObjectMetricSourceType, hpa.Spec.Metrics[1].Type)
	require.Equal(t, metricsprovider.MetricNameExtAvgMemoryUtilization, hpa.Spec.Metrics[1].Object.Metric.Name)
	require.Equal(t, int64(60), hpa.Spec.Metrics[1].Object.Target.Value.Value())
	require.Equal(t, metricsprovider.MetricNameExtAvgBandwidthUtilization, hpa.Spec.Metrics[2].Object.Metric.Name)
	require.Equal(t, int64(50), hpa.Spec.Metrics[2].Object.Target.Value.Value())

	hpa.Name = "tiproxygroup-db-tiproxy"
	require.True(t, reconcileExistingHPA(&hpa, &entity.Component{
		ComponentType: apiv1.ComponentType_TIPROXY,
	}, 1, 3, autoscalingv2.MinChangePolicySelect, autoscalingv2.MaxChangePolicySelect, false))
	// Resource CPU stays at 70 (scale-out); gate is prepended for scale-in at 30%.
	assert.Equal(t, metricsprovider.MetricNameExtTiProxyCPUScaleInGate, hpa.Spec.Metrics[0].Object.Metric.Name)
	assert.Equal(t, "30", hpa.Annotations[metricsprovider.AnnotationTiProxyCPUScaleInThreshold])
	assert.Equal(t, int32(70), *hpa.Spec.Metrics[1].Resource.Target.AverageUtilization)
	assert.Equal(t, int64(60), hpa.Spec.Metrics[2].Object.Target.Value.Value())
	assert.Equal(t, int64(50), hpa.Spec.Metrics[3].Object.Target.Value.Value())
}

func TestRenderTiDBGroupHPA_ScaleInDisabledByDefault(t *testing.T) {
	templateLib := render.NewTemplateLibrary()
	tplPath := "../../../deployments/templates"

	data, err := os.ReadFile(filepath.Join(tplPath, "tidb_group_hpa.templ"))
	require.NoError(t, err)
	templateLib.SafeNewTemplate("tidb_group_hpa", string(data))

	yamlStr, err := templateLib.Render("tidb_group_hpa", map[string]string{
		"ComponentGroup":  "db-tidb",
		"ComponentType":   "tidb",
		"ClusterId":       "123",
		"MinNodeCount":    "1",
		"MaxNodeCount":    "3",
		"ScaleDownPolicy": "Disabled",
		"ScaleUpPolicy":   "Max",
	})
	require.NoError(t, err)

	var hpa autoscalingv2.HorizontalPodAutoscaler
	require.NoError(t, yaml.Unmarshal([]byte(yamlStr), &hpa))

	// No scale-in gate annotation or metric by default — opt-in only.
	require.Nil(t, hpa.Annotations)
	for _, m := range hpa.Spec.Metrics {
		if m.Type == autoscalingv2.ObjectMetricSourceType && m.Object != nil {
			require.NotEqual(t, metricsprovider.MetricNameExtTiDBCPUScaleInGate, m.Object.Metric.Name)
		}
	}

	require.NotNil(t, hpa.Spec.Behavior)
	require.NotNil(t, hpa.Spec.Behavior.ScaleDown)
	require.NotNil(t, hpa.Spec.Behavior.ScaleDown.SelectPolicy)
	require.Equal(t, autoscalingv2.DisabledPolicySelect, *hpa.Spec.Behavior.ScaleDown.SelectPolicy)
}
