package ngoperation

import (
	"context"
	"fmt"

	clusterng "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"
	opent "github.com/tidbcloud/devops/entity/operation"
	"github.com/tidbcloud/devops/pkg/service/operation/actions"
	nextgenv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/nextgen/v1"
	mgmtv1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
)

var (
	ActionCreatePlacementPolicy = opent.ActionMetadata{
		Name: "CreatePlacementPolicy",
		Type: opent.ActionTypeAuto,
	}
)

var _ actions.Action = &createPlacementPolicy{}

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

func NewCreatePlacementPolicy(comps *actions.Components) actions.AutoAction {
	return &createPlacementPolicy{comps: comps}
}

func (a *createPlacementPolicy) Metadata() opent.ActionMetadata {
	return ActionCreatePlacementPolicy
}

func (a *createPlacementPolicy) PreCheck(ctx context.Context, instance *opent.Instance) error {
	_, _, err := a.getParam(ctx, instance)
	return err
}

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

	logicalRegion, provider := GetLogicalRegionAndProviderString(rp.GetRegion())
	policy, err := a.comps.MgmtHTTP.NextGenRegionServer(logicalRegion, provider).CreatePlacementPolicy(ctx, req)
	if err != nil {
		return actions.FailedActionResult(err), nil
	}

	outputs := map[opent.OutputName]string{}
	if policy.GetPlacementPolicyId() != "" {
		outputs[opent.OutputPlacementPolicyID] = policy.GetPlacementPolicyId()
	}

	return actions.RunningActionResult(fmt.Sprintf("sent request to create placement policy: %s", placementPolicyIDForMessage(policy, req.GetClusterId())), outputs), nil
}

func (a *createPlacementPolicy) Result(ctx context.Context, instance *opent.Instance) (*actions.ActionResult, error) {
	return actions.SucceededActionResult(nil), nil
}

func (a *createPlacementPolicy) getParam(ctx context.Context, instance *opent.Instance) (*clusterng.CreatePlacementPolicyRequest, *nextgenv1.ResourcePoolProfile, error) {
	req, err := buildCreatePlacementPolicyParam(instance)
	if err != nil {
		return nil, nil, err
	}

	cluster, err := actions.CheckClusterExistence(ctx, a.comps.CoreSvc, instance)
	if err != nil {
		return nil, nil, err
	}
	if cluster.GetDeployType() != mgmtv1.DeployType_DEPLOY_TYPE_NEXTGEN {
		return nil, nil, fmt.Errorf("unsupported deploy type: %s", cluster.GetDeployType().String())
	}

	clusterSharedPoolID := cluster.GetNextGenCluster().GetSharedPoolId()
	if clusterSharedPoolID != "" && clusterSharedPoolID != req.GetSharedPoolId() {
		return nil, nil, fmt.Errorf("resource_pool_id %s does not match cluster %s shared pool %s", req.GetSharedPoolId(), req.GetClusterId(), clusterSharedPoolID)
	}

	rp, err := a.comps.CoreSvc.Mgmt.GetResourcePoolString(ctx, req.GetSharedPoolId())
	if err != nil {
		return nil, nil, err
	}

	return req, rp, nil
}

func buildCreatePlacementPolicyParam(instance *opent.Instance) (*clusterng.CreatePlacementPolicyRequest, error) {
	clusterID, err := actions.GetClusterIDStr(instance)
	if err != nil {
		return nil, err
	}

	sharedPoolID, err := actions.GetResourcePoolID(instance)
	if err != nil {
		return nil, err
	}

	tikvGroupID, ok := instance.GetParam(opent.ParamTiKVGroupID)
	if !ok || tikvGroupID == "" {
		return nil, fmt.Errorf("param %s is required", opent.ParamTiKVGroupID)
	}

	return &clusterng.CreatePlacementPolicyRequest{
		SharedPoolId: sharedPoolID,
		ClusterId:    clusterID,
		TikvGroupId:  tikvGroupID,
	}, nil
}

func placementPolicyIDForMessage(policy *clusterng.PlacementPolicy, fallbackID string) string {
	if policy.GetPlacementPolicyId() != "" {
		return policy.GetPlacementPolicyId()
	}
	return fallbackID
}
