package cluster

import (
	"context"
	"testing"
	"time"

	. "github.com/onsi/gomega"
	"github.com/samber/lo"
	accountsdk "github.com/tidbcloud/account/idl/pbgen/proto/account"
	"go.uber.org/mock/gomock"
	grpcmetadata "google.golang.org/grpc/metadata"

	openapicommonv1beta1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/common/v1beta1"
	openapidedicatedv1beta1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/dedicated/v1beta1"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/adapter/account/mock_account"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/pkg/common"
	"github.com/tidbcloud/tidb-management-service/utilities/grpc/metadata"
)

func TestGetInstanceIdIndex(t *testing.T) {
	t.Parallel()

	g := NewWithT(t)

	case1 := "tidb-1"
	g.Expect(getInstanceIdIndex(case1)).Should(Equal(1))

	case2 := "tikv-1"
	g.Expect(getInstanceIdIndex(case2)).Should(Equal(1))

	case3 := "tiflash-1"
	g.Expect(getInstanceIdIndex(case3)).Should(Equal(1))

	case4 := "group-tidb-1"
	g.Expect(getInstanceIdIndex(case4)).Should(Equal(1))

	case5 := "group-x-tidb-1"
	g.Expect(getInstanceIdIndex(case5)).Should(Equal(1))

	case6 := "tiproxy-1"
	g.Expect(getInstanceIdIndex(case6)).Should(Equal(1))

	case7 := "group-x-tiproxy-1"
	g.Expect(getInstanceIdIndex(case7)).Should(Equal(1))
}

func TestGetComponentType(t *testing.T) {
	t.Parallel()

	g := NewWithT(t)

	case1 := "tidb-1"
	compType, groupName := getComponentType(case1)
	g.Expect(compType).Should(Equal(openapidedicatedv1beta1.ComponentType_TIDB))
	g.Expect(groupName).Should(Equal(""))

	case2 := "tikv-1"
	compType, groupName = getComponentType(case2)
	g.Expect(compType).Should(Equal(openapidedicatedv1beta1.ComponentType_TIKV))
	g.Expect(groupName).Should(Equal(""))

	case3 := "tiflash-1"
	compType, groupName = getComponentType(case3)
	g.Expect(compType).Should(Equal(openapidedicatedv1beta1.ComponentType_TIFLASH))
	g.Expect(groupName).Should(Equal(""))

	case4 := "group-tidb-1"
	compType, groupName = getComponentType(case4)
	g.Expect(compType).Should(Equal(openapidedicatedv1beta1.ComponentType_TIDB))
	g.Expect(groupName).Should(Equal("group"))

	case5 := "group-x-tidb-1"
	compType, groupName = getComponentType(case5)
	g.Expect(compType).Should(Equal(openapidedicatedv1beta1.ComponentType_TIDB))
	g.Expect(groupName).Should(Equal("group-x"))

	case6 := "tiproxy-1"
	compType, groupName = getComponentType(case6)
	g.Expect(compType).Should(Equal(openapidedicatedv1beta1.ComponentType_TIPROXY))
	g.Expect(groupName).Should(Equal(""))

	case7 := "group-x-tiproxy-1"
	compType, groupName = getComponentType(case7)
	g.Expect(compType).Should(Equal(openapidedicatedv1beta1.ComponentType_TIPROXY))
	g.Expect(groupName).Should(Equal("group-x"))
}

func TestGetDedicatedComponentNodeInfo(t *testing.T) {
	g := NewWithT(t)

	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	mockAccountCli := mock_account.NewMockClient(ctrl)
	mockAccountCli.EXPECT().
		GetProject(gomock.Any(), gomock.Any()).
		DoAndReturn(func(ctx context.Context, projectId uint64) (*accountsdk.Project, error) {
			return &accountsdk.Project{
				Id:       projectId,
				TenantId: 1, // org id
			}, nil
		}).
		AnyTimes()
	mockAccountCli.EXPECT().
		GetTenantThirdPartyAccountProviders(gomock.Any(), gomock.Any()).
		DoAndReturn(func(ctx context.Context, id uint64) ([]openapicommonv1beta1.Region_CloudProvider, error) {
			return []openapicommonv1beta1.Region_CloudProvider{}, nil
		}).
		AnyTimes()
	mockAccountCli.EXPECT().
		UpsertClusterRelations(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
		Return(nil).
		AnyTimes()

	service, err := initServiceWithFakeClients(mockAccountCli)
	g.Expect(err).ShouldNot(HaveOccurred())

	var clusterID string
	// Create a cluster first
	ctx := grpcmetadata.NewIncomingContext(t.Context(), grpcmetadata.New(map[string]string{
		metadata.UserIDHeaderKey:           "1",
		metadata.OrgIDHeaderKey:            "1",
		metadata.DefaultProjectIDHeaderKey: "1",
		metadata.ProjectListHeaderKey:      "1,2,3",
		metadata.IsCheckPermissionKey:      "true",
	}))

	req := &openapidedicatedv1beta1.CreateClusterRequest{
		Cluster: &openapidedicatedv1beta1.Cluster{
			DisplayName: "node-test-1",
			RegionId:    "aws-us-west-2",
			TidbNodeSetting: &openapidedicatedv1beta1.Cluster_TidbNodeSetting{
				NodeSpecKey: "8C16G",
				TidbNodeGroups: []*openapidedicatedv1beta1.TidbNodeGroup{
					{
						NodeCount: 1,
					},
				},
			},
			TikvNodeSetting: &openapidedicatedv1beta1.Cluster_StorageNodeSetting{
				NodeSpecKey:   "8C16G",
				NodeCount:     3,
				StorageSizeGi: 50,
				StorageType:   openapidedicatedv1beta1.Cluster_StorageNodeSetting_Standard,
			},
			Labels: map[string]string{
				common.ProjectIDLabelKey: "1",
			},
		},
	}
	c, err := service.CreateCluster(ctx, req)
	g.Expect(err).ShouldNot(HaveOccurred())

	time.Sleep(3 * time.Second) // wait for the workflow dag to finish

	updated, nerr := service.UpdateCluster(ctx, &openapidedicatedv1beta1.UpdateClusterRequest{
		Cluster: &openapidedicatedv1beta1.UpdateClusterRequest_Cluster{
			ClusterId: c.ClusterId,
			TidbNodeSetting: &openapidedicatedv1beta1.UpdateClusterRequest_TidbNodeSetting{
				NodeSpecKey: "8C16G",
				TidbNodeGroups: []*openapidedicatedv1beta1.UpdateClusterRequest_TidbNodeSetting_TidbNodeGroup{
					{
						NodeCount: lo.ToPtr(int32(1)),
						TiproxySetting: &openapidedicatedv1beta1.TidbNodeGroup_TiProxySetting{
							NodeCount:   lo.ToPtr(int32(1)),
							NodeSpecKey: "2C4G",
						},
					},
				},
			},
		},
	})
	g.Expect(nerr).ShouldNot(HaveOccurred())
	g.Expect(updated).ShouldNot(BeNil())
	time.Sleep(3 * time.Second) // wait for the workflow dag to finish

	clusterID = c.ClusterId

	t.Run("Get tidb node info should success", func(t *testing.T) {
		nodeInfo, err := service.ListNodeInstances(ctx, &openapidedicatedv1beta1.ListNodeInstancesRequest{
			ClusterId:     clusterID,
			ComponentType: openapidedicatedv1beta1.ComponentType_TIDB,
		})
		g.Expect(err).ShouldNot(HaveOccurred())
		g.Expect(nodeInfo).ShouldNot(BeNil())
		g.Expect(nodeInfo.NodeInstances).ShouldNot(BeNil())
		g.Expect(nodeInfo.TotalSize).Should(Equal(int32(1)))
		g.Expect(nodeInfo.NodeInstances[0].InstanceId).Should(Equal("tidb-1"))
	})

	t.Run("Get tikv node info should success", func(t *testing.T) {
		nodeInfo, err := service.ListNodeInstances(ctx, &openapidedicatedv1beta1.ListNodeInstancesRequest{
			ClusterId:     clusterID,
			ComponentType: openapidedicatedv1beta1.ComponentType_TIKV,
		})
		g.Expect(err).ShouldNot(HaveOccurred())
		g.Expect(nodeInfo).ShouldNot(BeNil())
		g.Expect(nodeInfo.NodeInstances).ShouldNot(BeNil())
		g.Expect(nodeInfo.TotalSize).Should(Equal(int32(3)))
		g.Expect(nodeInfo.NodeInstances[0].InstanceId).Should(Equal("tikv-1"))
	})

	t.Run("Get node info should success", func(t *testing.T) {
		_, err := service.ListNodeInstances(ctx, &openapidedicatedv1beta1.ListNodeInstancesRequest{ClusterId: "1"})
		g.Expect(err).Should(HaveOccurred())
		g.Expect(err.Error()).Should(ContainSubstring("cluster not found"))
	})

	t.Run("Get tiproxy node info should success", func(t *testing.T) {
		nodeInfo, err := service.ListNodeInstances(ctx, &openapidedicatedv1beta1.ListNodeInstancesRequest{
			ClusterId:     clusterID,
			ComponentType: openapidedicatedv1beta1.ComponentType_TIPROXY,
		})
		g.Expect(err).ShouldNot(HaveOccurred())
		g.Expect(nodeInfo).ShouldNot(BeNil())
		g.Expect(nodeInfo.NodeInstances).ShouldNot(BeNil())
		g.Expect(nodeInfo.TotalSize).Should(Equal(int32(1)))
		g.Expect(nodeInfo.NodeInstances[0].InstanceId).Should(Equal("tiproxy-1"))
	})
}
