package cluster

import (
	"context"
	"testing"
	"time"

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

	globalcommonv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/common/v1"
	globaldedicatedv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/dedicated/v1"
	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/domain/adapter/dataflow/mock_dataflow"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/adapter/dedicatedglobal/mock_dedicatedglobal"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/pkg/common"
	"github.com/tidbcloud/tidb-management-service/utilities/grpc/metadata"
)

func TestListDedicatedCluster(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())

	t.Run("List should success", func(t *testing.T) {
		// Create a cluster first
		ctx := grpcmetadata.NewIncomingContext(t.Context(), grpcmetadata.New(map[string]string{
			metadata.UserIDHeaderKey:           "1",
			metadata.OrgIDHeaderKey:            "1",
			metadata.DefaultProjectIDHeaderKey: "4",
			metadata.ProjectListHeaderKey:      "1,2,3,4",
			metadata.IsCheckPermissionKey:      "true",
		}))

		req := &openapidedicatedv1beta1.CreateClusterRequest{
			Cluster: &openapidedicatedv1beta1.Cluster{
				DisplayName: "list-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: "4",
				},
			},
		}
		_, err := service.CreateCluster(ctx, req)
		g.Expect(err).ShouldNot(HaveOccurred())

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

		// Build get request
		clusters, err := service.ListClusters(ctx, &openapidedicatedv1beta1.ListClustersRequest{ProjectId: "4"})

		g.Expect(err).ShouldNot(HaveOccurred())
		g.Expect(clusters).ShouldNot(BeNil())
		c, ok := lo.Find(clusters.Clusters, func(cluster *openapidedicatedv1beta1.Cluster) bool {
			return cluster.GetDisplayName() == "list-test-1"
		})
		g.Expect(ok).Should(BeTrue())
		g.Expect(c.DisplayName).Should(Equal("list-test-1"))
		g.Expect(c.Version).ShouldNot(BeEmpty())
		g.Expect(c.GetRegionDisplayName()).Should(Equal(fakeRegionDisplayName(c.GetRegionId())))
	})
}

func TestInsufficientResourceField(t *testing.T) {
	ctrl := gomock.NewController(t, gomock.WithOverridableExpectations())

	gs := mock_dedicatedglobal.NewMockDedicatedGlobal(ctrl)
	gs.EXPECT().
		GetDefaultTidbVersion(gomock.Any(), gomock.Any()).
		Return(&globaldedicatedv1.GetDefaultTidbVersionResponse{
			Version: "v8.5.2",
		}, nil).
		AnyTimes()
	gs.EXPECT().
		GetSpu(gomock.Any(), gomock.Any()).
		Return(&globaldedicatedv1.Spu{
			Name:          "8C16G",
			ComponentType: globaldedicatedv1.ComponentType_TIKV,
			SupportedDiskPairs: []*globaldedicatedv1.Spu_DiskPair{
				{
					DataVolumeType:    globaldedicatedv1.StorageMu_IO2,
					RaftlogVolumeType: globaldedicatedv1.StorageMu_VOLUME_TYPE_UNSPECIFIED,
				},
				{
					DataVolumeType:    globaldedicatedv1.StorageMu_GP3,
					RaftlogVolumeType: globaldedicatedv1.StorageMu_VOLUME_TYPE_UNSPECIFIED,
				},
				{
					DataVolumeType:    globaldedicatedv1.StorageMu_GP3,
					RaftlogVolumeType: globaldedicatedv1.StorageMu_GP3,
				},
				{
					DataVolumeType:    globaldedicatedv1.StorageMu_GP3,
					RaftlogVolumeType: globaldedicatedv1.StorageMu_IO2,
				},
			},
		}, nil).
		AnyTimes()

	df := mock_dataflow.NewMockDataFlow(ctrl)

	account := mock_account.NewMockClient(ctrl)
	account.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()
	account.EXPECT().
		GetTenantThirdPartyAccountProviders(gomock.Any(), gomock.Any()).
		Return([]openapicommonv1beta1.Region_CloudProvider{}, nil).
		AnyTimes()
	account.EXPECT().
		UpsertClusterRelations(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
		Return(nil).
		AnyTimes()

	svc, err := initService(gs, df, account)
	require.NoError(t, err)

	t.Run("insufficient resource field should present", func(t *testing.T) {
		ctx := grpcmetadata.NewIncomingContext(t.Context(), grpcmetadata.New(map[string]string{
			metadata.UserIDHeaderKey:           "1",
			metadata.OrgIDHeaderKey:            "1",
			metadata.DefaultProjectIDHeaderKey: "4",
			metadata.ProjectListHeaderKey:      "1,2,3,4",
			metadata.IsCheckPermissionKey:      "true",
		}))

		mockedGlobalCluster := &globaldedicatedv1.Cluster{
			RegionId:               globalcommonv1.Region_AWS_US_WEST_2,
			CloudProviderId:        globalcommonv1.CloudProvider_AWS,
			InsufficientVmResource: true,
		}

		gs.EXPECT().CreateCluster(gomock.Any(), gomock.Any()).Return(mockedGlobalCluster, nil).Times(1)
		gs.EXPECT().GetCluster(gomock.Any(), gomock.Any()).Return(mockedGlobalCluster, nil).AnyTimes()

		createdCluster, err := svc.CreateCluster(ctx, &openapidedicatedv1beta1.CreateClusterRequest{
			Cluster: &openapidedicatedv1beta1.Cluster{
				DisplayName: "insufficient-resource-test",
				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: "4",
				},
			},
		})
		require.NoError(t, err)

		gotCluster, err := svc.GetCluster(t.Context(), &openapidedicatedv1beta1.GetClusterRequest{
			ClusterId: createdCluster.GetClusterId(),
		})
		require.NoError(t, err)

		// Check if the insufficient resource field is present.
		assert.NotEmpty(t, gotCluster.GetAnnotations())
		v, ok := gotCluster.GetAnnotations()[common.InsufficientVmResourcesKey]
		assert.True(t, ok)
		assert.Equal(t, "true", v)
	})
}
