package common

import (
	"context"
	"errors"
	"fmt"
	"net/http"
	"time"

	"github.com/tidbcloud/dataflow-service-e2e/internal/config"
	"github.com/tidbcloud/dataflow-service-e2e/internal/log"
	"github.com/tidbcloud/dataflow-service-e2e/internal/misc"
	. "github.com/tidbcloud/dataflow-service-e2e/internal/model"
	"github.com/tidbcloud/dataflow-service-e2e/internal/tidbcloud/dedicated/consoleapi"
	console_br_params "github.com/tidbcloud/dataflow-service-e2e/internal/tidbcloud/dedicated/consoleapi/br/client/backup_service"
	br_model "github.com/tidbcloud/dataflow-service-e2e/internal/tidbcloud/dedicated/consoleapi/br/model"
)

// BackupValidator provides validation helper methods for BR E2E tests.
type BackupValidator struct {
	lgr     log.SimpleLogger
	backupW *consoleapi.BackupClientWrapper
}

// NewBackupValidator creates a new BackupValidator instance.
func NewBackupValidator(lgr log.SimpleLogger) *BackupValidator {
	return &BackupValidator{
		lgr:     lgr,
		backupW: consoleapi.NewBackupClientWrapper(lgr),
	}
}

// Client returns the underlying BackupClientWrapper for direct API access.
func (v *BackupValidator) Client() *consoleapi.BackupClientWrapper {
	return v.backupW
}

// IsAutoBackupEnabled checks if automatic backup is enabled for the cluster.
func (v *BackupValidator) IsAutoBackupEnabled(tidbCluster *TiDBDedicatedEntity) error {
	resp, err := v.backupW.QueryBackupSetting(tidbCluster)
	if err != nil {
		return err
	}

	if resp.SnapshotBackupSchedule == nil {
		return fmt.Errorf("snapshot backup schedule not found in cluster %s", tidbCluster.ClusterID)
	}

	if resp.SnapshotBackupSchedule.Disable {
		return fmt.Errorf("auto backup is disabled in cluster %s", tidbCluster.ClusterID)
	}

	v.lgr.Info("auto backup is enabled in cluster %s, schedule type: %s",
		tidbCluster.ClusterID, resp.SnapshotBackupSchedule.ScheduleType)
	return nil
}

// IsAutoBackupDisabled checks if automatic backup is disabled for the cluster.
func (v *BackupValidator) IsAutoBackupDisabled(tidbCluster *TiDBDedicatedEntity) error {
	resp, err := v.backupW.QueryBackupSetting(tidbCluster)
	if err != nil {
		return err
	}

	if resp.SnapshotBackupSchedule != nil && !resp.SnapshotBackupSchedule.Disable {
		return fmt.Errorf("auto backup is still enabled in cluster %s", tidbCluster.ClusterID)
	}

	v.lgr.Info("auto backup is disabled in cluster %s", tidbCluster.ClusterID)
	return nil
}

// IsPiTREnabled checks if PiTR (continuous backup) is enabled for the cluster.
func (v *BackupValidator) IsPiTREnabled(tidbCluster *TiDBDedicatedEntity) error {
	resp, err := v.backupW.QueryBackupSetting(tidbCluster)
	if err != nil {
		return err
	}

	if resp.ContinuousBackupSchedule == nil || !resp.ContinuousBackupSchedule.Enable {
		return fmt.Errorf("PiTR is not enabled in cluster %s", tidbCluster.ClusterID)
	}
	if resp.Status != string(br_model.DataflowBackupSettingStatusNormal) {
		return fmt.Errorf("backup setting status is %s", resp.Status)
	}

	v.lgr.Info("PiTR is enabled in cluster %s", tidbCluster.ClusterID)
	return nil
}

// IsPiTRDisabled checks if PiTR (continuous backup) is disabled for the cluster.
func (v *BackupValidator) IsPiTRDisabled(tidbCluster *TiDBDedicatedEntity) error {
	resp, err := v.backupW.QueryBackupSetting(tidbCluster)
	if err != nil {
		return err
	}

	if resp.ContinuousBackupSchedule != nil && resp.ContinuousBackupSchedule.Enable {
		return fmt.Errorf("PiTR is still enabled in cluster %s", tidbCluster.ClusterID)
	}
	if resp.Status != string(br_model.DataflowBackupSettingStatusNormal) {
		return fmt.Errorf("backup setting status is %s", resp.Status)
	}

	v.lgr.Info("PiTR is disabled in cluster %s", tidbCluster.ClusterID)
	return nil
}

// IsBackupSettingAsExpected verifies backup settings match expected values.
func (v *BackupValidator) IsBackupSettingAsExpected(
	tidbCluster *TiDBDedicatedEntity,
	expectedScheduleType string,
	expectedPiTREnabled bool,
) error {
	resp, err := v.backupW.QueryBackupSetting(tidbCluster)
	if err != nil {
		return err
	}

	if resp.SnapshotBackupSchedule != nil {
		if resp.SnapshotBackupSchedule.ScheduleType != expectedScheduleType {
			return fmt.Errorf("schedule type mismatch, expected %s, actual %s",
				expectedScheduleType, resp.SnapshotBackupSchedule.ScheduleType)
		}
	}

	actualPiTREnabled := resp.ContinuousBackupSchedule != nil && resp.ContinuousBackupSchedule.Enable
	if actualPiTREnabled != expectedPiTREnabled {
		return fmt.Errorf("PiTR enabled mismatch, expected %v, actual %v",
			expectedPiTREnabled, actualPiTREnabled)
	}

	v.lgr.Info("backup settings match expected values for cluster %s", tidbCluster.ClusterID)
	return nil
}

// IsBackupInList checks if a specific backup exists in the backup list.
func (v *BackupValidator) IsBackupInList(tidbCluster *TiDBDedicatedEntity, backupID uint64) error {
	resp, err := v.backupW.ListBackups(tidbCluster, 1, 100)
	if err != nil {
		return err
	}

	for _, backup := range resp.Items {
		if backup.Id == backupID {
			v.lgr.Info("backup %d(%s) found in cluster %s", backupID, backup.Name, tidbCluster.ClusterID)
			return nil
		}
	}

	return fmt.Errorf("backup %d not found in cluster %s", backupID, tidbCluster.ClusterID)
}

// IsBackupNotInList checks if a specific backup does not exist in the backup list.
func (v *BackupValidator) IsBackupNotInList(tidbCluster *TiDBDedicatedEntity, backupID uint64) error {
	resp, err := v.backupW.ListBackups(tidbCluster, 1, 100)
	if err != nil {
		return err
	}

	for _, backup := range resp.Items {
		if backup.Id == backupID {
			return fmt.Errorf("backup %d(%s) still exists in cluster %s", backupID, backup.Name, tidbCluster.ClusterID)
		}
	}

	v.lgr.Info("backup %d not found in cluster %s (as expected)", backupID, tidbCluster.ClusterID)
	return nil
}

// GetLatestBackup returns the most recent backup for a cluster.
func (v *BackupValidator) GetLatestBackup(tidbCluster *TiDBDedicatedEntity) (*consoleapi.BackupItem, error) {
	resp, err := v.backupW.ListBackups(tidbCluster, 1, 1)
	if err != nil {
		return nil, err
	}

	if len(resp.Items) == 0 {
		return nil, fmt.Errorf("no backups found for cluster %s", tidbCluster.ClusterID)
	}

	backup := resp.Items[0]
	v.lgr.Info("latest backup for cluster %s: id=%d, name=%s, status=%s",
		tidbCluster.ClusterID, backup.Id, backup.Name, backup.Status)
	return backup, nil
}

// IsBackupInStatus checks if a backup is in the expected status.
func (v *BackupValidator) IsBackupInStatus(tidbCluster *TiDBDedicatedEntity, backupID uint64, expectedStatus string) error {
	resp, err := v.backupW.ListBackups(tidbCluster, 1, 100)
	if err != nil {
		return err
	}

	for _, backup := range resp.Items {
		if backup.Id == backupID {
			if backup.Status != expectedStatus {
				return fmt.Errorf("backup %d(%s) status mismatch, expected %s, actual %s",
					backupID, backup.Name, expectedStatus, backup.Status)
			}
			v.lgr.Info("backup %d(%s) is in expected status %s", backupID, backup.Name, expectedStatus)
			return nil
		}
	}

	return fmt.Errorf("backup %d not found in cluster %s", backupID, tidbCluster.ClusterID)
}

// WaitForBackupComplete waits for a backup to complete with polling.
func (v *BackupValidator) WaitForBackupComplete(tidbCluster *TiDBDedicatedEntity, backupID uint64, timeout time.Duration) error {
	deadline := time.Now().Add(timeout)
	interval := 30 * time.Second

	for time.Now().Before(deadline) {
		err := v.IsBackupInStatus(tidbCluster, backupID, string(br_model.DataflowBackupExportStatusSUCCESS))
		if err == nil {
			return nil
		}

		backupName := ""
		resp, listErr := v.backupW.ListBackups(tidbCluster, 1, 100)
		if listErr == nil {
			for _, backup := range resp.Items {
				if backup.Id == backupID {
					backupName = backup.Name
					if backup.Status == string(br_model.DataflowBackupExportStatusFAILED) {
						return fmt.Errorf("backup %d(%s) failed", backupID, backupName)
					}
				}
			}
		}

		v.lgr.Info("waiting for backup %d(%s) to complete, remaining time: %s", backupID, backupName, deadline.Sub(time.Now()))
		time.Sleep(interval)
	}

	return fmt.Errorf("timeout waiting for backup %d to complete", backupID)
}

// IsRestoreInStatus checks if a restore is in the expected status.
func (v *BackupValidator) IsRestoreInStatus(tidbCluster *TiDBDedicatedEntity, restoreID uint64, expectedStatus string) error {
	resp, err := v.backupW.ListRestores(tidbCluster, 1, 100)
	if err != nil {
		return err
	}

	for _, restore := range resp.Items {
		if restore.Id == restoreID {
			if restore.Status != expectedStatus {
				return fmt.Errorf("restore %d status mismatch, expected %s, actual %s",
					restoreID, expectedStatus, restore.Status)
			}
			v.lgr.Info("restore %d is in expected status %s", restoreID, expectedStatus)
			return nil
		}
	}

	return fmt.Errorf("restore %d not found for cluster %s", restoreID, tidbCluster.ClusterID)
}

// WaitForRestoreComplete waits for a restore to complete with polling.
func (v *BackupValidator) WaitForRestoreComplete(tidbCluster *TiDBDedicatedEntity, restoreID uint64, timeout time.Duration) error {
	deadline := time.Now().Add(timeout)
	interval := 30 * time.Second

	for time.Now().Before(deadline) {
		resp, err := v.backupW.ListRestores(tidbCluster, 1, 100)
		if err != nil {
			if isUnauthorizedAPIError(err) {
				return fmt.Errorf("list restores authorization failed: %w", err)
			}
			v.lgr.Warn("failed to list restores: %s", err.Error())
			time.Sleep(interval)
			continue
		}

		for _, restore := range resp.Items {
			if restore.Id == restoreID {
				switch restore.Status {
				case string(br_model.DataflowBackupExportStatusSUCCESS):
					v.lgr.Info("restore %d completed successfully", restoreID)
					return nil
				case string(br_model.DataflowBackupExportStatusFAILED):
					return fmt.Errorf("restore %d failed", restoreID)
				default:
					v.lgr.Info("restore %d in progress, status: %s, progress: %.2f%%",
						restoreID, restore.Status, restore.Progress*100)
				}
				break
			}
		}

		time.Sleep(interval)
	}

	return fmt.Errorf("timeout waiting for restore %d to complete", restoreID)
}

func isUnauthorizedAPIError(err error) bool {
	var apiErr interface {
		Code() int
	}
	return errors.As(err, &apiErr) && apiErr.Code() == http.StatusUnauthorized
}

// HasPiTRTimeRanges checks if PiTR time ranges are available.
func (v *BackupValidator) HasPiTRTimeRanges(tidbCluster *TiDBDedicatedEntity) error {
	resp, err := v.backupW.ListPiTRTimeRanges(tidbCluster)
	if err != nil {
		return err
	}

	if len(resp.Ranges) == 0 {
		return fmt.Errorf("no PiTR time ranges available for cluster %s", tidbCluster.ClusterID)
	}

	v.lgr.Info("PiTR time ranges available for cluster %s: %s", tidbCluster.ClusterID, misc.JSON(resp.Ranges))
	return nil
}

// IsExportInStatus checks if a backup export is in the expected status.
func (v *BackupValidator) IsExportInStatus(tidbCluster *TiDBDedicatedEntity, exportID uint64, expectedStatus string) error {
	resp, err := v.backupW.ListBackupExports(tidbCluster, 1, 100)
	if err != nil {
		return err
	}

	for _, export := range resp.Items {
		if export.Id == exportID {
			if export.Status != expectedStatus {
				return fmt.Errorf("export %d status mismatch, expected %s, actual %s",
					exportID, expectedStatus, export.Status)
			}
			v.lgr.Info("export %d is in expected status %s", exportID, expectedStatus)
			return nil
		}
	}

	return fmt.Errorf("export %d not found for cluster %s", exportID, tidbCluster.ClusterID)
}

// WaitForExportComplete waits for a backup export to complete with polling.
func (v *BackupValidator) WaitForExportComplete(tidbCluster *TiDBDedicatedEntity, exportID uint64, timeout time.Duration) error {
	deadline := time.Now().Add(timeout)
	interval := 30 * time.Second

	for time.Now().Before(deadline) {
		err := v.IsExportInStatus(tidbCluster, exportID, string(br_model.DataflowBackupExportStatusSUCCESS))
		if err == nil {
			return nil
		}

		// Check if export failed or was canceled
		resp, listErr := v.backupW.ListBackupExports(tidbCluster, 1, 100)
		if listErr == nil {
			for _, export := range resp.Items {
				if export.Id == exportID {
					if export.Status == string(br_model.DataflowBackupExportStatusFAILED) {
						return fmt.Errorf("export %d failed: %s", exportID, export.ErrorMsg)
					}
					if export.Status == string(br_model.DataflowBackupExportStatusCANCELED) {
						return fmt.Errorf("export %d was canceled", exportID)
					}
				}
			}
		} else {
			v.lgr.Warn("failed to list backup exports: %s", listErr.Error())
		}

		v.lgr.Info("waiting for export %d to complete, remaining time: %s", exportID, deadline.Sub(time.Now()))
		time.Sleep(interval)
	}

	return fmt.Errorf("timeout waiting for export %d to complete", exportID)
}

// EnableDailyAutoBackup enables daily automatic backup with default settings.
func (v *BackupValidator) EnableDailyAutoBackup(tidbCluster *TiDBDedicatedEntity, scheduleTime string, retentionDays int64) error {
	req := &consoleapi.ModifyBackupSettingRequest{
		SnapshotBackupSchedule: &consoleapi.SnapshotBackupScheduleRequest{
			Disable:           false,
			ScheduleType:      string(br_model.DataflowSnapshotBackupScheduleTypeDaily),
			DailyScheduleTime: scheduleTime,
			RetentionPolicy: &consoleapi.RetentionPolicyReq{
				RetentionPeriod: retentionDays,
			},
		},
	}
	return v.backupW.ModifyBackupSetting(tidbCluster, req)
}

// DisableAutoBackup disables automatic backup.
func (v *BackupValidator) DisableAutoBackup(tidbCluster *TiDBDedicatedEntity) error {
	req := &consoleapi.ModifyBackupSettingRequest{
		SnapshotBackupSchedule: &consoleapi.SnapshotBackupScheduleRequest{
			Disable: true,
		},
	}
	return v.backupW.ModifyBackupSetting(tidbCluster, req)
}

// EnablePiTR enables PiTR (continuous backup).
func (v *BackupValidator) EnablePiTR(tidbCluster *TiDBDedicatedEntity) error {
	req := &consoleapi.ModifyBackupSettingRequest{
		ContinuousBackupSchedule: &consoleapi.ContinuousBackupScheduleRequest{
			Enable: true,
		},
	}
	return v.backupW.ModifyBackupSetting(tidbCluster, req)
}

// DisablePiTR disables PiTR (continuous backup).
func (v *BackupValidator) DisablePiTR(tidbCluster *TiDBDedicatedEntity) error {
	req := &consoleapi.ModifyBackupSettingRequest{
		ContinuousBackupSchedule: &consoleapi.ContinuousBackupScheduleRequest{
			Enable: false,
		},
	}
	return v.backupW.ModifyBackupSetting(tidbCluster, req)
}

// TriggerSnapshotBackup triggers a manual snapshot backup with retry logic for 500 errors.
// Returns the backup ID if successful.
func (v *BackupValidator) TriggerSnapshotBackup(tidbCluster *TiDBDedicatedEntity, name string) (uint64, error) {
	req := &consoleapi.CreateBackupScheduleRequest{
		BackupMode:      "FULL",
		BackupTime:      time.Now().Format(time.RFC3339),
		BackupType:      "MANUAL",
		Description:     "Manual snapshot backup for PiTR activation",
		Name:            name,
		RetentionPeriod: 7, // 7 days retention
	}

	// Retry logic for 500 errors
	maxRetries := 3
	for i := 0; i < maxRetries; i++ {
		if i > 0 {
			v.lgr.Info("retrying TriggerSnapshotBackup (attempt %d/%d)", i+1, maxRetries)
			time.Sleep(30 * time.Second)
		}

		resp, err := v.backupW.CreateBackupSchedule(tidbCluster, req)
		if err != nil {
			v.lgr.Warn("TriggerSnapshotBackup failed (attempt %d/%d): %v", i+1, maxRetries, err)
			continue
		}
		v.lgr.Info("triggered snapshot backup %d(%s) for cluster %s", resp.Id, name, tidbCluster.ClusterID)
		return resp.Id, nil
	}
	return 0, fmt.Errorf("TriggerSnapshotBackup failed after %d retries", maxRetries)
}

// TriggerSnapshotBackupAndWait triggers a snapshot backup and waits for it to complete.
// This method includes a workaround for the backend API bug where CreateBackupSchedule
// returns backup_id=0 when the cluster is enabling PiTR.
func (v *BackupValidator) TriggerSnapshotBackupAndWait(
	tidbCluster *TiDBDedicatedEntity,
	backupName string,
	timeout time.Duration,
) (uint64, error) {
	backupID, err := v.TriggerSnapshotBackup(tidbCluster, backupName)
	if err != nil {
		return 0, err
	}

	// Workaround: Backend API bug - CreateBackupSchedule returns backup_id=0 when cluster is enabling PiTR
	if backupID == 0 {
		v.lgr.Info("backup_id is 0 (backend bug), listing backups to find by name: %s", backupName)

		maxRetries := 5
		for i := 0; i < maxRetries; i++ {
			if i > 0 {
				time.Sleep(2 * time.Second)
			}

			backupList, listErr := v.backupW.ListBackups(tidbCluster, 1, 100)
			if listErr != nil {
				v.lgr.Warn("failed to list backups (attempt %d/%d): %v", i+1, maxRetries, listErr)
				continue
			}

			for _, backup := range backupList.Items {
				if backup.Name == backupName {
					backupID = backup.Id
					v.lgr.Info("found backup by name: %s, backup_id: %d", backupName, backupID)
					break
				}
			}

			if backupID != 0 {
				break
			}
		}

		if backupID == 0 {
			return 0, fmt.Errorf("failed to find backup by name after listing: %s", backupName)
		}
	}

	// Wait for backup to complete
	if err := v.WaitForBackupComplete(tidbCluster, backupID, timeout); err != nil {
		// Preserve the created backup ID so callers can still clean it up.
		return backupID, fmt.Errorf("snapshot backup %d did not complete: %w", backupID, err)
	}

	v.lgr.Info("snapshot backup %d completed successfully", backupID)
	return backupID, nil
}

// WaitForBackupSettingReady waits for backup setting status to become ready for modifications.
// Ready statuses are: Normal, Editing_Snapshot
// Not ready statuses include: Waiting_For_Editing_PiTR, Editing_PiTR, Disabling, etc.
func (v *BackupValidator) WaitForBackupSettingReady(tidbCluster *TiDBDedicatedEntity, timeout time.Duration) error {
	return v.WaitForBackupSettingStatus(tidbCluster, string(br_model.DataflowBackupSettingStatusNormal), timeout)
}

// WaitForBackupSettingStatus waits for backup setting status to become ready for modifications.
func (v *BackupValidator) WaitForBackupSettingStatus(tidbCluster *TiDBDedicatedEntity, status string, timeout time.Duration) error {
	deadline := time.Now().Add(timeout)
	interval := 10 * time.Second

	for time.Now().Before(deadline) {
		resp, err := v.backupW.QueryBackupSetting(tidbCluster)
		if err != nil {
			return err
		}

		// Check if status matches the desired status
		if resp.Status == status {
			v.lgr.Info("backup setting reached desired status: %s", resp.Status)
			return nil
		}

		v.lgr.Info("waiting for backup setting to reach desired status, current status: %s, desired status: %s, remaining time: %s",
			resp.Status, status, time.Until(deadline))
		time.Sleep(interval)
	}

	return fmt.Errorf("timeout waiting for backup setting to reach desired status: %s", status)
}

// IsDualRegionEnabled checks if dual-region backup (2 regions) is configured and active.
func (v *BackupValidator) IsDualRegionEnabled(tidbCluster *TiDBDedicatedEntity) error {
	resp, err := v.backupW.QueryBackupSetting(tidbCluster)
	if err != nil {
		return err
	}
	if len(resp.Regions) < 2 {
		return fmt.Errorf("expected dual-region (≥2 regions), got %d region(s)", len(resp.Regions))
	}
	v.lgr.Info("dual-region is enabled in cluster %s (%d regions)", tidbCluster.ClusterID, len(resp.Regions))
	return nil
}

// GetSecondaryRegion returns a secondary region from backup options. When the
// caller will create a cluster in that region, the result must also be present
// in the E2E runner's configured region list.
func (v *BackupValidator) GetSecondaryRegion(
	tidbCluster *TiDBDedicatedEntity,
	requiresClusterCreation bool,
) (string, error) {
	dualRegionOptions, err := v.getDualRegionOptions(tidbCluster)
	if err != nil {
		return "", err
	}

	return pickSecondaryRegionFromBackupOptions(
		tidbCluster,
		dualRegionOptions,
		requiresClusterCreation,
		config.GlobalConfig.RegionsOf(tidbCluster.CloudProvider),
	)
}

func (v *BackupValidator) getDualRegionOptions(
	tidbCluster *TiDBDedicatedEntity,
) (*br_model.BackupOptionsRespDualRegionOptions, error) {
	params := console_br_params.NewGetBackupOptionsParamsWithContext(log.WithLogger(context.Background(), v.lgr)).
		WithOrgID(tidbCluster.OrgID).
		WithProjectID(tidbCluster.ProjectID).
		WithClusterID(tidbCluster.ClusterID)

	resp, err := consoleapi.BrBackupServiceClient().GetBackupOptions(params, nil)
	if err != nil {
		return nil, err
	}

	if resp.Payload == nil {
		return nil, fmt.Errorf("no backup options payload for cluster %s", tidbCluster.ClusterID)
	}

	return resp.Payload.DualRegionOptions, nil
}

func pickSecondaryRegionFromBackupOptions(
	tidbCluster *TiDBDedicatedEntity,
	dualRegionOptions *br_model.BackupOptionsRespDualRegionOptions,
	requiresClusterCreation bool,
	configuredRegions []string,
) (string, error) {
	if dualRegionOptions == nil {
		return "", fmt.Errorf("no dual_region_options for cluster %s", tidbCluster.ClusterID)
	}

	configuredRegionSet := make(map[string]struct{}, len(configuredRegions))
	if requiresClusterCreation {
		for _, region := range configuredRegions {
			configuredRegionSet[region] = struct{}{}
		}
	}

	for _, regionOption := range dualRegionOptions.RegionOptions {
		if regionOption == nil || regionOption.Region == nil {
			continue
		}
		regionName := regionOption.Region.Name
		if regionName == "" || regionName == tidbCluster.Region {
			continue
		}
		if requiresClusterCreation {
			if _, ok := configuredRegionSet[regionName]; !ok {
				continue
			}
		}
		return regionName, nil
	}

	if requiresClusterCreation {
		return "", fmt.Errorf(
			"no API-advertised secondary region is configured for cluster creation on cluster %s",
			tidbCluster.ClusterID,
		)
	}
	return "", fmt.Errorf("no secondary region is available for cluster %s", tidbCluster.ClusterID)
}

// EnableDualRegion enables dual-region backup configuration.
func (v *BackupValidator) EnableDualRegion(tidbCluster *TiDBDedicatedEntity, secondaryRegion string) error {
	req := &consoleapi.ModifyBackupSettingRequest{
		Regions: []*consoleapi.RegionInfoRequest{
			{Name: tidbCluster.Region},
			{Name: secondaryRegion},
		},
	}
	return v.backupW.ModifyBackupSetting(tidbCluster, req)
}

// DisableDualRegion downgrades from dual-region to single-region backup
// by setting Regions to only the primary region.
func (v *BackupValidator) DisableDualRegion(tidbCluster *TiDBDedicatedEntity) error {
	req := &consoleapi.ModifyBackupSettingRequest{
		Regions: []*consoleapi.RegionInfoRequest{
			{Name: tidbCluster.Region},
		},
	}
	return v.backupW.ModifyBackupSetting(tidbCluster, req)
}

// GetDifferentSecondaryRegion returns a secondary region from backup options
// that is NOT the excludeRegion. Used to test switching secondary regions.
func (v *BackupValidator) GetDifferentSecondaryRegion(tidbCluster *TiDBDedicatedEntity, excludeRegion string) (string, error) {
	params := console_br_params.NewGetBackupOptionsParamsWithContext(log.WithLogger(context.Background(), v.lgr)).
		WithOrgID(tidbCluster.OrgID).
		WithProjectID(tidbCluster.ProjectID).
		WithClusterID(tidbCluster.ClusterID)

	resp, err := consoleapi.BrBackupServiceClient().GetBackupOptions(params, nil)
	if err != nil {
		return "", err
	}

	if resp.Payload == nil || resp.Payload.DualRegionOptions == nil {
		return "", fmt.Errorf("no dual_region_options for cluster %s", tidbCluster.ClusterID)
	}

	for _, opt := range resp.Payload.DualRegionOptions.RegionOptions {
		if opt != nil && opt.Region != nil && opt.Region.Name != "" && opt.Region.Name != excludeRegion {
			return opt.Region.Name, nil
		}
	}
	return "", fmt.Errorf("no secondary region other than %s available for cluster %s", excludeRegion, tidbCluster.ClusterID)
}

// ExpectCRRFStatus verifies crrf_status from QueryBackupSetting.
func (v *BackupValidator) ExpectCRRFStatus(tidbCluster *TiDBDedicatedEntity, expected string) error {
	resp, err := v.backupW.QueryBackupSetting(tidbCluster)
	if err != nil {
		return err
	}
	if resp.CrrfStatus != expected {
		return fmt.Errorf("expected crrf_status=%s, got %s", expected, resp.CrrfStatus)
	}
	return nil
}
