package backup

import (
	"fmt"
	"strings"
	"time"

	retry "github.com/avast/retry-go/v4"
	"github.com/tidbcloud/dataflow-service-e2e/cases"
	"github.com/tidbcloud/dataflow-service-e2e/cases/essential/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/essential"
)

type SnapshotRestoreSmokeCase struct {
	cases.BaseCase
	provider CloudProviderType
}

func NewSnapshotRestoreSmokeCase(provider CloudProviderType) cases.E2ECase {
	return &SnapshotRestoreSmokeCase{provider: provider}
}
func (c *SnapshotRestoreSmokeCase) Name() string {
	return fmt.Sprintf("%sEssentialV1SnapshotRestoreSmoke", c.provider)
}
func (c *SnapshotRestoreSmokeCase) Tags() map[string]string {
	return map[string]string{cases.TagKeyDomain: config.DomainBR, cases.TagKeyAPIModes: common.APIConsoleOnly}
}
func (c *SnapshotRestoreSmokeCase) DeclareDependencyRequirements() map[string]DependencyRequirement {
	region := config.GlobalConfig.DefaultRegionOf(c.provider)
	return map[string]DependencyRequirement{
		"essential": &TiDBServerlessRequirement{
			Dedicated: true,
			TiDBServerlessDimensions: TiDBServerlessDimensions{
				CloudProvider: c.provider,
				Region:        region,
				ServicePlan:   essential.ServicePlanEssential,
			},
		},
	}
}

func (c *SnapshotRestoreSmokeCase) Run() {
	source := c.TiDBServerless("essential")
	c.Step("verify Essential v1 cluster", func() error { return common.ValidateCluster(source) })
	ctx, br := c.Context(), essential.NewBRClient()
	baseline, marker := c.Name()+"-baseline", c.Name()+"-marker"
	restoreName := c.Name() + "-" + common.UniqueRunSuffix()
	known := map[string]struct{}{}
	var point time.Time
	var backupID, targetID string
	defer func() { cleanupRestoreTarget(&c.BaseCase, source, targetID) }()
	c.Step("record existing backups", func() error {
		backups, err := br.ListAllBackups(ctx, source, 100)
		if err != nil {
			return err
		}
		for _, backup := range backups {
			known[backup.ID] = struct{}{}
		}
		return nil
	})
	c.Step("write restore baseline", func() error {
		ts, err := common.WriteMarker(source, baseline)
		point = time.Unix(ts, 0).UTC()
		return err
	})
	c.Expect("automatic backup follows baseline", func() error {
		backups, err := br.ListAllBackups(ctx, source, 100)
		if err != nil {
			return err
		}
		for _, backup := range backups {
			if _, exists := known[backup.ID]; exists || backup.BackupTime.IsZero() || !backup.BackupTime.After(point) {
				continue
			}
			if strings.EqualFold(backup.State, "SUCCEEDED") {
				backupID = backup.ID
				return nil
			}
			if strings.EqualFold(backup.State, "FAILED") || strings.EqualFold(backup.State, "CANCELED") {
				return retry.Unrecoverable(fmt.Errorf("automatic backup %s failed", backup.ID))
			}
		}
		return fmt.Errorf("no successful automatic backup after %s", point.Format(time.RFC3339))
	}, 120, 30*time.Second)
	c.Step("write restore marker", func() error { _, err := common.WriteMarker(source, marker); return err })
	c.Step("submit snapshot restore", func() error {
		var err error
		targetID, err = br.Restore(ctx, source, backupID, restoreName, "SnapshotRestore", point)
		return err
	})
	c.Expect("snapshot restore completes", func() error {
		state, err := br.RestoreState(ctx, source, targetID)
		if err != nil {
			return err
		}
		return common.ValidateRestoreState(state)
	}, 120, 30*time.Second)
	c.Expect("snapshot restore target is active", func() error { return waitRestoreTargetActive(&c.BaseCase, source, targetID) }, 60, 30*time.Second)
	var password string
	c.Step("generate snapshot restore password", func() error { var err error; password, err = common.GenerateRestorePassword(); return err })
	c.RetryDo("set snapshot restore target password", func() error { return setRestoreTargetPassword(&c.BaseCase, source, targetID, password) }, 5, 15*time.Second)
	c.Step("verify snapshot restored baseline and absent marker", func() error {
		target, err := restoreTargetEntity(&c.BaseCase, source, targetID, password)
		if err != nil {
			return err
		}
		return common.VerifyRestoreData(target, baseline, marker)
	})
}
