package clusteraction

import (
	"context"
	"fmt"
	"strconv"
	"time"

	"k8s.io/apimachinery/pkg/util/wait"

	"github.com/tidbcloud/core-svc/idl/pbgen/proto/core"
	opent "github.com/tidbcloud/devops/entity/operation"
	"github.com/tidbcloud/devops/pkg/service/operation/actions"
	apiv1 "github.com/tidbcloud/infra-provider/api/tidbcloud/cluster-server/v1"
)

const (
	// Retry configuration for CancelScaleCluster API calls
	maxRetries    = 5
	retryInterval = 1 * time.Second
)

var (
	ActionCancelScaleCluster opent.ActionMetadata = opent.ActionMetadata{
		Name: "CancelScaleCluster",
		Type: opent.ActionTypeAuto,
	}

	// cancelScaleBackoff defines the retry strategy for CancelScaleCluster API calls.
	// It uses a fixed interval retry (not exponential) with the following parameters:
	//   - Steps: Maximum number of retry attempts (5 times)
	//   - Duration: Fixed delay between retries (1 second)
	//   - Factor: 1 (no exponential backoff, fixed interval)
	//   - Cap: Maximum delay cap (1 second, same as Duration)
	// Retry timeline: 1s -> 1s -> 1s -> 1s -> 1s (fixed interval)
	cancelScaleBackoff = wait.Backoff{
		Steps:    maxRetries,
		Duration: retryInterval,
		Factor:   1,
		Cap:      retryInterval,
	}
)

type cancelScaleCluster struct {
	actions.BaseAction
	comps *actions.Components
}

func NewCancelScaleCluster(comps *actions.Components) actions.AutoAction {
	return &cancelScaleCluster{comps: comps}
}

func (c *cancelScaleCluster) Metadata() opent.ActionMetadata {
	return ActionCancelScaleCluster
}

func (c *cancelScaleCluster) PreCheck(ctx context.Context, instance *opent.Instance) error {
	return nil
}

func (c *cancelScaleCluster) Start(ctx context.Context, instance *opent.Instance) (*actions.ActionResult, error) {
	cid, err := actions.GetClusterID(instance)
	if err != nil {
		return actions.FailedActionResult(err), nil
	}
	cidStr := strconv.FormatUint(cid, 10)

	req := &apiv1.CancelScaleClusterRequest{
		ClusterId: cidStr,
		Reason:    "",
	}

	// Implement retry logic for CancelScaleCluster
	var cancelErr error
	err = wait.ExponentialBackoff(cancelScaleBackoff, func() (bool, error) {
		cancelErr = c.comps.MgmtHTTP.ClusterServer(cidStr).CancelScaleCluster(ctx, req)
		if cancelErr != nil {
			c.comps.Logger.Info("failed to cancel scale cluster, will retry",
				"cluster_id", cidStr,
				"error", cancelErr)
			return false, nil // Don't abort retries
		}
		return true, nil
	})

	if err != nil {
		c.comps.Logger.Error(cancelErr, "failed to cancel scale cluster after all retries",
			"cluster_id", cidStr)
		return actions.FailedActionResult(cancelErr), nil
	}

	return actions.RunningActionResult("sent request to cancel scale cluster", nil), nil
}

func (c *cancelScaleCluster) Result(ctx context.Context, instance *opent.Instance) (*actions.ActionResult, error) {
	cid, err := actions.GetClusterID(instance)
	if err != nil {
		return nil, err
	}

	cluster, err := c.comps.CoreSvc.GetCluster(ctx, cid, false)
	if err != nil {
		return actions.NewActionResult(opent.ActionStatusUnknown, err.Error(), nil), nil
	}

	// Wait until the cluster is back to NORMAL, meaning scaling (and its cancellation) has been fully synced.
	if cluster.Status == core.ClusterStatus_CLUSTER_STATUS_NORMAL {
		return actions.SucceededActionResult(nil), nil
	}

	return actions.RunningActionResult(fmt.Sprintf("cluster status is '%s'", cluster.Status), nil), nil
}
