package region

import (
	"context"
	"testing"

	. "github.com/onsi/gomega"
	"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"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/adapter/regionserver/mock_regionserver"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/utilities/grpc/metadata"
)

func TestRegisterRegion(t *testing.T) {
	g := NewWithT(t)
	// Init mock dependencies.
	ctrl := gomock.NewController(t)
	regionalCli := mock_regionserver.NewMockClient(ctrl)

	service := newService(regionalCli)

	type testCase struct {
		name      string
		inputsFn  func() (context.Context, *globaldedicatedv1.RegisterRegionRequest)
		outputsFn func(*entity.Region, error)
	}

	cases := []testCase{
		{
			name: "Register should success",
			inputsFn: func() (context.Context, *globaldedicatedv1.RegisterRegionRequest) {
				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.UserEmailHeaderKey:        "foo@bar.com",
				}))
				req := &globaldedicatedv1.RegisterRegionRequest{
					Region: &globalcommonv1.Region{
						Id:                globalcommonv1.Region_AWS_US_WEST_1,
						CloudProviderId:   globalcommonv1.CloudProvider_AWS,
						IsPublic:          true,
						State:             globalcommonv1.Region_ACTIVE,
						DisplayName:       "aws-us-west-1",
						DefaultBackupTime: "00:00",
					},
				}
				return ctx, req
			},
			outputsFn: func(region *entity.Region, err error) {
				g.Expect(err).ShouldNot(HaveOccurred())

				g.Expect(region).ShouldNot(BeNil())
				g.Expect(region.GetId()).Should(Equal(globalcommonv1.Region_AWS_US_WEST_1))
				g.Expect(region.GetCloudProviderId()).Should(Equal(globalcommonv1.CloudProvider_AWS))
				g.Expect(region.GetIsPublic()).Should(BeTrue())
				g.Expect(region.GetState()).Should(Equal(globalcommonv1.Region_ACTIVE))
				g.Expect(region.GetDisplayName()).Should(Equal("aws-us-west-1"))
				g.Expect(region.GetDefaultBackupTime()).Should(Equal("00:00"))
				g.Expect(region.GetLogicalName()).Should(Equal(globalcommonv1.Region_AWS_US_WEST_1.String()))
			},
		},
		{
			name: "Register duplicate region should fail",
			inputsFn: func() (context.Context, *globaldedicatedv1.RegisterRegionRequest) {
				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.UserEmailHeaderKey:        "foo@bar.com",
				}))
				req := &globaldedicatedv1.RegisterRegionRequest{
					Region: &globalcommonv1.Region{
						Id:                globalcommonv1.Region_AWS_US_WEST_2,
						CloudProviderId:   globalcommonv1.CloudProvider_AWS,
						IsPublic:          true,
						State:             globalcommonv1.Region_ACTIVE,
						DisplayName:       "Oregon (us-west-2)",
						DefaultBackupTime: "00:00",
					},
				}
				return ctx, req
			},
			outputsFn: func(region *entity.Region, err error) {
				g.Expect(err).Should(HaveOccurred())
				g.Expect(region).Should(BeNil())
				g.Expect(err.Error()).Should(ContainSubstring("region logical us-west-2 already exists"))
			},
		},
		{
			name: "Register new private region should success",
			inputsFn: func() (context.Context, *globaldedicatedv1.RegisterRegionRequest) {
				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.UserEmailHeaderKey:        "foo@bar.com",
				}))
				req := &globaldedicatedv1.RegisterRegionRequest{
					Region: &globalcommonv1.Region{
						Id:                globalcommonv1.Region_AWS_US_WEST_2,
						CloudProviderId:   globalcommonv1.CloudProvider_AWS,
						IsPublic:          false,
						State:             globalcommonv1.Region_ACTIVE,
						DisplayName:       "Oregon (us-west-2)",
						DefaultBackupTime: "00:00",
						OpenedTenantIds:   []uint64{333},
						LogicalName:       "us-west-2-333",
					},
				}
				return ctx, req
			},
			outputsFn: func(region *entity.Region, err error) {
				g.Expect(err).ShouldNot(HaveOccurred())
				g.Expect(region).ShouldNot(BeNil())
				g.Expect(region.GetId()).Should(Equal(globalcommonv1.Region_AWS_US_WEST_2))
				g.Expect(region.GetCloudProviderId()).Should(Equal(globalcommonv1.CloudProvider_AWS))
				g.Expect(region.GetIsPublic()).Should(BeFalse())
				g.Expect(region.GetState()).Should(Equal(globalcommonv1.Region_ACTIVE))
				g.Expect(region.GetDisplayName()).Should(Equal("Oregon (us-west-2)"))
				g.Expect(region.GetDefaultBackupTime()).Should(Equal("00:00"))
				g.Expect(region.GetLogicalName()).Should(Equal("us-west-2-333"))
			},
		},
		{
			name: "Register duplicate region open to tenant should fail",
			inputsFn: func() (context.Context, *globaldedicatedv1.RegisterRegionRequest) {
				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.UserEmailHeaderKey:        "foo@bar.com",
				}))
				req := &globaldedicatedv1.RegisterRegionRequest{
					Region: &globalcommonv1.Region{
						Id:                globalcommonv1.Region_AWS_US_WEST_2,
						CloudProviderId:   globalcommonv1.CloudProvider_AWS,
						IsPublic:          false,
						State:             globalcommonv1.Region_ACTIVE,
						DisplayName:       "Oregon (us-west-2)",
						DefaultBackupTime: "00:00",
						OpenedTenantIds:   []uint64{333, 444},
						LogicalName:       "us-west-2-333-2",
					},
				}
				return ctx, req
			},
			outputsFn: func(region *entity.Region, err error) {
				g.Expect(err).Should(HaveOccurred())
				g.Expect(region).Should(BeNil())
				g.Expect(err.Error()).Should(ContainSubstring("organization 333 is aleady in region us-west-2-333"))
			},
		},
		{
			name: "Register public region with logical name should fail",
			inputsFn: func() (context.Context, *globaldedicatedv1.RegisterRegionRequest) {
				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.UserEmailHeaderKey:        "foo@bar.com",
				}))
				req := &globaldedicatedv1.RegisterRegionRequest{
					Region: &globalcommonv1.Region{
						Id:                globalcommonv1.Region_AWS_US_WEST_2,
						CloudProviderId:   globalcommonv1.CloudProvider_AWS,
						IsPublic:          true,
						State:             globalcommonv1.Region_ACTIVE,
						DisplayName:       "Oregon (us-west-2)",
						DefaultBackupTime: "00:00",
						OpenedTenantIds:   []uint64{555},
						LogicalName:       "us-west-2-333-3",
					},
				}
				return ctx, req
			},
			outputsFn: func(region *entity.Region, err error) {
				g.Expect(err).Should(HaveOccurred())
				g.Expect(region).Should(BeNil())
				g.Expect(err.Error()).
					Should(ContainSubstring("private logical region us-west-2-333-3 must be not public"))
			},
		},
		{
			name: "Update private region",
			inputsFn: func() (context.Context, *globaldedicatedv1.RegisterRegionRequest) {
				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.UserEmailHeaderKey:        "foo@bar.com",
				}))
				req := &globaldedicatedv1.RegisterRegionRequest{
					Region: &globalcommonv1.Region{
						Id:                globalcommonv1.Region_AWS_US_WEST_2,
						CloudProviderId:   globalcommonv1.CloudProvider_AWS,
						IsPublic:          false,
						State:             globalcommonv1.Region_ACTIVE,
						DisplayName:       "Oregon (us-west-2)",
						DefaultBackupTime: "00:00",
						OpenedTenantIds:   []uint64{},
						LogicalName:       "us-west-2-upeate",
					},
				}
				return ctx, req
			},
			outputsFn: func(region *entity.Region, err error) {
				g.Expect(err).ShouldNot(HaveOccurred())
				g.Expect(region).ShouldNot(BeNil())
				g.Expect(region.GetId()).Should(Equal(globalcommonv1.Region_AWS_US_WEST_2))
				g.Expect(region.GetCloudProviderId()).Should(Equal(globalcommonv1.CloudProvider_AWS))
				g.Expect(region.GetIsPublic()).Should(BeFalse())
				g.Expect(region.GetState()).Should(Equal(globalcommonv1.Region_ACTIVE))
				g.Expect(region.GetDisplayName()).Should(Equal("Oregon (us-west-2)"))
				g.Expect(region.GetDefaultBackupTime()).Should(Equal("00:00"))
				g.Expect(region.GetLogicalName()).Should(Equal("us-west-2-upeate"))

				// update private region open to tenant
				region.OpenedTenantIds = []uint64{666}
				region, err = service.UpdateRegion(t.Context(), &globaldedicatedv1.UpdateRegionRequest{Region: region})
				g.Expect(err).ShouldNot(HaveOccurred())
				g.Expect(region).ShouldNot(BeNil())
				g.Expect(region.GetOpenedTenantIds()).Should(Equal([]uint64{666}))

				// update private region to public failed
				region.IsPublic = true
				_, err = service.UpdateRegion(t.Context(), &globaldedicatedv1.UpdateRegionRequest{Region: region})
				g.Expect(err).Should(HaveOccurred())
				g.Expect(err.Error()).
					Should(ContainSubstring("private logical region us-west-2-upeate must be not public"))

				// update exist tenant
				region.OpenedTenantIds = []uint64{333}
				_, err = service.UpdateRegion(t.Context(), &globaldedicatedv1.UpdateRegionRequest{Region: region})
				g.Expect(err).Should(HaveOccurred())
				g.Expect(err.Error()).Should(ContainSubstring("organization 333 is aleady in region us-west-2-333"))
			},
		},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			ctx, req := tc.inputsFn()
			cluster, err := service.RegisterRegion(ctx, req)
			tc.outputsFn(cluster, err)
		})
	}
}

func TestListRegions(t *testing.T) {
	g := NewWithT(t)
	// Init mock dependencies.
	ctrl := gomock.NewController(t)
	regionalCli := mock_regionserver.NewMockClient(ctrl)

	service := newService(regionalCli)

	type testCase struct {
		name      string
		inputsFn  func() (context.Context, *globaldedicatedv1.ListRegionsRequest)
		outputsFn func([]*entity.Region, error)
	}

	cases := []testCase{
		{
			name: "List should success",
			inputsFn: func() (context.Context, *globaldedicatedv1.ListRegionsRequest) {
				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.UserEmailHeaderKey:        "foo@bar.com",
				}))
				req := &globaldedicatedv1.ListRegionsRequest{
					CloudProviderIds: []globalcommonv1.CloudProvider_Name{globalcommonv1.CloudProvider_AWS},
					OrganizationId:   1,
				}
				return ctx, req
			},
			outputsFn: func(regions []*entity.Region, err error) {
				g.Expect(err).ShouldNot(HaveOccurred())
				g.Expect(regions).ShouldNot(BeNil())
				for _, r := range regions {
					g.Expect(r.GetCloudProviderId()).Should(Equal(globalcommonv1.CloudProvider_AWS))
					g.Expect(r.GetLogicalName()).ShouldNot(BeEmpty())
					g.Expect(r.GetDisplayName()).ShouldNot(BeEmpty())
					g.Expect(r.GetLogicalName()).Should(Equal(r.GetId().String()))
				}
			},
		},
		{
			name: "List private should success",
			inputsFn: func() (context.Context, *globaldedicatedv1.ListRegionsRequest) {
				ctx := grpcmetadata.NewIncomingContext(t.Context(), grpcmetadata.New(map[string]string{
					metadata.UserIDHeaderKey:           "1",
					metadata.OrgIDHeaderKey:            "4399",
					metadata.DefaultProjectIDHeaderKey: "1",
					metadata.ProjectListHeaderKey:      "1,2,3",
					metadata.UserEmailHeaderKey:        "foo@bar.com",
				}))
				req := &globaldedicatedv1.ListRegionsRequest{
					CloudProviderIds: []globalcommonv1.CloudProvider_Name{globalcommonv1.CloudProvider_AWS},
					OrganizationId:   4399,
				}
				return ctx, req
			},
			outputsFn: func(regions []*entity.Region, err error) {
				g.Expect(err).ShouldNot(HaveOccurred())
				g.Expect(regions).ShouldNot(BeNil())
				g.Expect(len(regions)).Should(Equal(1))
				for _, r := range regions {
					g.Expect(r.GetCloudProviderId()).Should(Equal(globalcommonv1.CloudProvider_AWS))
					g.Expect(r.GetLogicalName()).ShouldNot(BeEmpty())
					g.Expect(r.GetDisplayName()).ShouldNot(BeEmpty())
					g.Expect(r.GetLogicalName()).ShouldNot(Equal(r.GetId().String()))
				}
			},
		},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			ctx, req := tc.inputsFn()
			cluster, err := service.ListRegions(ctx, req)
			tc.outputsFn(cluster, err)
		})
	}
}
