package br

import (
	"context"
	"testing"

	"github.com/stretchr/testify/require"
	"go.uber.org/mock/gomock"

	globalcommonv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/common/v1"
	globalngv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/nextgen/v1"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/entity"
	brrepo "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/br"
	mockbrrepo "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/br/mock_br"
	mockclusterrepo "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/cluster/mock_cluster"
	mockregionrepo "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/region/mock_region"
	resourcepoolrepo "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/resourcepool"
	mockresourcepoolrepo "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/resourcepool/mock_resourcepool"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/infra/repoimpl/schema"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	grpcmetadata "github.com/tidbcloud/tidb-management-service/utilities/grpc/metadata"
	"github.com/tidbcloud/tidb-management-service/utilities/pagination"
	mockworkflow "github.com/tidbcloud/tidb-management-service/utilities/workflow/mock_workflow"
)

func TestCreateFinalBackup(t *testing.T) {
	t.Run("creates backup with final purpose", func(t *testing.T) {
		ctrl := gomock.NewController(t)
		backupRepo := mockbrrepo.NewMockBackupRepo(ctrl)
		clusterRepo := mockclusterrepo.NewMockRepo(ctrl)
		regionRepo := mockregionrepo.NewMockRepo(ctrl)
		resourcePoolRepo := mockresourcepoolrepo.NewMockRepo(ctrl)
		workflow := mockworkflow.NewMockWorkflow(ctrl)
		service := &serviceImpl{
			backupRepo:               backupRepo,
			clusterRepo:              clusterRepo,
			regionRepo:               regionRepo,
			resourcePoolRepo:         resourcePoolRepo,
			workflow:                 workflow,
			transaction:              newMockTransactioner(),
			backupEncryptionResolver: NewResolver(nil),
		}
		cluster := finalBackupCluster()
		clusterRepo.EXPECT().Get(gomock.Any(), uint64(42), false).Return(cluster, nil)
		regionRepo.EXPECT().Get(gomock.Any(), uint64(1)).Return(&entity.Region{RegionID: 1}, nil)
		resourcePoolRepo.EXPECT().Load(gomock.Any(), uint64(7), false).Return(activePool(7), nil)
		resourcePoolRepo.EXPECT().LoadLifecycleForUpdate(gomock.Any(), uint64(7), false).Return(
			&resourcepoolrepo.Lifecycle{State: globalngv1.ResourcePool_ACTIVE}, nil,
		)
		backupRepo.EXPECT().UnscopedQuery(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(
			func(_ context.Context, filter *brrepo.QueryBackupsFilter, page *pagination.Page) ([]*entity.Backup, bizerr.BizError) {
				require.Equal(t, []uint64{42}, filter.ClusterIds)
				require.Equal(t, []schema.BackupPurpose{schema.BackupPurposeFinal}, filter.Purposes)
				require.Equal(t, int64(1), page.PageSize)
				return nil, nil
			},
		)
		backupRepo.EXPECT().Create(gomock.Any(), gomock.Any()).DoAndReturn(
			func(_ context.Context, backup *entity.Backup) bizerr.BizError {
				require.NotNil(t, backup.Purpose)
				require.Equal(t, schema.BackupPurposeFinal, *backup.Purpose)
				require.Equal(t, "Final backup", backup.DisplayName)
				backup.ID = 99
				return nil
			},
		)
		workflow.EXPECT().RunDagWithBizId(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)

		response, err := service.CreateFinalBackup(finalBackupContext(), &globalngv1.CreateFinalBackupRequest{ClusterId: 42})
		require.NoError(t, err)
		require.Equal(t, "99", response.GetBackupId())
	})

	t.Run("reuses existing final backup", func(t *testing.T) {
		ctrl := gomock.NewController(t)
		backupRepo := mockbrrepo.NewMockBackupRepo(ctrl)
		clusterRepo := mockclusterrepo.NewMockRepo(ctrl)
		regionRepo := mockregionrepo.NewMockRepo(ctrl)
		resourcePoolRepo := mockresourcepoolrepo.NewMockRepo(ctrl)
		service := &serviceImpl{
			backupRepo:               backupRepo,
			clusterRepo:              clusterRepo,
			regionRepo:               regionRepo,
			resourcePoolRepo:         resourcePoolRepo,
			transaction:              newMockTransactioner(),
			backupEncryptionResolver: NewResolver(nil),
		}
		clusterRepo.EXPECT().Get(gomock.Any(), uint64(42), false).Return(finalBackupCluster(), nil)
		regionRepo.EXPECT().Get(gomock.Any(), uint64(1)).Return(&entity.Region{RegionID: 1}, nil)
		resourcePoolRepo.EXPECT().Load(gomock.Any(), uint64(7), false).Return(activePool(7), nil)
		resourcePoolRepo.EXPECT().LoadLifecycleForUpdate(gomock.Any(), uint64(7), false).Return(
			&resourcepoolrepo.Lifecycle{State: globalngv1.ResourcePool_ACTIVE}, nil,
		)
		backupRepo.EXPECT().UnscopedQuery(gomock.Any(), gomock.Any(), gomock.Any()).Return(
			[]*entity.Backup{{Backup: &schema.Backup{ID: 88}}}, nil,
		)

		response, err := service.CreateFinalBackup(finalBackupContext(), &globalngv1.CreateFinalBackupRequest{ClusterId: 42})
		require.NoError(t, err)
		require.Equal(t, "88", response.GetBackupId())
	})
}

func finalBackupCluster() *entity.Cluster {
	return &entity.Cluster{
		Cluster: &globalngv1.Cluster{
			ClusterId:       42,
			OrganizationId:  8,
			SharedPoolId:    "7",
			RegionId:        globalcommonv1.Region_AWS_US_EAST_1,
			CloudProviderId: globalcommonv1.CloudProvider_AWS,
			ServicePlan:     globalcommonv1.ServicePlan_Premium,
		},
		RegionId: 1,
	}
}

func activePool(id uint64) *entity.ResourcePool {
	return &entity.ResourcePool{ResourcePool: &globalngv1.ResourcePool{
		Id:             id,
		LifecycleState: globalngv1.ResourcePool_ACTIVE,
	}}
}

func finalBackupContext() context.Context {
	return grpcmetadata.AppendToOutgoingMeta(context.Background(), &grpcmetadata.RequestMeta{OrgID: 8})
}
