package backupv2_test

import (
	"context"
	"fmt"
	"strconv"
	"strings"
	"testing"

	"github.com/AlekSi/pointer"
	workflowEntity "github.com/shiningrush/fastflow/pkg/entity"
	"github.com/stretchr/testify/mock"
	"github.com/stretchr/testify/require"
	"github.com/stretchr/testify/suite"
	feature_gate_mock "github.com/tidbcloud/dataflow-service/app/internal/feature_gate/mock"
	backupv2_impl "github.com/tidbcloud/dataflow-service/app/models/backup/impl"
	backup_model_mock "github.com/tidbcloud/dataflow-service/app/models/backup/mock"
	bucket_model_mock "github.com/tidbcloud/dataflow-service/app/models/bucket/mock"
	"github.com/tidbcloud/dataflow-service/app/models/domain"
	"github.com/tidbcloud/dataflow-service/app/repo"
	crrfRepoMock "github.com/tidbcloud/dataflow-service/app/repo/crrf/mock"
	"github.com/tidbcloud/dataflow-service/app/repo/entity"
	repo_mock "github.com/tidbcloud/dataflow-service/app/repo/mock"
	"github.com/tidbcloud/dataflow-service/app/workflow"
	workflow_mock "github.com/tidbcloud/dataflow-service/app/workflow/mock"
	service_config "github.com/tidbcloud/dataflow-service/internal/config"
	grpcli_mock "github.com/tidbcloud/dataflow-service/pkg/grpc/grpcli/mock"
	"github.com/tidbcloud/pkgv2/db"
	"gorm.io/gorm"
)

type gcpDualRegionTestSuite struct {
	suite.Suite
	db *gorm.DB

	mockBucketV2Model          *bucket_model_mock.BucketModelV2
	mockBucketReplicationModel *bucket_model_mock.BucketReplicationModel
	mockPitBackupSwitcher      *backup_model_mock.PitBackupSwitcher
	mockBackupSettingRepo      *repo_mock.BackupSettingRepo
	mockLogBackupRepo          *repo_mock.LogBackupRepo
	mockBackupScheduleRepo     *repo_mock.BackupScheduleRepo
	mockBucketRepo             *repo_mock.BucketRepo
	mockCoreGrpcClient         *grpcli_mock.CoreGrpcClient
	mockMgmtGrpcClient         *grpcli_mock.MgmtSvcGrpcClient
	mockAccountGrpcClient      *grpcli_mock.AccountSvcGrpcClient
	mockWorkflow               *workflow_mock.Workflow
	mockFeatureGate            *feature_gate_mock.FeatureGate
	mockTiDBClusterInfraClient *grpcli_mock.TiDBClusterInfraClient
	mockRegionGrpcClient       *grpcli_mock.RegionGrpcClient
	mockCRRFSettingRepo        *crrfRepoMock.CRRFSettingRepo
	mockAWSBucketManagerModel  *bucket_model_mock.AWSBucketManagerModel
	mockAWSBucketConfigRepo    *repo_mock.AWSBucketConfigurationRepo
}

func TestGCPDualRegionTestSuite(t *testing.T) {
	suite.Run(t, new(gcpDualRegionTestSuite))
}

func (s *gcpDualRegionTestSuite) SetupSuite() {
	s.mockBucketV2Model = bucket_model_mock.NewBucketModelV2(s.T())
	s.mockBucketReplicationModel = bucket_model_mock.NewBucketReplicationModel(s.T())
	s.mockPitBackupSwitcher = backup_model_mock.NewPitBackupSwitcher(s.T())
	s.mockBackupSettingRepo = repo_mock.NewBackupSettingRepo(s.T())
	s.mockLogBackupRepo = repo_mock.NewLogBackupRepo(s.T())
	s.mockBackupScheduleRepo = repo_mock.NewBackupScheduleRepo(s.T())
	s.mockBucketRepo = repo_mock.NewBucketRepo(s.T())
	s.mockCoreGrpcClient = grpcli_mock.NewCoreGrpcClient(s.T())
	s.mockMgmtGrpcClient = grpcli_mock.NewMgmtSvcGrpcClient(s.T())
	s.mockAccountGrpcClient = grpcli_mock.NewAccountSvcGrpcClient(s.T())
	s.mockWorkflow = workflow_mock.NewWorkflow(s.T())
	s.mockFeatureGate = feature_gate_mock.NewFeatureGate(s.T())
	s.mockTiDBClusterInfraClient = grpcli_mock.NewTiDBClusterInfraClient(s.T())
	s.mockRegionGrpcClient = grpcli_mock.NewRegionGrpcClient(s.T())
	s.mockCRRFSettingRepo = crrfRepoMock.NewCRRFSettingRepo(s.T())
	s.mockAWSBucketManagerModel = bucket_model_mock.NewAWSBucketManagerModel(s.T())
	s.mockAWSBucketConfigRepo = repo_mock.NewAWSBucketConfigurationRepo(s.T())
}

//nolint:funlen
func (s *gcpDualRegionTestSuite) TestEnableDualRegionSetting() {
	testDB, err := repo.GetFakeDB()
	require.NoError(s.T(), err)
	s.db = testDB
	trans := db.NewProvider(s.db)

	backupSettingModel := backupv2_impl.NewBackupSettingModel(
		trans, s.mockBackupSettingRepo, s.mockLogBackupRepo, s.mockCoreGrpcClient, s.mockMgmtGrpcClient, s.mockWorkflow,
		s.mockBucketV2Model, s.mockBucketReplicationModel, s.mockBucketRepo, s.mockPitBackupSwitcher,
		s.mockFeatureGate, s.mockBackupScheduleRepo, s.mockAccountGrpcClient, s.mockTiDBClusterInfraClient, s.mockRegionGrpcClient,
		s.mockCRRFSettingRepo, s.mockAWSBucketManagerModel, s.mockAWSBucketConfigRepo)

	mockedClusterID := entity.ID(123)
	mockedBucket := entity.BackupSettingBucket{
		Regions:  []string{"us-east1"},
		Master:   true,
		BucketId: 123,
	}
	mockedBackupSetting := &entity.BackupSetting{
		ClusterID:         mockedClusterID,
		InfraProviderType: "gcp",
		PiTR: &entity.PiTRSetting{
			Enable: false,
		},
		Bucket: &entity.BucketSetting{
			DualRegionEnable: false,
			Buckets:          []entity.BackupSettingBucket{mockedBucket},
			Prefix:           pointer.ToString(strconv.FormatUint(mockedClusterID.Uint64(), 10)),
		},
		Status: entity.BackupSettingStatusNormal,
	}
	mockedBackupSettingID := entity.ID(456)
	mockedBackupSetting.ID = mockedBackupSettingID

	s.mockBackupSettingRepo.On("Get", mock.Anything, mock.Anything).Return(mockedBackupSetting, nil)
	ctx := context.Background()

	// no region
	req := &domain.RegionSettingModificationReqV2{
		ClusterID: pointer.ToUint64(mockedClusterID.Uint64()),
		Enable:    true,
		Regions:   []string{},
	}
	_, err = backupSettingModel.ModifyRegionSetting(ctx, req)
	s.Error(err)

	// too many regions
	req.Regions = []string{"us-east1", "us-east2", "us-west1"}
	_, err = backupSettingModel.ModifyRegionSetting(ctx, req)
	s.Error(err)

	// master region mismatch
	req.Regions = []string{"us-east2", "us-west1"}
	_, err = backupSettingModel.ModifyRegionSetting(ctx, req)
	s.Error(err)

	// dual region already enabled
	req.Regions = []string{"us-east1", "us-east5"}
	mockedBackupSetting.Bucket.DualRegionEnable = true
	s.mockBackupSettingRepo.On("Get", mock.Anything, mock.Anything).Return(mockedBackupSetting, nil)
	_, err = backupSettingModel.ModifyRegionSetting(ctx, req)
	s.Error(err)

	// no master bucket
	mockedBackupSetting.Bucket.DualRegionEnable = false
	mockedBackupSetting.Bucket.Buckets = nil
	s.mockBackupSettingRepo.On("Get", mock.Anything, mock.Anything).Return(mockedBackupSetting, nil)
	_, err = backupSettingModel.ModifyRegionSetting(ctx, req)
	s.Error(err)

	// dual region not match in config
	mockedBackupSetting.Bucket.Buckets = []entity.BackupSettingBucket{mockedBucket}
	service_config.Global().ServerConfig.BucketOptions.GCPBucketGeneralDualRegions = map[string][]string{
		"US": {"us-east1", "us-east2"},
	}
	_, err = backupSettingModel.ModifyRegionSetting(ctx, req)
	s.Error(err)

	// normal case
	req.Regions = []string{"us-east1", "us-east2"}
	m1 := s.mockBackupSettingRepo.On("Get", mock.Anything, mock.Anything).Return(mockedBackupSetting, nil)
	expectedUpdatedBackupSetting := &entity.BackupSetting{
		ClusterID:         mockedClusterID,
		InfraProviderType: "gcp",
		PiTR:              mockedBackupSetting.PiTR,
		Bucket: &entity.BucketSetting{
			DualRegionEnable: true,
			Buckets: []entity.BackupSettingBucket{
				{
					Regions: req.Regions,
					Master:  true,
				},
			},
			Prefix: pointer.ToString(strconv.FormatUint(mockedClusterID.Uint64(), 10)),
		},
		Status: entity.BackupSettingStatusEditingRegion,
		Action: entity.BackupSettingAction{
			CurrentActions: []string{string(entity.BackupSettingActionModifyRegion)},
		},
	}
	expectedUpdatedBackupSetting.ID = mockedBackupSettingID
	m2 := s.mockBackupSettingRepo.On("Save", mock.Anything, expectedUpdatedBackupSetting).Return(nil)
	mockedNodeRole := "mocked_role_arn"
	m3 := s.mockTiDBClusterInfraClient.On(
		"GetNetworkNodeRole", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(mockedNodeRole, nil)
	mockedDagName := workflow.DagToggleDualRegionBackupGCP
	mockedDagInsVars := map[string]string{}
	mockedDagInsVars[workflow.KeyBackupSettingId] = fmt.Sprintf("%d", expectedUpdatedBackupSetting.ID)
	mockedDagInsVars[workflow.KeyRegion] = strings.Join(
		expectedUpdatedBackupSetting.Bucket.Buckets[0].Regions, workflow.SepBucketRegions)
	mockedDagInsVars[workflow.KeyNetworkNodeRole] = mockedNodeRole
	mockedDagIns := &workflowEntity.DagInstance{}
	m4 := s.mockWorkflow.On("RunDagWithTags", mockedDagName, mockedDagInsVars, mock.Anything).Return(mockedDagIns, nil)
	_, err = backupSettingModel.ModifyRegionSetting(ctx, req)
	s.NoError(err)

	m1.Unset()
	m2.Unset()
	m3.Unset()
	m4.Unset()
}

//nolint:funlen
func (s *gcpDualRegionTestSuite) TestDisableDualRegionSetting() {
	testDB, err := repo.GetFakeDB()
	require.NoError(s.T(), err)
	s.db = testDB
	trans := db.NewProvider(s.db)

	backupSettingModel := backupv2_impl.NewBackupSettingModel(
		trans, s.mockBackupSettingRepo, s.mockLogBackupRepo, s.mockCoreGrpcClient, s.mockMgmtGrpcClient, s.mockWorkflow,
		s.mockBucketV2Model, s.mockBucketReplicationModel, s.mockBucketRepo, s.mockPitBackupSwitcher,
		s.mockFeatureGate, s.mockBackupScheduleRepo, s.mockAccountGrpcClient, s.mockTiDBClusterInfraClient, s.mockRegionGrpcClient,
		s.mockCRRFSettingRepo, s.mockAWSBucketManagerModel, s.mockAWSBucketConfigRepo)

	mockedClusterID := entity.ID(123)
	mockedBucket := entity.BackupSettingBucket{
		Regions:  []string{"us-east1", "us-east2"},
		Master:   true,
		BucketId: 123,
	}
	mockedBackupSetting := &entity.BackupSetting{
		ClusterID:         mockedClusterID,
		InfraProviderType: "gcp",
		PiTR: &entity.PiTRSetting{
			Enable: false,
		},
		Bucket: &entity.BucketSetting{
			DualRegionEnable: true,
			Buckets:          []entity.BackupSettingBucket{mockedBucket},
			Prefix:           pointer.ToString(strconv.FormatUint(mockedClusterID.Uint64(), 10)),
		},
		Status: entity.BackupSettingStatusNormal,
	}
	mockedBackupSettingID := entity.ID(456)
	mockedBackupSetting.ID = mockedBackupSettingID

	s.mockBackupSettingRepo.On("Get", mock.Anything, mock.Anything).Return(mockedBackupSetting, nil)
	// No active CRRF — dual-region disable is allowed.
	mCRRF := s.mockCRRFSettingRepo.On("GetByClusterID", mock.Anything, mock.Anything).Return(nil, nil).Maybe()
	ctx := context.Background()

	// no region
	req := &domain.RegionSettingModificationReqV2{
		ClusterID: pointer.ToUint64(mockedClusterID.Uint64()),
		Enable:    false,
		Regions:   []string{},
	}
	_, err = backupSettingModel.ModifyRegionSetting(ctx, req)
	s.Error(err)

	// too many regions
	req.Regions = []string{"us-east1", "us-west1"}
	_, err = backupSettingModel.ModifyRegionSetting(ctx, req)
	s.Error(err)

	// master region mismatch
	req.Regions = []string{"us-east2"}
	_, err = backupSettingModel.ModifyRegionSetting(ctx, req)
	s.Error(err)

	// dual region already disabled
	req.Regions = []string{"us-east1"}
	mockedBackupSetting.Bucket.DualRegionEnable = false
	s.mockBackupSettingRepo.On("Get", mock.Anything, mock.Anything).Return(mockedBackupSetting, nil)
	_, err = backupSettingModel.ModifyRegionSetting(ctx, req)
	s.Error(err)

	// no master bucket
	mockedBackupSetting.Bucket.DualRegionEnable = true
	mockedBackupSetting.Bucket.Buckets = nil
	s.mockBackupSettingRepo.On("Get", mock.Anything, mock.Anything).Return(mockedBackupSetting, nil)
	_, err = backupSettingModel.ModifyRegionSetting(ctx, req)
	s.Error(err)

	// normal case
	mockedBackupSetting.Bucket.Buckets = []entity.BackupSettingBucket{mockedBucket}
	m1 := s.mockBackupSettingRepo.On("Get", mock.Anything, mock.Anything).Return(mockedBackupSetting, nil)
	expectedUpdatedBackupSetting := &entity.BackupSetting{
		ClusterID:         mockedClusterID,
		InfraProviderType: "gcp",
		PiTR:              mockedBackupSetting.PiTR,
		Bucket: &entity.BucketSetting{
			DualRegionEnable: false,
			Buckets: []entity.BackupSettingBucket{
				{
					Regions: req.Regions,
					Master:  true,
				},
			},
			Prefix: pointer.ToString(strconv.FormatUint(mockedClusterID.Uint64(), 10)),
		},
		Status: entity.BackupSettingStatusEditingRegion,
		Action: entity.BackupSettingAction{
			CurrentActions: []string{string(entity.BackupSettingActionModifyRegion)},
		},
	}
	expectedUpdatedBackupSetting.ID = mockedBackupSettingID
	m2 := s.mockBackupSettingRepo.On("Save", mock.Anything, expectedUpdatedBackupSetting).Return(nil)
	mockedNodeRole := "mocked_role_arn"
	m3 := s.mockTiDBClusterInfraClient.On(
		"GetNetworkNodeRole", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(mockedNodeRole, nil)
	mockedDagName := workflow.DagToggleDualRegionBackupGCP
	mockedDagInsVars := map[string]string{}
	mockedDagInsVars[workflow.KeyBackupSettingId] = fmt.Sprintf("%d", expectedUpdatedBackupSetting.ID)
	mockedDagInsVars[workflow.KeyRegion] = strings.Join(
		expectedUpdatedBackupSetting.Bucket.Buckets[0].Regions, workflow.SepBucketRegions)
	mockedDagInsVars[workflow.KeyNetworkNodeRole] = mockedNodeRole
	mockedDagIns := &workflowEntity.DagInstance{}
	m4 := s.mockWorkflow.On("RunDagWithTags", mockedDagName, mockedDagInsVars, mock.Anything).Return(mockedDagIns, nil)
	_, err = backupSettingModel.ModifyRegionSetting(ctx, req)
	s.NoError(err)

	m1.Unset()
	m2.Unset()
	m3.Unset()
	m4.Unset()
	mCRRF.Unset()
}
