package cluster

import (
	"testing"
	"time"

	"github.com/samber/lo"
	"github.com/smartystreets/goconvey/convey"
	"go.uber.org/mock/gomock"

	globalcommonv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/common/v1"
	globalserverlessv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/serverless/v1"
	regionalserverlessv1 "github.com/tidbcloud/tidb-management-service/api/spec/regional/serverless/v1"
	"github.com/tidbcloud/tidb-management-service/serverless-global-service/domain/adapter"
	"github.com/tidbcloud/tidb-management-service/serverless-global-service/domain/adapter/mock_adapter"
	"github.com/tidbcloud/tidb-management-service/serverless-global-service/domain/repository/mock_repository"
	"github.com/tidbcloud/tidb-management-service/serverless-global-service/domain/service/cvt"
	"github.com/tidbcloud/tidb-management-service/serverless-global-service/domain/testdata"
	"github.com/tidbcloud/tidb-management-service/serverless-global-service/pkg/config"
	"github.com/tidbcloud/tidb-management-service/utilities/clusterevent/mock_clusterevent"
	"github.com/tidbcloud/tidb-management-service/utilities/featuregate"
	"github.com/tidbcloud/tidb-management-service/utilities/workflow/mock_workflow"
)

func TestUpdateCluster(t *testing.T) {
	convey.Convey("TestUpdateCluster", t, func() {
		ctrl := gomock.NewController(t)
		defer ctrl.Finish()

		clusterRepo := mock_repository.NewMockClusterRepo(ctrl)
		cloudProviderRepo := mock_repository.NewMockCloudProviderRepo(ctrl)
		regionRepo := mock_repository.NewMockRegionRepo(ctrl)

		encryptSvc := mock_adapter.NewMockEncryptorService(ctrl)
		serverlessSvc := mock_adapter.NewMockServerlessService(ctrl)
		serverlessSvc.EXPECT().
			GetSpendLimit(gomock.Any(), gomock.Any(), gomock.Any()).
			Return(&globalserverlessv1.SpendLimit{Monthly: 1}, nil)
		regionalSvc := mock_adapter.NewMockRegionalService(ctrl)
		sys9Lifecycle := mock_adapter.NewMockSys9Lifecycle(ctrl)
		rbSvc := mock_adapter.NewMockBRService(ctrl)
		billingSvc := mock_adapter.NewMockBillingService(ctrl)
		committer := mock_clusterevent.NewMockWALCommitter(ctrl)
		featGateSvc := featuregate.NewFeatureGate(config.Global().ServerConfig.FeatureGates, nil)

		trans := NewMockMockTransaction(ctrl)
		trans.EXPECT().Transaction(gomock.Any(), gomock.Any()).Return(nil).AnyTimes()

		flow := mock_workflow.NewMockWorkflow(ctrl)
		flow.EXPECT().RegisterDag(gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
		svc, err := NewClusterService(
			flow,
			cloudProviderRepo,
			regionRepo,
			clusterRepo,
			encryptSvc,
			regionalSvc,
			sys9Lifecycle,
			serverlessSvc,
			rbSvc,
			billingSvc,
			nil,
			nil,
			nil,
			trans,
			committer,
			featGateSvc,
		)
		convey.So(err, convey.ShouldBeNil)

		en := testdata.GetCluster()
		en.CreationFinishedAt = lo.ToPtr(time.Now())
		regionalCluster := testdata.GetRegionalCluster()

		req := &globalserverlessv1.ServerlessServiceUpdateRequest{
			Cluster: &globalserverlessv1.ServerlessServiceCluster{
				ClusterId: en.ClusterId,
				Cluster: &regionalserverlessv1.ServerlessRegionalServiceClusterInfo{
					Endpoints: &regionalserverlessv1.Endpoints{
						Public: &regionalserverlessv1.PublicEndpoint{
							Disabled: true,
						},
						Private: &regionalserverlessv1.PrivateEndpoint{
							AuthorizedNetwork: []*regionalserverlessv1.PrivateEndpoint_AuthorizedNetwork{
								{
									VpcEndpointId: "vpce-1234567890abcdef0",
									DisplayName:   "private-network-1",
								},
							},
						},
					},
				},
				SpendLimit: &globalserverlessv1.SpendLimit{
					Monthly: 1,
				},
				BackupPolicy: &globalserverlessv1.AutomatedBackupPolicy{
					BackupTime: "6:00",
				},
			},
		}

		plan := &adapter.ActionCheckResp{
			Pass: true,
		}
		spendLimit := &globalserverlessv1.SpendLimit{
			Monthly: req.GetCluster().GetSpendLimit().GetMonthly(),
		}

		convey.Convey("Update", func() {
			convey.Convey("pg starter rename should skip regional list", func() {
				pgCluster := testdata.GetCluster()
				pgCluster.CreationFinishedAt = lo.ToPtr(time.Now())
				pgCluster.ServicePlan = globalcommonv1.ServicePlan_Starter
				pgCluster.EngineType = globalcommonv1.Region_POSTGRESQL_COMPATIBLE.String()

				clusterRepo.EXPECT().Get(gomock.Any(), pgCluster.ClusterId, false).Return(pgCluster, nil)
				rbSvc.EXPECT().
					GetAutoBackupPolicy(gomock.Any(), pgCluster.ClusterId, pgCluster.TenantId, pgCluster.ProjectId).
					Return(nil, nil)

				renameReq := &globalserverlessv1.ServerlessServiceUpdateRequest{
					Cluster: &globalserverlessv1.ServerlessServiceCluster{
						ClusterId: pgCluster.ClusterId,
						Cluster:   &regionalserverlessv1.ServerlessRegionalServiceClusterInfo{},
					},
				}

				result, err := svc.Update(testdata.Ctx, renameReq)
				convey.So(err, convey.ShouldBeNil)
				convey.So(result, convey.ShouldNotBeNil)
			})

			convey.Convey("update cluster", func() {
				regionalSvc.EXPECT().
					ListCluster(gomock.Any(), en.Region, gomock.Any()).
					Return([]*regionalserverlessv1.ServerlessRegionalServiceClusterInfo{regionalCluster}, nil)
				clusterRepo.EXPECT().Get(gomock.Any(), en.ClusterId, false).Return(en, nil).AnyTimes()
				billingSvc.EXPECT().
					CanCreateScalableCluster(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), en.TenantId).
					Return(plan, nil)
				serverlessSvc.EXPECT().
					GetSpendLimit(gomock.Any(), en.ClusterId, en.TenantId).
					Return(spendLimit, nil)
				serverlessSvc.EXPECT().
					SetSpendLimit(gomock.Any(), en.ClusterId, en.TenantId, req.GetCluster().GetSpendLimit().GetMonthly(), en.Region.Id.String(), false, true).
					Return(nil)
				rbSvc.EXPECT().
					GetAutoBackupPolicy(gomock.Any(), en.ClusterId, en.TenantId, en.ProjectId).
					Return(req.Cluster.BackupPolicy, nil)
				result, err := svc.Update(testdata.Ctx, req)
				convey.So(err, convey.ShouldBeNil)
				convey.So(result.Cluster.ClusterId, convey.ShouldEqual, en.ClusterId)
				convey.So(
					result.Cluster.UserStatus,
					convey.ShouldEqual,
					cvt.ClusterStatusRegionalToGlobal(regionalCluster.State),
				)
				convey.So(result.Cluster.SpendLimit.Monthly, convey.ShouldEqual, spendLimit.Monthly)
				convey.So(result.Cluster.Cluster.Endpoints.Private.AuthorizedNetwork, convey.ShouldHaveLength, 1)
				convey.So(
					result.Cluster.Cluster.Endpoints.Private.AuthorizedNetwork[0].VpcEndpointId,
					convey.ShouldEqual,
					"vpce-1234567890abcdef0",
				)
				convey.So(
					result.Cluster.Cluster.Endpoints.Private.AuthorizedNetwork[0].DisplayName,
					convey.ShouldEqual,
					"private-network-1",
				)
			})
		})
	})
}
