package backup

import (
	"context"
	"errors"
	"fmt"
	"strings"
	"time"

	"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/model"
	"github.com/tidbcloud/dataflow-service-e2e/internal/tidbcloud/essential"
)

func cleanupRestoreTarget(c *cases.BaseCase, source *TiDBServerlessEntity, targetID string) {
	if targetID == "" {
		return
	}
	client := essential.NewClusterClient(c.Logger())
	deleted := false
	c.RetryDo("delete restore target", func() error {
		view, err := client.DeleteWithView(source.OrgID, source.ProjectID, targetID)
		if err != nil {
			return err
		}
		// A successful DELETE response is authoritative when it carries the
		// terminal state. Some restore targets return 403 to a subsequent GET
		// while their deleted record is being removed, so do not reinterpret
		// that authorization response as absence.
		deleted = view == nil || view.State == "DELETED"
		return nil
	}, 3, time.Minute)
	if deleted {
		return
	}
	c.Expect("restore target deleted", func() error {
		_, err := client.Get(source.OrgID, source.ProjectID, targetID)
		if err == nil {
			return fmt.Errorf("restore target %s still exists", targetID)
		}
		if essential.IsNotFound(err) {
			return nil
		}
		return err
	}, 30, 30*time.Second)
}

func waitRestoreTargetActive(c *cases.BaseCase, source *TiDBServerlessEntity, targetID string) error {
	target, err := essential.NewClusterClient(c.Logger()).Get(source.OrgID, source.ProjectID, targetID)
	if err != nil {
		return err
	}
	if target.State != "ACTIVE" {
		return fmt.Errorf("restore target state is %q", target.State)
	}
	if err := common.ValidateRestoreTarget(target.ServicePlan, target.Provider, string(source.CloudProvider), target.MinRCU, target.MaxRCU); err != nil {
		return err
	}
	if normalizeRegion(target.Region, target.Provider) != normalizeRegion(source.Region, string(source.CloudProvider)) {
		return fmt.Errorf("restore target region/provider is not %s/%s", source.CloudProvider, source.Region)
	}
	return nil
}

func normalizeRegion(region, provider string) string {
	return strings.TrimPrefix(strings.ToLower(region), strings.ToLower(provider)+"-")
}

func setRestoreTargetPassword(c *cases.BaseCase, source *TiDBServerlessEntity, targetID, password string) error {
	err := essential.NewClusterClient(c.Logger()).ChangeRootPassword(source.OrgID, source.ProjectID, targetID, password)
	if err == nil || errors.Is(err, context.DeadlineExceeded) {
		return err
	}
	return classifyRestoreRetry(err)
}

// classifyRestoreRetry keeps network failures and server-side failures
// retryable. Only known non-transient client errors fail immediately; 408,
// 425, and 429 remain retryable because they commonly represent throttling or
// a temporarily unavailable control-plane operation.
func classifyRestoreRetry(err error) error {
	status, ok := essential.HTTPStatus(err)
	if !ok || status >= 500 || status < 400 || status == 408 || status == 425 || status == 429 {
		return err
	}
	return retry.Unrecoverable(err)
}

func restoreTargetEntity(c *cases.BaseCase, source *TiDBServerlessEntity, targetID, password string) (*TiDBServerlessEntity, error) {
	target, err := essential.NewClusterClient(c.Logger()).Get(source.OrgID, source.ProjectID, targetID)
	if err != nil {
		return nil, err
	}
	return essential.ServerlessClusterEntity(source, target, password)
}
