package serverlessaction

import (
	"context"
	"fmt"
	"strconv"

	"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/serverless-service/idl/pbgen/proto/rpc"
)

var (
	ActionUpdateClusterResourceConfig = opent.ActionMetadata{
		Name: "UpdateClusterResourceConfig",
		Type: opent.ActionTypeAuto,
	}
)

var (
	_ actions.Action = &updateClusterResourceConfig{}
)

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

func NewUpdateClusterResourceConfig(comps *actions.Components) actions.AutoAction {
	return &updateClusterResourceConfig{comps: comps}
}

func (a *updateClusterResourceConfig) Metadata() opent.ActionMetadata {
	return ActionUpdateClusterResourceConfig
}

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

	cluster, err := a.comps.CoreSvc.GetCluster(ctx, cid, false)
	if err != nil {
		return err
	}
	if cluster.DeployType != core.DeployType_DEPLOY_TYPE_SHARED {
		return fmt.Errorf("only support serverless cluster")
	}
	if cluster.Status != core.ClusterStatus_CLUSTER_STATUS_NORMAL {
		return fmt.Errorf("cluster %d is not normal", cid)
	}

	_, err = a.getParam(instance)
	if err != nil {
		return err
	}

	return err
}

func (a *updateClusterResourceConfig) Start(ctx context.Context, instance *opent.Instance) (*actions.ActionResult, error) {
	param, err := a.getParam(instance)
	if err != nil {
		return actions.FailedActionResult(err), nil
	}
	_, err = a.comps.ServerlessCli.UpdateCluster(ctx, param)
	if err != nil {
		return actions.FailedActionResult(err), nil
	}
	return actions.RunningActionResult("sent request to update cluster resource config", nil), nil
}

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

func (a *updateClusterResourceConfig) getParam(instance *opent.Instance) (*rpc.UpdateClusterRequest, error) {
	cid, err := actions.GetClusterIDStr(instance)
	if err != nil {
		return nil, err
	}
	oid, err := actions.GetTenantIDStr(instance)
	if err != nil {
		return nil, err
	}
	pid, err := actions.GetProjectIDStr(instance)
	if err != nil {
		return nil, err
	}

	minReplicas, _ := instance.GetParam(opent.ParamMinTiDBReplicas)
	maxReplicas, _ := instance.GetParam(opent.ParamMaxTiDBReplicas)
	minRCU, _ := instance.GetParam(opent.ParamMinRCU)
	maxRCU, _ := instance.GetParam(opent.ParamMaxRCU)
	tidbPool, _ := instance.GetParam(opent.ParamTiDBPool)
	ruMode, _ := instance.GetParam(opent.ParamRUMode)
	canaryStr, _ := instance.GetParam(opent.ParamCanary)
	if len(minReplicas) == 0 &&
		len(maxReplicas) == 0 &&
		len(minRCU) == 0 &&
		len(maxRCU) == 0 &&
		len(tidbPool) == 0 &&
		len(ruMode) == 0 &&
		len(canaryStr) == 0 {
		return nil, fmt.Errorf("at least one of the cluster resource is required")
	}
	canary := getCanary(canaryStr)
	param := &rpc.UpdateClusterRequest{
		ClusterId: cid,
		OrgId:     oid,
		ProjectId: pid,
	}
	if len(minReplicas) > 0 {
		replicas, err := convertStringToUint(minReplicas)
		if err != nil {
			return nil, err
		}
		param.MinTidbReplicas = replicas
	}
	if len(maxReplicas) > 0 {
		replicas, err := convertStringToUint(maxReplicas)
		if err != nil {
			return nil, err
		}
		param.MaxTidbReplicas = replicas
	}
	if len(minRCU) > 0 {
		rcu, err := convertStringToUint(minRCU)
		if err != nil {
			return nil, err
		}
		param.MinRcu = rcu
	}
	if len(maxRCU) > 0 {
		rcu, err := convertStringToUint(maxRCU)
		if err != nil {
			return nil, err
		}
		param.MaxRcu = rcu
	}
	if len(tidbPool) > 0 {
		param.TidbPool = getTiDBPool(tidbPool)
	}
	if len(ruMode) > 0 {
		param.RuMode = getRUMode(ruMode)
	}
	if canary != nil {
		param.Canary = canary
	}
	return param, nil
}

func getTiDBPool(tidbPool string) *rpc.TiDBPool {
	var result rpc.TiDBPool
	switch tidbPool {
	case "Free":
		result = rpc.TiDBPool_TIDB_POOL_DEFAULT
	case "VIP":
		result = rpc.TiDBPool_TIDB_POOL_VIP
	case "Super VIP":
		result = rpc.TiDBPool_TIDB_POOL_SUPER_VIP
	default:
		result = rpc.TiDBPool_TIDB_POOL_UNSPECIFIED
	}
	return &result
}

func getRUMode(ruMode string) *rpc.RUMode {
	var result rpc.RUMode
	switch ruMode {
	case "Standard":
		result = rpc.RUMode_RU_MODE_STANDARD
	case "Throttled":
		result = rpc.RUMode_RU_MODE_THROTTLED
	default:
		result = rpc.RUMode_RU_MODE_UNSPECIFIED
	}
	return &result
}

func getCanary(canaryStr string) *bool {
	switch canaryStr {
	case "True":
		canary := true
		return &canary
	case "False":
		canary := false
		return &canary
	default:
		return nil
	}
}

func convertStringToUint(num string) (*uint32, error) {
	numInt, err := strconv.ParseUint(num, 10, 64)
	if err != nil {
		return nil, err
	}
	numNew := uint32(numInt)
	return &numNew, nil
}
