package backup

import (
	"fmt"
	"time"

	. "github.com/tidbcloud/dataflow-service-e2e/cases"
	"github.com/tidbcloud/dataflow-service-e2e/cases/dedicated/common"
	"github.com/tidbcloud/dataflow-service-e2e/internal/config"
	. "github.com/tidbcloud/dataflow-service-e2e/internal/model"
	"github.com/tidbcloud/dataflow-service-e2e/internal/tidbcloud/dedicated/consoleapi"
	br_model "github.com/tidbcloud/dataflow-service-e2e/internal/tidbcloud/dedicated/consoleapi/br/model"
	"github.com/tidbcloud/dataflow-service-e2e/internal/tidbcloud/dedicated/consoleapi/cluster/client/cluster_service"
	"github.com/tidbcloud/dataflow-service-e2e/internal/tidbcloud/dedicated/consoleapi/cluster_non_gen"
)

// snapshotRestoreSmokeCase tests snapshot restore functionality for a TiDB Dedicated cluster.
// It covers:
// - Use an existing backup or create one
// - Trigger a restore to create a new cluster
// - Wait for restore to complete
// - Verify restore status
type snapshotRestoreSmokeCase struct {
	BaseCase
	backupValidator   *common.BackupValidator
	clusterApiWrapper *cluster_non_gen.ClusterClient
	cloudProvider     CloudProviderType
}

// NewSnapshotRestoreSmokeCase creates a new snapshot restore smoke test case.
func NewSnapshotRestoreSmokeCase(cloudProvider CloudProviderType) E2ECase {
	return &snapshotRestoreSmokeCase{cloudProvider: cloudProvider}
}

func (m *snapshotRestoreSmokeCase) Name() string {
	return fmt.Sprintf("%sSnapshotRestoreSmoke", m.cloudProvider)
}

func (m *snapshotRestoreSmokeCase) Tags() map[string]string {
	return map[string]string{TagKeyDomain: TagValueDomainBR}
}

func (m *snapshotRestoreSmokeCase) DeclareDependencyRequirements() map[string]DependencyRequirement {
	return map[string]DependencyRequirement{
		"tidb": &TiDBDedicatedRequirement{
			Dedicated: true,
			TiDBDedicatedDimensions: TiDBDedicatedDimensions{
				CloudProvider: m.cloudProvider,
				Region:        config.GlobalConfig.DefaultRegionOf(m.cloudProvider),
			},
		},
	}
}

func (m *snapshotRestoreSmokeCase) Run() {
	const (
		backupTimeout  = 30 * time.Minute
		restoreTimeout = 60 * time.Minute
		retentionDays  = 1
	)

	tidbCluster := m.TiDBDedicated("tidb")
	restoreClusterName := GenerateClusterName("e2e-restore")

	// Initialize validator
	m.backupValidator = common.NewBackupValidator(m.Logger())
	m.clusterApiWrapper = cluster_non_gen.NewClient(m.Logger())

	var backupID uint64
	var restoreID uint64
	var restoredClusterID string
	var createdBackup bool

	// Cleanup
	defer func() {
		// Clean up restored cluster
		if restoredClusterID != "" {
			m.RetryDo("cleanup: delete restored cluster", func() error {
				m.Logger().Info("deleting restored cluster with ID: %s", restoredClusterID)
				return m.clusterApiWrapper.DeleteCluster(tidbCluster.OrgID, tidbCluster.ProjectID, restoredClusterID)
			}, 3, time.Minute)
		} else if restoreClusterName != "" {
			// Fallback cleanup by name if ID is missing (e.g. restore failed early)
			m.RetryDo("cleanup: delete restored cluster by name", func() error {
				cli := consoleapi.ClusterClient().ClusterService
				pageSize := int32(100)
				params := cluster_service.NewClusterServiceListClustersParams().
					WithProjectID(tidbCluster.ProjectID).
					WithPageSize(&pageSize)

				resp, err := cli.ClusterServiceListClusters(params)
				if err != nil {
					return err
				}

				var clusterID string
				for _, c := range resp.Payload.Clusters {
					if c.DisplayName != nil && *c.DisplayName == restoreClusterName {
						clusterID = c.ClusterID
						break
					}
				}

				if clusterID != "" {
					m.Logger().Info("found restored cluster %s (id: %s), deleting it", restoreClusterName, clusterID)
					return m.clusterApiWrapper.DeleteCluster(tidbCluster.OrgID, tidbCluster.ProjectID, clusterID)
				}

				m.Logger().Info("restored cluster %s not found, skip deletion", restoreClusterName)
				return nil
			}, 3, time.Minute)
		}
		// Delete backup if we created it
		if createdBackup && backupID > 0 {
			m.RetryDo("cleanup: delete test backup", func() error {
				return m.backupValidator.Client().DeleteBackup(tidbCluster, backupID)
			}, 3, time.Minute)
		}
	}()

	m.
		// Step 1: Get or create a backup to restore from
		Step("get or create backup for restore", func() error {
			// Try to get the latest successful backup
			resp, err := m.backupValidator.Client().ListBackups(tidbCluster, 1, 10)
			if err != nil {
				return err
			}

			// Find a completed backup
			for _, backup := range resp.Items {
				if backup.Status == string(br_model.DataflowBackupExportStatusSUCCESS) {
					backupID = backup.Id
					m.Logger().Info("using existing backup %d(%s) for restore", backupID, backup.Name)
					return nil
				}
			}

			// No suitable backup found, create one
			m.Logger().Info("no suitable backup found, creating a new one")
			req := &consoleapi.CreateBackupScheduleRequest{
				Name:            GenerateBackupName("e2e-restore-backup"),
				Description:     "E2E test backup for restore",
				BackupType:      "MANUAL",
				BackupTime:      time.Now().Format(time.RFC3339),
				BackupMode:      "SNAPSHOT",
				RetentionPeriod: retentionDays,
			}
			createResp, err := m.backupValidator.Client().CreateBackupSchedule(tidbCluster, req)
			if err != nil {
				return err
			}
			backupID = createResp.Id
			createdBackup = true
			m.Logger().Info("created backup %d(%s) for restore test", backupID, req.Name)
			return nil
		}).

		// Step 2: Wait for backup to be ready if we created one
		Step("ensure backup is ready", func() error {
			if createdBackup {
				return m.backupValidator.WaitForBackupComplete(tidbCluster, backupID, backupTimeout)
			}
			return nil
		}).

		// Step 3: Trigger restore from backup
		RetryDo("trigger restore from snapshot backup", func() error {
			req := &consoleapi.RestoreBackupRequest{
				ClusterName: restoreClusterName,
				Region:      tidbCluster.Region,
				Description: "E2E test restored cluster",
				Password:    DefaultClusterPassword,
				RestoreMode: string(br_model.DataflowRestoreModeRESTOREMODESNAPSHOT),
				Port:        DefaultClusterPort,
				Components:  BuildMinimalClusterComponentsForRegion(m.clusterApiWrapper, tidbCluster.OrgID, tidbCluster.ProjectID, string(m.cloudProvider), tidbCluster.Region),
			}
			resp, err := m.backupValidator.Client().RestoreBackup(tidbCluster, backupID, req)
			if err != nil {
				return err
			}
			restoreID = resp.Id
			restoredClusterID = resp.ClusterId
			m.Logger().Info("triggered restore with ID: %d, target cluster: %s (id: %s)", restoreID, restoreClusterName, restoredClusterID)
			return nil
		}, 3, time.Minute).

		// Step 4: Verify restore appears in list
		Expect("restore is in list", func() error {
			resp, err := m.backupValidator.Client().ListRestores(tidbCluster, 1, 100)
			if err != nil {
				return err
			}
			for _, r := range resp.Items {
				if r.Id == restoreID {
					m.Logger().Info("restore %d found in list, status: %s", restoreID, r.Status)
					return nil
				}
			}
			return fmt.Errorf("restore %d not found in list", restoreID)
		}, 5, time.Second*30).

		// Step 5: Get restore progress
		Step("check restore progress", func() error {
			resp, err := m.backupValidator.Client().GetRestoreProgress(tidbCluster)
			if err != nil {
				m.Logger().Warn("failed to get restore progress: %s", err.Error())
				// This is informational, don't fail the test
				return nil
			}
			m.Logger().Info("restore progress: status=%s, steps=%d",
				resp.RestoreStatus, len(resp.Steps))
			return nil
		}).

		// Step 6: Wait for restore to complete
		Step("wait for restore to complete", func() error {
			return m.backupValidator.WaitForRestoreComplete(tidbCluster, restoreID, restoreTimeout)
		})
}
