package ngoperation

import (
	"context"
	"encoding/json"
	"fmt"
	"strconv"

	opent "github.com/tidbcloud/devops/entity/operation"
	"github.com/tidbcloud/devops/pkg/service/operation/actions"

	clusterng "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"
	eventent "github.com/tidbcloud/devops/entity/event"
	evbus "github.com/tidbcloud/eventbus-datastruct/event"
	nextgenv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/nextgen/v1"
)

var (
	ActionScaleResourcePoolQuota opent.ActionMetadata = opent.ActionMetadata{
		Name: "ScaleNextGenResourcePoolQuota",
		Type: opent.ActionTypeAuto,
	}
)

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

func NewScaleResourcePoolQuota(comps *actions.Components) actions.AutoAction {
	return &scaleResourcePoolQuota{comps: comps}
}

func (a *scaleResourcePoolQuota) Metadata() opent.ActionMetadata {
	return ActionScaleResourcePoolQuota
}

func (a *scaleResourcePoolQuota) PreCheck(ctx context.Context, instance *opent.Instance) error {
	req, rp, currentMax, currentMin, err := buildScaleQuotaParam(ctx, a.comps, instance, "")
	if err != nil {
		return err
	}
	if err := validateScaleResourcePoolTarget(req.ComponentType.String(), req.SharedPoolId, rp); err != nil {
		return err
	}

	instance.Params[opent.ParamScaleDirection] = computeQuotaScaleDirection(currentMax, currentMin, req.MaxNodeCount, req.MinNodeCount)
	return nil
}

func (a *scaleResourcePoolQuota) Start(ctx context.Context, instance *opent.Instance) (*actions.ActionResult, error) {
	req, rp, _, _, err := buildScaleQuotaParam(ctx, a.comps, instance, "")
	if err != nil {
		return actions.FailedActionResult(err), nil
	}
	if err := validateScaleResourcePoolTarget(req.ComponentType.String(), req.SharedPoolId, rp); err != nil {
		return actions.FailedActionResult(err), nil
	}

	sharedPoolID, err := actions.GetResourcePoolID(instance)
	if err != nil {
		a.comps.Logger.Error(err, "get resource pool id failed for scale resource pool quota event")
		return actions.SucceededActionResult(nil), nil
	}
	rp, sp, err := GetResourcePoolAndSharedPoolWithRetry(ctx, sharedPoolID, a.comps.CoreSvc, a.comps.MgmtHTTP, 3)
	if err != nil {
		a.comps.Logger.Error(err, "get resource pool and shared pool failed for scale resource pool quota event")
		return actions.SucceededActionResult(nil), nil
	}

	logicalRegion, provider := GetLogicalRegionAndProviderString(rp.GetRegion())
	_, err = a.comps.MgmtHTTP.NextGenRegionServer(logicalRegion, provider).ScaleSharedPoolQuota(ctx, instance.Params[opent.ParamResourcePoolID], req)
	if err != nil {
		return actions.FailedActionResult(err), nil
	}

	// send event
	var before []byte
	for _, comp := range sp.ComponentGroups {
		if comp.ComponentType == req.ComponentType.String() && comp.Id == req.Id {
			before, _ = json.Marshal(comp.NodeQuota)
		}
	}
	after, _ := json.Marshal(req)
	displayEv, err := assembleResourcePoolEvent(string(eventent.ModifyResourcePoolQuotaEvent),
		evbus.EventStatusSucceeded,
		instance.Applicant,
		rp,
		sp,
		string(eventent.ModifyResourcePoolQuotaSummary),
		strconv.FormatUint(rp.ResourcePool.Id, 10),
		clusterng.ComponentType_name[int32(req.ComponentType)],
		string(before),
		string(after),
	)
	if err != nil {
		a.comps.Logger.Error(err, "assemble scale resource pool quota event failed")
		return actions.SucceededActionResult(nil), nil
	}
	err = a.comps.EventBus.DBaaSDisplayEvent.Publish(displayEv)
	if err != nil {
		a.comps.Logger.Error(err, "publish scale resource pool quota event failed")
	}

	return actions.SucceededActionResult(nil), nil
}

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

func buildScaleQuotaParam(ctx context.Context, deps *actions.Components, instance *opent.Instance, defComponentType string) (*clusterng.SetSharedPoolComponentQuotaRequest, *nextgenv1.ResourcePoolProfile, uint32, uint32, error) {
	sharedPoolID, err := actions.GetResourcePoolID(instance)
	if err != nil {
		return nil, nil, 0, 0, err
	}
	componentID, ok := instance.GetParam(opent.ParamComponentID)
	if !ok || componentID == "" {
		return nil, nil, 0, 0, fmt.Errorf("param component_id is required")
	}
	componentType := defComponentType
	if componentType == "" {
		componentType, ok = instance.GetParam(opent.ParamComponentType)
		if !ok || componentType == "" {
			return nil, nil, 0, 0, fmt.Errorf("param component_type is required")
		}
	}

	req := &clusterng.SetSharedPoolComponentQuotaRequest{
		SharedPoolId:  sharedPoolID,
		Id:            componentID,
		ComponentType: clusterng.ComponentType(clusterng.ComponentType_value[componentType]),
	}

	// current resource pool quota
	resourcePool, err := deps.CoreSvc.Mgmt.GetResourcePoolString(ctx, sharedPoolID)
	if err != nil {
		return nil, nil, 0, 0, err
	}
	logicalRegion, provider := GetLogicalRegionAndProviderString(resourcePool.GetRegion())
	resourcePoolDetail, err := deps.MgmtHTTP.NextGenRegionServer(logicalRegion, provider).GetSharedPool(ctx, sharedPoolID)
	if err != nil {
		return nil, nil, 0, 0, err
	}
	found := false
	var currentMax, currentMin uint32
	for _, comp := range resourcePoolDetail.ComponentGroups {
		if comp.Id == componentID && comp.ComponentType == componentType {
			found = true
			currentMax = comp.NodeQuota.MaxNodeCount
			currentMin = comp.NodeQuota.MinNodeCount
			req.MaxNodeCount = currentMax // default to current quota
			req.MinNodeCount = currentMin // default to current quota
			break
		}
	}
	if !found {
		return nil, nil, 0, 0, fmt.Errorf("component %s of type %s is not found in resource pool", componentID, componentType)
	}

	// max node count
	maxNodeCountStr, _ := instance.GetParam(opent.ParamMaxNodeCount)
	if maxNodeCountStr != "" {
		nodeCount, err := strconv.Atoi(maxNodeCountStr)
		if err != nil {
			return nil, nil, 0, 0, fmt.Errorf("invalid max_node_count: %s", maxNodeCountStr)
		}
		req.MaxNodeCount = uint32(nodeCount)
	}
	// min node count
	minNodeCountStr, _ := instance.GetParam(opent.ParamMinNodeCount)
	if minNodeCountStr != "" {
		nodeCount, err := strconv.Atoi(minNodeCountStr)
		if err != nil {
			return nil, nil, 0, 0, fmt.Errorf("invalid min_node_count: %s", minNodeCountStr)
		}
		req.MinNodeCount = uint32(nodeCount)
	}

	if maxNodeCountStr == "" && minNodeCountStr == "" {
		return nil, nil, 0, 0, fmt.Errorf("at least one of max_node_count or min_node_count should be set")
	}
	if req.MaxNodeCount < req.MinNodeCount {
		return nil, nil, 0, 0, fmt.Errorf("max_node_count %d cannot be less than min_node_count %d", req.MaxNodeCount, req.MinNodeCount)
	}

	return req, resourcePool, currentMax, currentMin, nil
}
