package ngoperation

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

	eventent "github.com/tidbcloud/devops/entity/event"
	opent "github.com/tidbcloud/devops/entity/operation"
	"github.com/tidbcloud/devops/pkg/infra/clients/mgmthttpcli"
	"github.com/tidbcloud/devops/pkg/infra/log"
	"github.com/tidbcloud/devops/pkg/service/operation/actions"
	evbus "github.com/tidbcloud/eventbus-datastruct/event"
)

var (
	ActionScaleNextGenTiDBGroup opent.ActionMetadata = opent.ActionMetadata{
		Name: "ScaleNextGenTiDBGroup",
		Type: opent.ActionTypeAuto,
	}
)

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

func NewScaleNextGenTiDBGroup(comps *actions.Components) actions.AutoAction {
	return &scaleNextGenTiDBGroup{comps: comps}
}

func (a *scaleNextGenTiDBGroup) Metadata() opent.ActionMetadata {
	return ActionScaleNextGenTiDBGroup
}

func (a *scaleNextGenTiDBGroup) Advisory(ctx context.Context, instance *opent.Instance) ([]opent.Advisory, error) {
	req, cid, tidbGroupID, err := a.getParam(instance)
	if err != nil {
		return nil, err
	}

	target := actions.ScaleTarget{ComponentType: "TiDB Group"}
	if req.NodeQuota != nil {
		target.TargetNodeCount = req.NodeQuota.NodeCount
		if req.NodeQuota.NodeSpec != nil {
			target.TargetCPUCores = req.NodeQuota.NodeSpec.Cpu
			target.TargetMemoryGi = req.NodeQuota.NodeSpec.MemoryGi
		}
	}

	tidbGroup, err := a.comps.MgmtHTTP.NextGen().GetTiDBGroup(ctx, cid, tidbGroupID)
	if err == nil && tidbGroup.NodeQuota != nil {
		target.CurrentNodeCount = tidbGroup.NodeQuota.NodeCount
		if tidbGroup.NodeQuota.NodeSpec != nil {
			target.CurrentCPUCores = tidbGroup.NodeQuota.NodeSpec.Cpu
			target.CurrentMemoryGi = tidbGroup.NodeQuota.NodeSpec.MemoryGi
		}
	}

	return actions.ClusterScaleAdvisory(ctx, log.FromContextOrDef(ctx, a.comps.Logger), a.comps.ClinicCli, a.comps.GeminiCli, cid, target)
}

func (a *scaleNextGenTiDBGroup) PreCheck(ctx context.Context, instance *opent.Instance) error {
	req, cid, tidbGroupID, err := a.getParam(instance)
	if err != nil {
		return err
	}

	tidbGroup, err := a.comps.MgmtHTTP.NextGen().GetTiDBGroup(ctx, cid, tidbGroupID)
	if err != nil {
		return err
	}

	var currentSpec *mgmthttpcli.NodeSpec
	if tidbGroup.NodeQuota != nil {
		currentSpec = tidbGroup.NodeQuota.NodeSpec
	}
	var currentCount uint32
	if tidbGroup.NodeQuota != nil {
		currentCount = tidbGroup.NodeQuota.NodeCount
	}
	instance.Params[opent.ParamScaleDirection] = computeNodeScaleDirection(
		currentCount, req.NodeQuota.NodeCount,
		currentSpec, req.NodeQuota.NodeSpec,
	)
	return nil
}

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

	tidbGroup, err := GetTiDBGroupWithRetry(ctx, cid, groupID, a.comps.MgmtHTTP, 3)
	if err != nil {
		return actions.FailedActionResult(err), nil
	}
	_, err = a.comps.MgmtHTTP.NextGen().ScaleTiDBGroup(ctx, cid, groupID, req)
	if err != nil {
		return actions.FailedActionResult(err), nil
	}
	// send event
	beforeBytes, _ := json.Marshal(tidbGroup.NodeQuota)
	afterBytes, _ := json.Marshal(req.NodeQuota)

	displayEv, err := assembleTiDBEvent(
		string(eventent.ScaleInstanceTiDBEvent),
		evbus.EventStatusStarted,
		instance.Applicant,
		tidbGroup,
		string(eventent.ScaleInstanceTiDBCreatedSummary),
		tidbGroup.SharedPoolID,
		tidbGroup.ClusterID,
		tidbGroup.Id,
		string(beforeBytes),
		string(afterBytes),
	)
	if err != nil {
		a.comps.Logger.Error(err, "assemble scale TiDB group event failed")
		return actions.RunningActionResult("sent request to scale TiDB group", nil), nil
	}
	err = a.comps.EventBus.DBaaSDisplayEvent.Publish(displayEv)
	if err != nil {
		a.comps.Logger.Error(err, "publish scale TiDB group event failed")
	}

	return actions.RunningActionResult("sent request to scale TiDB group", nil), nil
}

func (a *scaleNextGenTiDBGroup) Result(ctx context.Context, instance *opent.Instance) (*actions.ActionResult, error) {
	cid, err := actions.GetClusterIDStr(instance)
	if err != nil {
		return nil, err
	}
	tidbGroupID, hasGroupID := instance.GetParam(opent.ParamTiDBGroupID)
	if !hasGroupID || tidbGroupID == "" {
		return nil, fmt.Errorf("param tidb_group_id is required")
	}

	tidbGroup, err := GetTiDBGroupWithRetry(ctx, cid, tidbGroupID, a.comps.MgmtHTTP, 3)
	if err != nil {
		return actions.NewActionResult(opent.ActionStatusUnknown, err.Error(), nil), nil
	}

	if tidbGroup.Phase == "AVAILABLE" {
		displayEv, err := assembleTiDBEvent(
			string(eventent.ScaleInstanceTiDBEvent),
			evbus.EventStatusSucceeded,
			instance.Applicant,
			tidbGroup,
			string(eventent.ScaleInstanceTiDBSucceededSummary),
			tidbGroup.SharedPoolID,
			tidbGroup.ClusterID,
			tidbGroup.Id,
		)
		if err != nil {
			a.comps.Logger.Error(err, "assemble scale TiDB group succeeded event failed")
		} else {
			err = a.comps.EventBus.DBaaSDisplayEvent.Publish(displayEv)
			if err != nil {
				a.comps.Logger.Error(err, "publish scale TiDB group succeeded event failed")
			}
		}
		return actions.SucceededActionResult(nil), nil
	}
	return actions.RunningActionResult(fmt.Sprintf("TiDB group phase is '%s'", tidbGroup.Phase), nil), nil

}

func (a *scaleNextGenTiDBGroup) getParam(instance *opent.Instance) (*mgmthttpcli.ScaleTiDBGroupRequest, string, string, error) {
	cid, err := actions.GetClusterIDStr(instance)
	if err != nil {
		return nil, "", "", err
	}

	tidbGroupID, hasGroupID := instance.GetParam(opent.ParamTiDBGroupID)
	if !hasGroupID || tidbGroupID == "" {
		return nil, "", "", fmt.Errorf("param tidb_group_id is required")
	}

	req := &mgmthttpcli.ScaleTiDBGroupRequest{
		ComponentType: "TIDB",
		NodeQuota:     &mgmthttpcli.NodeQuota{},
		Force:         instance.Params[opent.ParamForceScale] == "true",
	}

	// node count
	nodeCountStr, _ := instance.GetParam(opent.ParamNodeCount)
	if nodeCountStr == "" {
		return nil, "", "", fmt.Errorf("param node_count is required")
	}
	nodeCount, err := strconv.Atoi(nodeCountStr)
	if err != nil {
		return nil, "", "", fmt.Errorf("invalid node_count: %s", nodeCountStr)
	}
	req.NodeQuota.NodeCount = uint32(nodeCount)

	// mem & cpu
	memoryGiStr, _ := instance.GetParam(opent.ParamMemoryGi)
	cpuCoresStr, _ := instance.GetParam(opent.ParamCpuCores)
	if (memoryGiStr != "") != (cpuCoresStr != "") {
		return nil, "", "", fmt.Errorf("both memory_gi and cpu_cores must be provided or neither")
	}
	if memoryGiStr != "" || cpuCoresStr != "" {
		memoryGi, err := strconv.Atoi(memoryGiStr)
		if err != nil {
			return nil, "", "", fmt.Errorf("invalid memory_gi: %s", memoryGiStr)
		}
		cpuCores, err := strconv.Atoi(cpuCoresStr)
		if err != nil {
			return nil, "", "", fmt.Errorf("invalid cpu_cores: %s", cpuCoresStr)
		}
		req.NodeQuota.NodeSpec = &mgmthttpcli.NodeSpec{
			MemoryGi: uint32(memoryGi),
			Cpu:      uint32(cpuCores),
		}
	}

	return req, cid, tidbGroupID, nil
}

func assembleTiDBEvent(
	eventType string,
	status evbus.EventStatus,
	author string,
	tidbGroup *mgmthttpcli.TiDBGroup,
	summaryFmt string,
	args ...string,
) (*evbus.DisplayEvent, error) {
	if tidbGroup == nil {
		return nil, fmt.Errorf("invalid TiDB group")
	}
	anyArgs := make([]any, len(args))
	for i, v := range args {
		anyArgs[i] = v
	}

	tenantIDUint, err := strconv.ParseUint(tidbGroup.TenantID, 10, 64)
	if err != nil {
		return nil, fmt.Errorf("invalid tenant ID: %s", tidbGroup.TenantID)
	}
	clusterIDUint, err := strconv.ParseUint(tidbGroup.ClusterID, 10, 64)
	if err != nil {
		return nil, fmt.Errorf("invalid cluster ID: %s", tidbGroup.ClusterID)
	}
	ev := &evbus.DisplayEvent{
		DisplayEventType: eventType,
		DisplayStatus:    status,
		Severity:         evbus.EventSeverityInfo,
		Summary:          fmt.Sprintf(summaryFmt, anyArgs...),
		HappenedAtMs:     uint64(time.Now().UnixMilli()),
		Author:           author,
		TenantID:         tenantIDUint,
		ProjectID:        0,
		ClusterID:        clusterIDUint,
	}
	return ev, nil
}
