package impl

import (
	"context"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
	"github.com/stretchr/testify/require"
	"github.com/tidbcloud/core-svc/idl/pbgen/proto/core"
	"github.com/tidbcloud/dataflow-service/app/models/domain"
	"github.com/tidbcloud/dataflow-service/app/repo/entity"
	repo_mock "github.com/tidbcloud/dataflow-service/app/repo/mock"
	"github.com/tidbcloud/dataflow-service/internal/config"
	mgmt "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
	"gorm.io/gorm"
)

func TestQueryGCPBackupRegionOptions(t *testing.T) {
	testcases := []struct {
		name                  string
		cluster               *mgmt.Cluster
		cloudRegionMap        map[string]*core.CloudRegion
		expectedRegionOptions []domain.BackupRegionOption
	}{
		{
			name: "region map is empty",
			cluster: &mgmt.Cluster{
				ClusterId:      1,
				OrganizationId: 1,
				Region:         "asia-east1",
			},
			cloudRegionMap:        map[string]*core.CloudRegion{},
			expectedRegionOptions: []domain.BackupRegionOption{},
		},
		{
			name: "region map is nil",
			cluster: &mgmt.Cluster{
				ClusterId:      1,
				OrganizationId: 1,
				Region:         "asia-east1",
			},
			cloudRegionMap:        nil,
			expectedRegionOptions: []domain.BackupRegionOption{},
		},
		{
			name: "no other region",
			cluster: &mgmt.Cluster{
				ClusterId:      1,
				OrganizationId: 1,
				Region:         "asia-east1",
			},
			cloudRegionMap: map[string]*core.CloudRegion{
				"asia-east1": {
					Name:     "asia-east1",
					Status:   core.CloudRegion_STATUS_ACTIVE,
					IsPublic: true,
				},
			},
			expectedRegionOptions: []domain.BackupRegionOption{},
		},
		{
			name: "only one region can be paired",
			cluster: &mgmt.Cluster{
				ClusterId:      1,
				OrganizationId: 1,
				Region:         "asia-east1",
			},
			cloudRegionMap: map[string]*core.CloudRegion{
				"asia-east1": {
					Name:     "asia-east1",
					Status:   core.CloudRegion_STATUS_ACTIVE,
					IsPublic: true,
				},
				"asia-southeast1": {
					Name:     "asia-southeast1",
					Status:   core.CloudRegion_STATUS_ACTIVE,
					IsPublic: true,
				},
				"us-west2": {
					Name:     "us-west2",
					Status:   core.CloudRegion_STATUS_ACTIVE,
					IsPublic: true,
				},
			},
			expectedRegionOptions: []domain.BackupRegionOption{
				{
					Region: &domain.RegionInfo{
						Name: "asia-southeast1",
					},
				},
			},
		},
		{
			name: "no region can be paired",
			cluster: &mgmt.Cluster{
				ClusterId:      1,
				OrganizationId: 1,
				Region:         "asia-east1",
			},
			cloudRegionMap: map[string]*core.CloudRegion{
				"asia-east1": {
					Name:     "asia-east1",
					Status:   core.CloudRegion_STATUS_ACTIVE,
					IsPublic: true,
				},
				"us-west2": {
					Name:     "us-west2",
					Status:   core.CloudRegion_STATUS_ACTIVE,
					IsPublic: true,
				},
			},
			expectedRegionOptions: []domain.BackupRegionOption{},
		},
		{
			name: "cluster region does not belong to dual region",
			cluster: &mgmt.Cluster{
				ClusterId:      1,
				OrganizationId: 1,
				Region:         "asia-east2",
			},
			cloudRegionMap: map[string]*core.CloudRegion{
				"asia-east2": {
					Name:     "asia-east2",
					Status:   core.CloudRegion_STATUS_ACTIVE,
					IsPublic: true,
				},
				"asia-southeast1": {
					Name:     "asia-southeast1",
					Status:   core.CloudRegion_STATUS_ACTIVE,
					IsPublic: true,
				},
				"us-west2": {
					Name:     "us-west2",
					Status:   core.CloudRegion_STATUS_ACTIVE,
					IsPublic: true,
				},
			},
			expectedRegionOptions: []domain.BackupRegionOption{},
		},
		{
			name: "multiple regions can be paired",
			cluster: &mgmt.Cluster{
				ClusterId:      1,
				OrganizationId: 1,
				Region:         "us-west1",
			},
			cloudRegionMap: map[string]*core.CloudRegion{
				"asia-east1": {
					Name:     "asia-east1",
					Status:   core.CloudRegion_STATUS_ACTIVE,
					IsPublic: true,
				},
				"asia-southeast1": {
					Name:     "asia-southeast1",
					Status:   core.CloudRegion_STATUS_ACTIVE,
					IsPublic: true,
				},
				"us-west1": {
					Name:     "us-west1",
					Status:   core.CloudRegion_STATUS_ACTIVE,
					IsPublic: true,
				},
				"us-west2": {
					Name:     "us-west2",
					Status:   core.CloudRegion_STATUS_ACTIVE,
					IsPublic: true,
				},
				"us-east1": {
					Name:     "us-east1",
					Status:   core.CloudRegion_STATUS_ACTIVE,
					IsPublic: true,
				},
			},
			expectedRegionOptions: []domain.BackupRegionOption{
				{
					Region: &domain.RegionInfo{
						Name: "us-east1",
					},
				},
				{
					Region: &domain.RegionInfo{
						Name: "us-west2",
					},
				},
			},
		},
		{
			name: "has inactive region",
			cluster: &mgmt.Cluster{
				ClusterId:      1,
				OrganizationId: 1,
				Region:         "us-west1",
			},
			cloudRegionMap: map[string]*core.CloudRegion{
				"us-west1": {
					Name:     "us-west1",
					Status:   core.CloudRegion_STATUS_ACTIVE,
					IsPublic: true,
				},
				"us-west2": {
					Name:     "us-west2",
					Status:   core.CloudRegion_STATUS_ACTIVE,
					IsPublic: true,
				},
				"us-east1": {
					Name:     "us-east1",
					Status:   core.CloudRegion_STATUS_INACTIVE,
					IsPublic: true,
				},
			},
			expectedRegionOptions: []domain.BackupRegionOption{
				{
					Region: &domain.RegionInfo{
						Name: "us-west2",
					},
				},
			},
		},
		{
			name: "has not public region and inactive region",
			cluster: &mgmt.Cluster{
				ClusterId:      1,
				OrganizationId: 1,
				Region:         "us-west1",
			},
			cloudRegionMap: map[string]*core.CloudRegion{
				"us-west1": {
					Name:     "us-west1",
					Status:   core.CloudRegion_STATUS_ACTIVE,
					IsPublic: true,
				},
				"us-west2": {
					Name:     "us-west2",
					Status:   core.CloudRegion_STATUS_ACTIVE,
					IsPublic: true,
				},
				"us-west3": {
					Name:     "us-west3",
					Status:   core.CloudRegion_STATUS_ACTIVE,
					IsPublic: false,
				},
				"us-west4": {
					Name:            "us-west4",
					Status:          core.CloudRegion_STATUS_ACTIVE,
					IsPublic:        false,
					OpenedTenantIds: []uint64{1, 2},
				},
				"us-east1": {
					Name:     "us-east1",
					Status:   core.CloudRegion_STATUS_INACTIVE,
					IsPublic: true,
				},
			},
			expectedRegionOptions: []domain.BackupRegionOption{
				{
					Region: &domain.RegionInfo{
						Name: "us-west2",
					},
				},
				{
					Region: &domain.RegionInfo{
						Name: "us-west4",
					},
				},
			},
		},
	}

	config.InitGCPDualRegionConfigForTest()
	bm := &backupSettingModel{}
	for _, tc := range testcases {
		t.Run(tc.name, func(t *testing.T) {
			resp := bm.queryGCPBackupRegionOptions(context.Background(), tc.cluster, tc.cloudRegionMap)
			assert.Equal(t, tc.cluster.GetClusterId(), resp.ClusterID)
			assert.Equal(t, tc.cluster.GetRegion(), resp.ClusterRegion.Name)
			assert.Equal(t, tc.expectedRegionOptions, resp.BackupRegionOptions)
		})
	}
}

func TestGenAWSBackupRegionOptions(t *testing.T) {
	testcases := []struct {
		name                  string
		cluster               *mgmt.Cluster
		cloudRegionMap        map[string]*core.CloudRegion
		expectedRegionOptions []domain.BackupRegionOption
	}{
		{
			name: "region map is empty",
			cluster: &mgmt.Cluster{
				ClusterId:      1,
				OrganizationId: 1,
				Region:         "us-west-2",
			},
			cloudRegionMap:        map[string]*core.CloudRegion{},
			expectedRegionOptions: []domain.BackupRegionOption{},
		},
		{
			name: "region map is nil",
			cluster: &mgmt.Cluster{
				ClusterId:      1,
				OrganizationId: 1,
				Region:         "us-west-2",
			},
			cloudRegionMap:        nil,
			expectedRegionOptions: []domain.BackupRegionOption{},
		},
		{
			name: "one region can be paired",
			cluster: &mgmt.Cluster{
				ClusterId:      1,
				OrganizationId: 1,
				Region:         "us-west-2",
			},
			cloudRegionMap: map[string]*core.CloudRegion{
				"us-west-2": {
					Name:     "us-west-2",
					Status:   core.CloudRegion_STATUS_ACTIVE,
					IsPublic: true,
				},
			},
			expectedRegionOptions: []domain.BackupRegionOption{},
		},
		{
			name: "multiple regions can be paired",
			cluster: &mgmt.Cluster{
				ClusterId:      1,
				OrganizationId: 1,
				Region:         "us-west-2",
			},
			cloudRegionMap: map[string]*core.CloudRegion{
				"us-west-2": {
					Name:     "us-west-2",
					Status:   core.CloudRegion_STATUS_ACTIVE,
					IsPublic: true,
				},
				"ap-east-1": {
					Name:     "ap-east-1",
					Status:   core.CloudRegion_STATUS_ACTIVE,
					IsPublic: true,
				},
				"ap-southeast-1": {
					Name:     "ap-southeast-1",
					Status:   core.CloudRegion_STATUS_ACTIVE,
					IsPublic: true,
				},
			},
			expectedRegionOptions: []domain.BackupRegionOption{
				{
					Region: &domain.RegionInfo{
						Name: "ap-east-1",
					},
				},
				{
					Region: &domain.RegionInfo{
						Name: "ap-southeast-1",
					},
				},
			},
		},
		{
			name: "has inactive region",
			cluster: &mgmt.Cluster{
				ClusterId:      1,
				OrganizationId: 1,
				Region:         "us-west-2",
			},
			cloudRegionMap: map[string]*core.CloudRegion{
				"us-west-2": {
					Name:     "us-west-2",
					Status:   core.CloudRegion_STATUS_ACTIVE,
					IsPublic: true,
				},
				"us-east-1": {
					Name:     "us-east-1",
					Status:   core.CloudRegion_STATUS_ACTIVE,
					IsPublic: true,
				},
				"ap-east-1": {
					Name:     "ap-east-1",
					Status:   core.CloudRegion_STATUS_INACTIVE,
					IsPublic: true,
				},
			},
			expectedRegionOptions: []domain.BackupRegionOption{
				{
					Region: &domain.RegionInfo{
						Name: "us-east-1",
					},
				},
			},
		},
		{
			name: "has not public region and inactive region",
			cluster: &mgmt.Cluster{
				ClusterId:      1,
				OrganizationId: 1,
				Region:         "us-west-2",
			},
			cloudRegionMap: map[string]*core.CloudRegion{
				"us-west-2": {
					Name:     "us-west-2",
					Status:   core.CloudRegion_STATUS_ACTIVE,
					IsPublic: true,
				},
				"us-east-1": {
					Name:     "us-east-1",
					Status:   core.CloudRegion_STATUS_ACTIVE,
					IsPublic: true,
				},
				"ap-east-1": {
					Name:     "ap-east-1",
					Status:   core.CloudRegion_STATUS_INACTIVE,
					IsPublic: true,
				},
				"ap-southeast-1": {
					Name:     "ap-southeast-1",
					Status:   core.CloudRegion_STATUS_ACTIVE,
					IsPublic: false,
				},
				"ap-northeast-1": {
					Name:            "ap-northeast-1",
					Status:          core.CloudRegion_STATUS_ACTIVE,
					IsPublic:        false,
					OpenedTenantIds: []uint64{1, 2},
				},
			},
			expectedRegionOptions: []domain.BackupRegionOption{
				{
					Region: &domain.RegionInfo{
						Name: "ap-northeast-1",
					},
				},
				{
					Region: &domain.RegionInfo{
						Name: "us-east-1",
					},
				},
			},
		},
	}

	bm := &backupSettingModel{}
	for _, tc := range testcases {
		t.Run(tc.name, func(t *testing.T) {
			resp := bm.genAWSBackupRegionOptions(context.Background(), tc.cluster, tc.cloudRegionMap)
			assert.Equal(t, tc.cluster.GetClusterId(), resp.ClusterID)
			assert.Equal(t, tc.cluster.GetRegion(), resp.ClusterRegion.Name)
			assert.Equal(t, tc.expectedRegionOptions, resp.BackupRegionOptions)
		})
	}
}

func TestIsRegionEnabled(t *testing.T) {
	testcases := []struct {
		tenantID       uint64
		region         *core.CloudRegion
		expectedResult bool
	}{
		{
			tenantID: 1,
			region: &core.CloudRegion{
				Name:   "us-west-2",
				Status: core.CloudRegion_STATUS_INACTIVE,
			},
			expectedResult: false,
		},
		{
			tenantID: 1,
			region: &core.CloudRegion{
				Name:     "us-west-2",
				Status:   core.CloudRegion_STATUS_INACTIVE,
				IsPublic: true,
			},
			expectedResult: false,
		},
		{
			tenantID: 1,
			region: &core.CloudRegion{
				Name:     "us-west-2",
				Status:   core.CloudRegion_STATUS_ACTIVE,
				IsPublic: true,
			},
			expectedResult: true,
		},
		{
			tenantID: 1,
			region: &core.CloudRegion{
				Name:            "us-west-2",
				Status:          core.CloudRegion_STATUS_ACTIVE,
				IsPublic:        false,
				OpenedTenantIds: []uint64{},
			},
			expectedResult: false,
		},
		{
			tenantID: 1,
			region: &core.CloudRegion{
				Name:            "us-west-2",
				Status:          core.CloudRegion_STATUS_ACTIVE,
				IsPublic:        false,
				OpenedTenantIds: []uint64{2, 3},
			},
			expectedResult: false,
		},
		{
			tenantID: 1,
			region: &core.CloudRegion{
				Name:            "us-west-2",
				Status:          core.CloudRegion_STATUS_ACTIVE,
				IsPublic:        false,
				OpenedTenantIds: []uint64{1, 2, 3},
			},
			expectedResult: true,
		},
	}

	for _, tc := range testcases {
		enabled := isRegionEnabled(tc.region, tc.tenantID)
		assert.Equal(t, tc.expectedResult, enabled)
	}
}

type fakeTransProvider struct{}

func (f *fakeTransProvider) ReadDB(ctx context.Context) *gorm.DB {
	return nil
}

func (f *fakeTransProvider) WriteDB(ctx context.Context) *gorm.DB {
	return nil
}

func (f *fakeTransProvider) Transaction(ctx context.Context, cb func(ctx context.Context) error) error {
	return cb(ctx)
}

func TestModifyAutoBackupSettingDisableWithoutSchedule(t *testing.T) {
	ctx := context.Background()
	backupSettingRepo := repo_mock.NewBackupSettingRepo(t)
	backupScheduleRepo := repo_mock.NewBackupScheduleRepo(t)
	bm := &backupSettingModel{
		dbProvider:         &fakeTransProvider{},
		backupSettingRepo:  backupSettingRepo,
		backupScheduleRepo: backupScheduleRepo,
	}

	backupSetting := &entity.BackupSetting{
		ClusterID: entity.ID(123),
		AutoBackup: &entity.AutoBackupSetting{
			Disable: false,
		},
	}

	backupSettingRepo.On("Get", mock.Anything, mock.Anything).Return(backupSetting, nil).Once()
	backupSettingRepo.On("Save", mock.Anything, mock.MatchedBy(func(setting *entity.BackupSetting) bool {
		return setting != nil && setting.IsAutoBackupDisabled()
	})).Return(nil).Once()

	resp, err := bm.ModifyAutoBackupSetting(ctx, &domain.ModifyAutoBackupSettingReq{
		ClusterID: 123,
		SnapshotBackupSchedule: domain.SnapshotBackupSchedule{
			Disable: true,
		},
	})

	require.NoError(t, err)
	require.NotNil(t, resp)
	backupScheduleRepo.AssertNotCalled(t, "List", mock.Anything, mock.Anything)
	backupScheduleRepo.AssertNotCalled(t, "PatchAuto", mock.Anything, mock.Anything, mock.Anything)
}

func TestCalculateScheduleCronRejectsNilWeeklySchedule(t *testing.T) {
	bm := &backupSettingModel{}

	_, err := bm.calculateScheduleCron(context.Background(), &domain.ModifyAutoBackupSettingReq{
		SnapshotBackupSchedule: domain.SnapshotBackupSchedule{
			ScheduleType: domain.SnapshotBackupScheduleTypeWeekly,
		},
	})

	require.Error(t, err)
}
