package cluster

import (
	"context"
	"testing"

	. "github.com/onsi/gomega"
	"go.uber.org/mock/gomock"
	"google.golang.org/grpc"
	"google.golang.org/protobuf/types/known/emptypb"

	globaldedicatedv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/dedicated/v1"
	tidbmgmtservicev1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/app/internalapi/cluster/mock_dedicatedclustersvc"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/adapter/dedicatedglobal/mock_dedicatedglobal"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/utilities/grpc/metadata"
)

func TestApplyGKEHeterogeneousDeploy(t *testing.T) {
	g := NewWithT(t)
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()
	dedicatedCli := mock_dedicatedglobal.NewMockDedicatedGlobal(ctrl)
	clusterSvc := mock_dedicatedclustersvc.NewMockService(ctrl)
	server := &serverImpl{
		dedicatedCli:        dedicatedCli,
		dedicatedClusterSvc: clusterSvc,
	}

	clusterSvc.EXPECT().
		GetCluster(gomock.Any(), uint64(3), entity.ClusterViewClusterViewLocal, false, false).
		Return(&entity.Cluster{
			OrganizationId: 1,
			ProjectId:      2,
			ClusterId:      3,
		}, nil)

	dedicatedCli.EXPECT().
		ApplyGKEHeterogeneousDeploy(gomock.Any(), gomock.Any()).
		DoAndReturn(func(
			ctx context.Context,
			req *globaldedicatedv1.ApplyGKEHeterogeneousDeployRequest,
			opts ...grpc.CallOption,
		) (*globaldedicatedv1.GKEHeterogeneousDeploy, error) {
			g.Expect(req.GetTenantId()).Should(Equal(uint64(1)))
			g.Expect(req.GetProjectId()).Should(Equal(uint64(2)))
			g.Expect(req.GetClusterId()).Should(Equal(uint64(3)))
			g.Expect(req.GetComponentType()).Should(Equal(globaldedicatedv1.ComponentType_TIKV))
			g.Expect(req.GetCurrentInstanceType()).Should(Equal("n2-standard-8"))
			g.Expect(req.GetAlternativeInstanceType()).Should(Equal("n2-standard-16"))
			g.Expect(req.GetTidbGroupId()).Should(Equal(uint64(0)))

			meta, err := metadata.ExtractRequestMeta(ctx)
			g.Expect(err).ShouldNot(HaveOccurred())
			g.Expect(meta.OrgID).Should(Equal(uint64(1)))
			g.Expect(meta.DefaultProjectID).Should(Equal(uint64(2)))
			g.Expect(meta.ClusterID).Should(Equal(uint64(3)))

			return &globaldedicatedv1.GKEHeterogeneousDeploy{
				Enabled:                 true,
				ComponentType:           req.GetComponentType(),
				CurrentInstanceType:     req.GetCurrentInstanceType(),
				AlternativeInstanceType: req.GetAlternativeInstanceType(),
				TidbGroupId:             req.GetTidbGroupId(),
			}, nil
		})

	resp, err := server.ApplyGKEHeterogeneousDeploy(context.Background(), &tidbmgmtservicev1.ApplyGKEHeterogeneousDeployRequest{
		TenantId:                1,
		ProjectId:               2,
		ClusterId:               3,
		ComponentType:           globaldedicatedv1.ComponentType_TIKV,
		CurrentInstanceType:     "n2-standard-8",
		AlternativeInstanceType: "n2-standard-16",
		TidbGroupId:             0,
	})
	g.Expect(err).ShouldNot(HaveOccurred())
	g.Expect(resp.GetEnabled()).Should(BeTrue())
	g.Expect(resp.GetComponentType()).Should(Equal(globaldedicatedv1.ComponentType_TIKV))
	g.Expect(resp.GetCurrentInstanceType()).Should(Equal("n2-standard-8"))
	g.Expect(resp.GetAlternativeInstanceType()).Should(Equal("n2-standard-16"))
	g.Expect(resp.GetTidbGroupId()).Should(Equal(uint64(0)))
}

func TestApplyGKEHeterogeneousDeployRejectsBlankInstanceType(t *testing.T) {
	g := NewWithT(t)
	server := &serverImpl{}

	resp, err := server.ApplyGKEHeterogeneousDeploy(context.Background(), &tidbmgmtservicev1.ApplyGKEHeterogeneousDeployRequest{
		TenantId:                1,
		ProjectId:               2,
		ClusterId:               3,
		ComponentType:           globaldedicatedv1.ComponentType_TIKV,
		CurrentInstanceType:     " ",
		AlternativeInstanceType: "n2-standard-16",
	})
	g.Expect(err).Should(HaveOccurred())
	g.Expect(resp).Should(BeNil())

	resp, err = server.ApplyGKEHeterogeneousDeploy(context.Background(), &tidbmgmtservicev1.ApplyGKEHeterogeneousDeployRequest{
		TenantId:                1,
		ProjectId:               2,
		ClusterId:               3,
		ComponentType:           globaldedicatedv1.ComponentType_TIKV,
		CurrentInstanceType:     "n2-standard-8",
		AlternativeInstanceType: "\t",
	})
	g.Expect(err).Should(HaveOccurred())
	g.Expect(resp).Should(BeNil())
}

func TestApplyGKEHeterogeneousDeployRejectsUnspecifiedComponentType(t *testing.T) {
	g := NewWithT(t)
	server := &serverImpl{}

	resp, err := server.ApplyGKEHeterogeneousDeploy(context.Background(), &tidbmgmtservicev1.ApplyGKEHeterogeneousDeployRequest{
		TenantId:                1,
		ProjectId:               2,
		ClusterId:               3,
		ComponentType:           globaldedicatedv1.ComponentType_COMPONENT_TYPE_UNSPECIFIED,
		CurrentInstanceType:     "n2-standard-8",
		AlternativeInstanceType: "n2-standard-16",
	})
	g.Expect(err).Should(HaveOccurred())
	g.Expect(resp).Should(BeNil())
}

func TestApplyGKEHeterogeneousDeployRejectsTidbWithoutTidbGroup(t *testing.T) {
	g := NewWithT(t)
	server := &serverImpl{}

	resp, err := server.ApplyGKEHeterogeneousDeploy(context.Background(), &tidbmgmtservicev1.ApplyGKEHeterogeneousDeployRequest{
		TenantId:                1,
		ProjectId:               2,
		ClusterId:               3,
		ComponentType:           globaldedicatedv1.ComponentType_TIDB,
		CurrentInstanceType:     "n2-standard-8",
		AlternativeInstanceType: "n2-standard-16",
	})
	g.Expect(err).Should(HaveOccurred())
	g.Expect(resp).Should(BeNil())
}

func TestApplyGKEHeterogeneousDeployRejectsNonTidbWithTidbGroup(t *testing.T) {
	g := NewWithT(t)
	server := &serverImpl{}

	resp, err := server.ApplyGKEHeterogeneousDeploy(context.Background(), &tidbmgmtservicev1.ApplyGKEHeterogeneousDeployRequest{
		TenantId:                1,
		ProjectId:               2,
		ClusterId:               3,
		ComponentType:           globaldedicatedv1.ComponentType_TIKV,
		CurrentInstanceType:     "n2-standard-8",
		AlternativeInstanceType: "n2-standard-16",
		TidbGroupId:             4,
	})
	g.Expect(err).Should(HaveOccurred())
	g.Expect(resp).Should(BeNil())
}

func TestApplyGKEHeterogeneousDeployRejectsSameInstanceType(t *testing.T) {
	g := NewWithT(t)
	server := &serverImpl{}

	resp, err := server.ApplyGKEHeterogeneousDeploy(context.Background(), &tidbmgmtservicev1.ApplyGKEHeterogeneousDeployRequest{
		TenantId:                1,
		ProjectId:               2,
		ClusterId:               3,
		ComponentType:           globaldedicatedv1.ComponentType_TIKV,
		CurrentInstanceType:     " n2-standard-8 ",
		AlternativeInstanceType: "n2-standard-8",
	})
	g.Expect(err).Should(HaveOccurred())
	g.Expect(resp).Should(BeNil())
}

func TestGetGKEHeterogeneousDeploy(t *testing.T) {
	g := NewWithT(t)
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()
	dedicatedCli := mock_dedicatedglobal.NewMockDedicatedGlobal(ctrl)
	server := &serverImpl{dedicatedCli: dedicatedCli}

	dedicatedCli.EXPECT().
		GetGKEHeterogeneousDeploy(gomock.Any(), gomock.Any()).
		DoAndReturn(func(
			ctx context.Context,
			req *globaldedicatedv1.GetGKEHeterogeneousDeployRequest,
			opts ...grpc.CallOption,
		) (*globaldedicatedv1.GKEHeterogeneousDeploy, error) {
			g.Expect(req.GetTenantId()).Should(Equal(uint64(1)))
			g.Expect(req.GetProjectId()).Should(Equal(uint64(2)))
			g.Expect(req.GetClusterId()).Should(Equal(uint64(3)))
			g.Expect(req.GetComponentType()).Should(Equal(globaldedicatedv1.ComponentType_TIKV))
			g.Expect(req.GetTidbGroupId()).Should(Equal(uint64(0)))
			return &globaldedicatedv1.GKEHeterogeneousDeploy{
				Enabled:                 true,
				ComponentType:           req.GetComponentType(),
				CurrentInstanceType:     "n2-standard-8",
				AlternativeInstanceType: "n2-standard-16",
				TidbGroupId:             req.GetTidbGroupId(),
			}, nil
		})

	resp, err := server.GetGKEHeterogeneousDeploy(context.Background(), &tidbmgmtservicev1.GetGKEHeterogeneousDeployRequest{
		TenantId:      1,
		ProjectId:     2,
		ClusterId:     3,
		ComponentType: globaldedicatedv1.ComponentType_TIKV,
		TidbGroupId:   0,
	})
	g.Expect(err).ShouldNot(HaveOccurred())
	g.Expect(resp.GetEnabled()).Should(BeTrue())
	g.Expect(resp.GetTidbGroupId()).Should(Equal(uint64(0)))
}

func TestGetGKEHeterogeneousDeployRejectsUnspecifiedComponentType(t *testing.T) {
	g := NewWithT(t)
	server := &serverImpl{}

	resp, err := server.GetGKEHeterogeneousDeploy(context.Background(), &tidbmgmtservicev1.GetGKEHeterogeneousDeployRequest{
		TenantId:      1,
		ProjectId:     2,
		ClusterId:     3,
		ComponentType: globaldedicatedv1.ComponentType_COMPONENT_TYPE_UNSPECIFIED,
	})
	g.Expect(err).Should(HaveOccurred())
	g.Expect(resp).Should(BeNil())
}

func TestGetGKEHeterogeneousDeployRejectsTidbWithoutTidbGroup(t *testing.T) {
	g := NewWithT(t)
	server := &serverImpl{}

	resp, err := server.GetGKEHeterogeneousDeploy(context.Background(), &tidbmgmtservicev1.GetGKEHeterogeneousDeployRequest{
		TenantId:      1,
		ProjectId:     2,
		ClusterId:     3,
		ComponentType: globaldedicatedv1.ComponentType_TIDB,
	})
	g.Expect(err).Should(HaveOccurred())
	g.Expect(resp).Should(BeNil())
}

func TestGetGKEHeterogeneousDeployRejectsNonTidbWithTidbGroup(t *testing.T) {
	g := NewWithT(t)
	server := &serverImpl{}

	resp, err := server.GetGKEHeterogeneousDeploy(context.Background(), &tidbmgmtservicev1.GetGKEHeterogeneousDeployRequest{
		TenantId:      1,
		ProjectId:     2,
		ClusterId:     3,
		ComponentType: globaldedicatedv1.ComponentType_TIKV,
		TidbGroupId:   4,
	})
	g.Expect(err).Should(HaveOccurred())
	g.Expect(resp).Should(BeNil())
}

func TestDeleteGKEHeterogeneousDeploy(t *testing.T) {
	g := NewWithT(t)
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()
	dedicatedCli := mock_dedicatedglobal.NewMockDedicatedGlobal(ctrl)
	clusterSvc := mock_dedicatedclustersvc.NewMockService(ctrl)
	server := &serverImpl{
		dedicatedCli:        dedicatedCli,
		dedicatedClusterSvc: clusterSvc,
	}

	clusterSvc.EXPECT().
		GetCluster(gomock.Any(), uint64(3), entity.ClusterViewClusterViewLocal, false, false).
		Return(&entity.Cluster{
			OrganizationId: 1,
			ProjectId:      2,
		}, nil)

	dedicatedCli.EXPECT().
		DeleteGKEHeterogeneousDeploy(gomock.Any(), gomock.Any()).
		DoAndReturn(func(
			ctx context.Context,
			req *globaldedicatedv1.DeleteGKEHeterogeneousDeployRequest,
			opts ...grpc.CallOption,
		) (*emptypb.Empty, error) {
			g.Expect(req.GetTenantId()).Should(Equal(uint64(1)))
			g.Expect(req.GetProjectId()).Should(Equal(uint64(2)))
			g.Expect(req.GetClusterId()).Should(Equal(uint64(3)))
			g.Expect(req.GetComponentType()).Should(Equal(globaldedicatedv1.ComponentType_TIKV))
			g.Expect(req.GetTidbGroupId()).Should(Equal(uint64(0)))
			return &emptypb.Empty{}, nil
		})

	resp, err := server.DeleteGKEHeterogeneousDeploy(context.Background(), &tidbmgmtservicev1.DeleteGKEHeterogeneousDeployRequest{
		TenantId:      1,
		ProjectId:     2,
		ClusterId:     3,
		ComponentType: globaldedicatedv1.ComponentType_TIKV,
		TidbGroupId:   0,
	})
	g.Expect(err).ShouldNot(HaveOccurred())
	g.Expect(resp).ShouldNot(BeNil())
}

func TestDeleteGKEHeterogeneousDeployRejectsUnspecifiedComponentType(t *testing.T) {
	g := NewWithT(t)
	server := &serverImpl{}

	resp, err := server.DeleteGKEHeterogeneousDeploy(context.Background(), &tidbmgmtservicev1.DeleteGKEHeterogeneousDeployRequest{
		TenantId:      1,
		ProjectId:     2,
		ClusterId:     3,
		ComponentType: globaldedicatedv1.ComponentType_COMPONENT_TYPE_UNSPECIFIED,
	})
	g.Expect(err).Should(HaveOccurred())
	g.Expect(resp).Should(BeNil())
}

func TestDeleteGKEHeterogeneousDeployRejectsTidbWithoutTidbGroup(t *testing.T) {
	g := NewWithT(t)
	server := &serverImpl{}

	resp, err := server.DeleteGKEHeterogeneousDeploy(context.Background(), &tidbmgmtservicev1.DeleteGKEHeterogeneousDeployRequest{
		TenantId:      1,
		ProjectId:     2,
		ClusterId:     3,
		ComponentType: globaldedicatedv1.ComponentType_TIDB,
	})
	g.Expect(err).Should(HaveOccurred())
	g.Expect(resp).Should(BeNil())
}

func TestDeleteGKEHeterogeneousDeployRejectsNonTidbWithTidbGroup(t *testing.T) {
	g := NewWithT(t)
	server := &serverImpl{}

	resp, err := server.DeleteGKEHeterogeneousDeploy(context.Background(), &tidbmgmtservicev1.DeleteGKEHeterogeneousDeployRequest{
		TenantId:      1,
		ProjectId:     2,
		ClusterId:     3,
		ComponentType: globaldedicatedv1.ComponentType_TIKV,
		TidbGroupId:   4,
	})
	g.Expect(err).Should(HaveOccurred())
	g.Expect(resp).Should(BeNil())
}
