package action

import (
	"context"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
	"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/domain/workflow"
	dataplanemocks "github.com/tidbcloud/cluster-service-ng/pkg/dataplane/mocks"
	"github.com/tidbcloud/cluster-service-ng/pkg/runtime/actions"
	repomocks "github.com/tidbcloud/cluster-service-ng/pkg/storage/repo/mocks"
	templateversion "github.com/tidbcloud/cluster-service-ng/pkg/template/version"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/types"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"
)

var autoscalerGVK = schema.GroupVersionKind{Group: "autoscaling.pingcap.com", Version: "v1alpha1", Kind: "Autoscaler"}

func TestTiDBGroupAutoscalerTemplateIsRegistered(t *testing.T) {
	templates, err := templateversion.LoadTemplatesFromPath("../../../deployments/templates")
	require.NoError(t, err)
	require.Contains(t, templates, "tidb_group_autoscaler")
}

func TestApplyAutoscalerActionCreatesIdempotentlyAndPreservesStatus(t *testing.T) {
	const (
		sharedPoolID = "pool-1"
		tidbGroupID  = "group-1"
		namespace    = "tidbpool-1"
		name         = "db-group-1"
	)
	scheme := runtime.NewScheme()
	scheme.AddKnownTypeWithName(tidbGroupGVK, &unstructured.Unstructured{})
	scheme.AddKnownTypeWithName(autoscalerGVK, &unstructured.Unstructured{})
	scheme.AddKnownTypeWithName(autoscalerGVK.GroupVersion().WithKind("AutoscalerList"), &unstructured.UnstructuredList{})
	target := &unstructured.Unstructured{}
	target.SetGroupVersionKind(tidbGroupGVK)
	target.SetNamespace(namespace)
	target.SetName(name)
	target.SetUID(types.UID("target-uid"))
	remoteClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(target).Build()

	sharedPool := &entity.SharedPool{EnableCapacityPlanner: true}
	sharedPool.DataplaneName = "dp-1"
	sharedPoolRepo := repomocks.NewSharedPoolRepo(t)
	sharedPoolRepo.On("Load", mock.Anything, sharedPoolID, false).Return(sharedPool, nil).Twice()
	dataplaneManager := dataplanemocks.NewManager(t)
	dataplaneManager.On("GetClient", "dp-1").Return(remoteClient, nil).Twice()
	action := &ApplyAutoscalerAction{
		sharedPoolRepo:     sharedPoolRepo,
		dataplaneManager:   dataplaneManager,
		templateVersionSvc: newAutoscalerTemplateVersionSvc(t),
	}
	args := actions.ActionArgs{
		workflow.SharedPoolIdKey: actions.ActionArgString(sharedPoolID),
		workflow.TiDBGroupIdKey:  actions.ActionArgString(tidbGroupID),
		workflow.AutoscalerTemplateArgsKey: actions.ActionArgString(`{
			"ComponentGroup":"db-group-1",
			"SharedPoolId":"pool-1",
			"MinReplicas":"1",
			"MaxReplicas":"4",
			"CPUVariants":"[\"3257m\",\"7220m\",\"15200m\"]",
			"MemoryVariants":"[\"12287Mi\",\"12287Mi\",\"26300Mi\"]"
		}`),
	}

	require.NoError(t, action.Invoke(args))
	autoscaler := &unstructured.Unstructured{}
	autoscaler.SetGroupVersionKind(autoscalerGVK)
	require.NoError(t, remoteClient.Get(context.Background(), client.ObjectKey{Namespace: namespace, Name: name}, autoscaler))
	assert.Equal(t, "DRY_RUN", autoscaler.Object["spec"].(map[string]any)["mode"])
	assert.Equal(t, "tidb_group_autoscaler", autoscaler.GetAnnotations()[templateversion.TemplateNameAnnotationKey])
	assert.Equal(t, "test-sha256", autoscaler.GetAnnotations()[templateversion.TemplateVersionAnnotationKey])
	require.Len(t, autoscaler.GetOwnerReferences(), 1)
	assert.Equal(t, types.UID("target-uid"), autoscaler.GetOwnerReferences()[0].UID)

	autoscaler.Object["status"] = map[string]any{"phase": "Pending"}
	require.NoError(t, remoteClient.Update(context.Background(), autoscaler))
	require.NoError(t, action.Invoke(args))

	list := &unstructured.UnstructuredList{}
	list.SetGroupVersionKind(autoscalerGVK.GroupVersion().WithKind("AutoscalerList"))
	require.NoError(t, remoteClient.List(context.Background(), list, client.InNamespace(namespace)))
	require.Len(t, list.Items, 1)
	assert.Equal(t, "Pending", list.Items[0].Object["status"].(map[string]any)["phase"])
	assert.Equal(t, []metav1.OwnerReference{{
		APIVersion: "core.pingcap.com/v1alpha1",
		Kind:       "TiDBGroup",
		Name:       name,
		UID:        types.UID("target-uid"),
		Controller: boolPointer(true),
	}}, list.Items[0].GetOwnerReferences())
}

func TestApplyAutoscalerActionSkipsWithoutTemplateArgs(t *testing.T) {
	sharedPoolRepo := repomocks.NewSharedPoolRepo(t)
	dataplaneManager := dataplanemocks.NewManager(t)
	action := &ApplyAutoscalerAction{sharedPoolRepo: sharedPoolRepo, dataplaneManager: dataplaneManager}

	require.NoError(t, action.Invoke(actions.ActionArgs{}))
	sharedPoolRepo.AssertNotCalled(t, "Load")
	dataplaneManager.AssertNotCalled(t, "GetClient")
}

func boolPointer(value bool) *bool {
	return &value
}

func newAutoscalerTemplateVersionSvc(t *testing.T) *templateversion.Service {
	t.Helper()
	templates, err := templateversion.LoadTemplatesFromPath("../../../deployments/templates")
	require.NoError(t, err)

	templateRepo := repomocks.NewTemplateRegistryRepo(t)
	templateRepo.On("ListByTemplateName", mock.Anything, "tidb_group_autoscaler").Return([]*entity.TemplateRegistry{{
		TemplateVersionInfo: apiv1.TemplateVersionInfo{
			TemplateName: "tidb_group_autoscaler",
			Version:      "test-sha256",
			Status:       apiv1.TemplateVersionStatus_TEMPLATE_VERSION_STATUS_AVAILABLE,
		},
		Content: templates["tidb_group_autoscaler"],
	}}, nil).Twice()

	return templateversion.NewService(templateRepo, 3)
}
