package server

import (
	"context"
	"encoding/json"
	"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/k8s"
	"github.com/tidbcloud/cluster-service-ng/pkg/template/render"
	apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
	"k8s.io/apiextensions-apiserver/pkg/apiserver/validation"
	"sigs.k8s.io/yaml"
)

func TestBuildTiDBGroupAutoscalerTemplateArgs(t *testing.T) {
	sharedPool := &entity.SharedPool{SharedPool: apiv1.SharedPool{
		Id: "pool-1",
		ComponentGroups: []*apiv1.Component{{
			Id:            "group-1",
			ComponentType: apiv1.ComponentType_TIDB,
			NodeQuota: &apiv1.NodeQuota{
				MinNodeCount: 2,
				MaxNodeCount: 8,
			},
		}},
	}}
	nodeSpecMap := map[string][]*entity.NodeSpec{
		"4C16G": {
			{Cpu: 4, MemoryGi: 16, AvailableCpu: 3, AvailableCpuMilli: 3470, AvailableMemoryMi: 14079, MaxCpuMilli: 3920, MaxMemoryMi: 14079},
		},
		"8C16G": {
			{Cpu: 8, MemoryGi: 16, AvailableCpu: 7, AvailableCpuMilli: 7460, AvailableMemoryMi: 14078, MaxCpuMilli: 7910, MaxMemoryMi: 14078},
		},
		"16C32G": {
			{Cpu: 16, MemoryGi: 32, AvailableCpu: 15, AvailableCpuMilli: 15440, AvailableMemoryMi: 29326, MaxCpuMilli: 15890, MaxMemoryMi: 29326},
		},
		"8C32G": {
			{Cpu: 8, MemoryGi: 32, AvailableCpu: 7, AvailableCpuMilli: 7460, AvailableMemoryMi: 27585, MaxCpuMilli: 7460, MaxMemoryMi: 27585},
		},
	}
	ruConfig := &entity.RuConfig{RuConfig: apiv1.RuConfig{Components: map[string]*apiv1.RuConfig_ComponentsMap{
		"tidb": {NodeSpecRuRange: map[string]*apiv1.RuConfig_RuRange{
			"4C16G":  {},
			"8C16G":  {},
			"16C32G": {},
			"8C32G":  {}, // Legal for other components, but not for TiDB autoscaling.
		}},
	}}}

	templateArgsJSON, err := buildTiDBGroupAutoscalerTemplateArgs(
		context.Background(), sharedPool, &entity.TiDBGroup{TiDBGroup: apiv1.TiDBGroup{Id: "group-1"}}, nodeSpecMap, ruConfig,
	)
	require.NoError(t, err)

	var templateArgs map[string]string
	require.NoError(t, json.Unmarshal([]byte(templateArgsJSON), &templateArgs))
	assert.Equal(t, map[string]string{
		"SharedPoolId":   "pool-1",
		"ComponentGroup": "db-group-1",
		"MinReplicas":    "2",
		"MaxReplicas":    "8",
		"CPUVariants":    `["3257m","7220m","15200m"]`,
		"MemoryVariants": `["13115Mi","13114Mi","28041Mi"]`,
	}, templateArgs)
}

func TestBuildTiDBGroupAutoscalerTemplateArgsRejectsUnsupportedTiDBTier(t *testing.T) {
	sharedPool := &entity.SharedPool{SharedPool: apiv1.SharedPool{ComponentGroups: []*apiv1.Component{{
		Id:            "group-1",
		ComponentType: apiv1.ComponentType_TIDB,
		NodeQuota:     &apiv1.NodeQuota{MinNodeCount: 1, MaxNodeCount: 2},
	}}}}
	ruConfig := &entity.RuConfig{RuConfig: apiv1.RuConfig{Components: map[string]*apiv1.RuConfig_ComponentsMap{
		"tidb": {NodeSpecRuRange: map[string]*apiv1.RuConfig_RuRange{"8C32G": {}}},
	}}}

	_, err := buildTiDBGroupAutoscalerTemplateArgs(
		context.Background(), sharedPool, &entity.TiDBGroup{TiDBGroup: apiv1.TiDBGroup{Id: "group-1"}}, map[string][]*entity.NodeSpec{
			"8C32G": {{Cpu: 8, MemoryGi: 32, AvailableCpu: 7, AvailableMemoryMi: 27585}},
		}, ruConfig,
	)
	require.ErrorContains(t, err, "no legal TiDB vertical tiers")
}

// TestTiDBGroupAutoscalerTemplateConformsToCRD validates the real builder and
// template output against the checked-in CRD schema.
func TestTiDBGroupAutoscalerTemplateConformsToCRD(t *testing.T) {
	sharedPool := &entity.SharedPool{SharedPool: apiv1.SharedPool{
		Id: "pool-1",
		ComponentGroups: []*apiv1.Component{{
			Id:            "group-1",
			ComponentType: apiv1.ComponentType_TIDB,
			NodeQuota: &apiv1.NodeQuota{
				MinNodeCount: 2,
				MaxNodeCount: 8,
			},
		}},
	}}
	nodeSpecMap := map[string][]*entity.NodeSpec{
		"4C16G": {
			{Cpu: 4, MemoryGi: 16, AvailableCpu: 3, AvailableCpuMilli: 3470, AvailableMemoryMi: 14079, MaxCpuMilli: 3920, MaxMemoryMi: 14079},
		},
		"8C16G": {
			{Cpu: 8, MemoryGi: 16, AvailableCpu: 7, AvailableCpuMilli: 7460, AvailableMemoryMi: 14078, MaxCpuMilli: 7910, MaxMemoryMi: 14078},
		},
		"16C32G": {
			{Cpu: 16, MemoryGi: 32, AvailableCpu: 15, AvailableCpuMilli: 15440, AvailableMemoryMi: 29326, MaxCpuMilli: 15890, MaxMemoryMi: 29326},
		},
	}
	ruConfig := &entity.RuConfig{RuConfig: apiv1.RuConfig{Components: map[string]*apiv1.RuConfig_ComponentsMap{
		"tidb": {NodeSpecRuRange: map[string]*apiv1.RuConfig_RuRange{
			"4C16G":  {},
			"8C16G":  {},
			"16C32G": {},
		}},
	}}}

	templateArgsJSON, err := buildTiDBGroupAutoscalerTemplateArgs(
		context.Background(), sharedPool, &entity.TiDBGroup{TiDBGroup: apiv1.TiDBGroup{Id: "group-1"}}, nodeSpecMap, ruConfig,
	)
	require.NoError(t, err)
	var templateArgs map[string]string
	require.NoError(t, json.Unmarshal([]byte(templateArgsJSON), &templateArgs))
	templateArgs["TargetUID"] = "target-uid"

	repoRoot, err := os.Getwd()
	require.NoError(t, err)
	templatePath := filepath.Join(repoRoot, "..", "..", "deployments", "templates", "tidb_group_autoscaler.templ")
	templateData, err := os.ReadFile(templatePath)
	require.NoError(t, err)
	obj, _, err := k8s.GenerateUnstructuredFromYaml(render.RenderContent(string(templateData), templateArgs))
	require.NoError(t, err)
	assert.Equal(t, "DRY_RUN", obj.Object["spec"].(map[string]any)["mode"])

	crdPath := filepath.Join(repoRoot, "..", "..", "internal", "capacity-planner", "config", "crd", "bases", "autoscaling.pingcap.com_autoscalers.yaml")
	crdData, err := os.ReadFile(crdPath)
	require.NoError(t, err)

	var crd apiextensionsv1.CustomResourceDefinition
	require.NoError(t, yaml.Unmarshal(crdData, &crd))

	var schema *apiextensionsv1.JSONSchemaProps
	for i := range crd.Spec.Versions {
		if crd.Spec.Versions[i].Name == "v1alpha1" {
			schema = crd.Spec.Versions[i].Schema.OpenAPIV3Schema
			break
		}
	}
	require.NotNil(t, schema, "v1alpha1 schema not found in CRD")

	internalSchema := &apiextensions.JSONSchemaProps{}
	require.NoError(t, apiextensionsv1.Convert_v1_JSONSchemaProps_To_apiextensions_JSONSchemaProps(schema, internalSchema, nil))

	validator, _, err := validation.NewSchemaValidator(internalSchema)
	require.NoError(t, err)

	errs := validation.ValidateCustomResource(nil, obj.Object, validator)
	require.Empty(t, errs.ToAggregate(), "built autoscaler spec does not conform to checked-in CRD schema")
}

// TestTiDBAutoscalingNodeSpecsMatchCapacityPlannerTemplate keeps the TiDB-only
// node spec allowlist aligned with the capacity planner's TiDB tier template.
func TestTiDBAutoscalingNodeSpecsMatchCapacityPlannerTemplate(t *testing.T) {
	repoRoot, err := os.Getwd()
	require.NoError(t, err)
	// domain/server is at <repo-root>/domain/server.
	configPath := filepath.Join(repoRoot, "..", "..", "internal", "capacity-planner", "config", "tier-templates.yaml")
	data, err := os.ReadFile(configPath)
	require.NoError(t, err)

	var config struct {
		Templates map[string]struct {
			Params []struct {
				Name   string   `yaml:"name"`
				Values []string `yaml:"values"`
			} `yaml:"params"`
		} `yaml:"templates"`
	}
	require.NoError(t, yaml.Unmarshal(data, &config))

	template, ok := config.Templates["tidb-node"]
	require.True(t, ok, "tidb-node template is required")

	values := map[string][]string{}
	for _, param := range template.Params {
		values[param.Name] = param.Values
	}
	require.Equal(t, []string{"4C16G", "8C16G", "16C32G"}, values["nodespec"])
	require.Len(t, tidbAutoscalingNodeSpecs, len(values["nodespec"]))
	for _, nodeSpec := range values["nodespec"] {
		_, ok := tidbAutoscalingNodeSpecs[nodeSpec]
		require.True(t, ok, "capacity planner TiDB tier %s must be allowed by CSNG", nodeSpec)
	}
}
