package tcaction

import (
	"context"
	"fmt"
	"strconv"
	"strings"

	"github.com/tidbcloud/core-svc/idl/pbgen/proto/core"
	opent "github.com/tidbcloud/devops/entity/operation"
	"github.com/tidbcloud/devops/pkg/service/operation/actions"
	"github.com/tidbcloud/devops/pkg/service/operation/repo"

	apiv1 "github.com/tidbcloud/infra-provider/api/tidbcloud/cluster-agent/v1"
)

var (
	ActionPauseTC opent.ActionMetadata = opent.ActionMetadata{
		Name: "PauseTC",
		Type: opent.ActionTypeAuto,
	}

	allShutdownOperations = []string{
		opent.OperationTypeShutdownOperationForClusterVersionUpgrade,
		opent.OperationTypeShutdownOperationForClusterConfigChange,
		opent.OperationTypeShutdownOperationForClusterScaleUpDown,
		opent.OperationTypeRecreateMemberForCluster,
		opent.OperationTypeReplaceNodeForCluster,
		opent.OperationTypeOfflineAzForCluster,
		opent.OperationTypeRecoverAzForCluster,
		opent.OperationTypeOfflineAzWithZonalShiftForCluster,
		opent.OperationTypeRecoverAzWithStopZonalShiftForCluster,
	}
)

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

func NewPauseTC(comps *actions.Components) actions.AutoAction {
	return &pauseCluster{comps: comps}
}

func (a *pauseCluster) Metadata() opent.ActionMetadata {
	return ActionPauseTC
}

func (a *pauseCluster) PreCheck(ctx context.Context, instance *opent.Instance) error {
	cid, err := actions.GetClusterID(instance)
	if err != nil {
		return err
	}
	cidStr := strconv.FormatUint(cid, 10)

	// just check the cluster existence
	cluster, err := a.comps.CoreSvc.GetCluster(ctx, cid, false)
	if err != nil {
		return err
	}
	if cluster.Status != core.ClusterStatus_CLUSTER_STATUS_NORMAL {
		return fmt.Errorf("cluster %d is not normal", cid)
	}

	// PauseTC Action is the first Action of all Shutdown Operations, so check
	// 'one shutdown operation one time' here.
	cnt, err := a.comps.OpRepo.CountInstance(ctx,
		repo.InstanceOpts.IDNotIn([]uint64{instance.ID}),
		repo.InstanceOpts.TypeIs(opent.OperationTargetCluster),
		repo.InstanceOpts.TargetIDIn(opent.OperationTargetCluster, []string{cidStr}),
		repo.InstanceOpts.PhaseNotIn([]opent.InstancePhase{opent.InstancePhaseSucceeded, opent.InstancePhaseFailed, opent.InstancePhaseCanceled}),
		repo.InstanceOpts.OperationIn(allShutdownOperations), // todo(chenfei): use category to filter?
	)
	if err != nil {
		return err
	}
	if cnt != 0 {
		return fmt.Errorf("there are %d instances running or pending in this cluster, cid=%s", cnt, cidStr)
	}

	return nil
}

func (a *pauseCluster) Start(ctx context.Context, instance *opent.Instance) (*actions.ActionResult, error) {
	req, err := a.getParam(instance)
	if err != nil {
		return actions.FailedActionResult(err), nil
	}

	resp, err := a.comps.MgmtHTTP.ClusterAgent(req.ClusterId).PauseTC(ctx, req)
	if err != nil {
		return nil, err
	}

	output := map[opent.OutputName]string{
		opent.OutputPausedTCNames: strings.Join(resp.PausedTcNames, ", "),
	}
	return actions.RunningActionResult("sent request to pause TC(s)", output), nil
}

func (a *pauseCluster) Result(ctx context.Context, instance *opent.Instance) (*actions.ActionResult, error) {
	param, err := a.getParam(instance)
	if err != nil {
		return nil, err
	}

	tcs, err := a.comps.MgmtHTTP.ClusterAgent(param.ClusterId).ListTCs(ctx)
	if err != nil {
		return actions.NewActionResult(opent.ActionStatusUnknown, err.Error(), nil), nil
	}

	var unpausedTCs []string
	for _, each := range tcs {
		if !each.Paused {
			unpausedTCs = append(unpausedTCs, each.K8SMetadata.Name)
		}
	}
	if len(unpausedTCs) != 0 {
		return actions.RunningActionResult(fmt.Sprintf("unpaused tcs: %v", unpausedTCs), nil), nil
	}

	return actions.SucceededActionResult(nil), nil
}

func (a *pauseCluster) getParam(instance *opent.Instance) (*apiv1.PauseTCReq, error) {
	cid, err := actions.GetClusterID(instance)
	if err != nil {
		return nil, err
	}

	p := &apiv1.PauseTCReq{
		ClusterId: strconv.FormatUint(cid, 10),
		All:       true,
		TcNames:   nil,
	}
	return p, nil
}
