package tenantaction

import (
	"context"
	"fmt"
	"strconv"

	opent "github.com/tidbcloud/devops/entity/operation"
	"github.com/tidbcloud/devops/pkg/service/operation/actions"
	commonv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/common/v1"
	nextgenv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/nextgen/v1"
	"google.golang.org/protobuf/encoding/protojson"
)

const legacyParamVcpusLimitPerResourcePool opent.ParamName = "vcpus_limit_per_resource_pool"

var (
	ActionUpdateRcuQuota opent.ActionMetadata = opent.ActionMetadata{
		Name: "UpdateRcuQuota",
		Type: opent.ActionTypeAuto,
	}
)

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

func NewUpdateRcuQuota(comps *actions.Components) actions.AutoAction {
	return &updateRcuQuota{comps: comps}
}

func (a *updateRcuQuota) Metadata() opent.ActionMetadata {
	return ActionUpdateRcuQuota
}

func (a *updateRcuQuota) PreCheck(ctx context.Context, instance *opent.Instance) error {
	_, ok := instance.Params[opent.ParamTenantID]
	if !ok {
		return fmt.Errorf("tenant_id is required")
	}

	if v, ok := instance.Params[opent.ParamServicePlan]; !ok || v == "" {
		return fmt.Errorf("service_plan is required")
	}

	anyChanged := false
	paramValue := func(name opent.ParamName) (string, bool) {
		if v, ok := instance.Params[name]; ok {
			return v, true
		}
		if name == opent.ParamVcpusLimitPerResourcePool {
			if v, ok := instance.Params[legacyParamVcpusLimitPerResourcePool]; ok {
				return v, true
			}
		}
		return "", false
	}

	for _, k := range []opent.ParamName{
		opent.ParamInstanceMaxRCU,
		opent.ParamTotalMaxRCU,
		opent.ParamBurstyRatio,
		opent.ParamIdleResourcePoolTTLInHour,
		opent.ParamVcpusLimitPerResourcePool,
		opent.ParamMinimalRCUBaseline,
		opent.ParamMinimalRCUCoefficient,
		opent.ParamScaleDownCooldownMinutes,
	} {
		if v, ok := paramValue(k); ok {
			if v == "" {
				continue
			}

			var nonZero bool
			var err error
			if k == opent.ParamBurstyRatio || k == opent.ParamMinimalRCUCoefficient {
				var parsed float64
				parsed, err = strconv.ParseFloat(v, 64)
				nonZero = parsed != 0
			} else {
				var parsed uint64
				parsed, err = strconv.ParseUint(v, 10, 64)
				nonZero = parsed != 0
			}
			if err != nil {
				return fmt.Errorf("param %s is invalid: %w", k, err)
			}
			if nonZero {
				anyChanged = true
			}
		}
	}

	if !anyChanged {
		return fmt.Errorf("no rcu quota changed")
	}

	return nil
}

func (a *updateRcuQuota) Start(ctx context.Context, instance *opent.Instance) (*actions.ActionResult, error) {
	tidStr, ok := instance.Params[opent.ParamTenantID]
	if !ok {
		return actions.FailedActionResult(fmt.Errorf("tenant_id missing")), nil
	}
	tid, err := strconv.ParseUint(tidStr, 10, 64)
	if err != nil {
		return actions.FailedActionResult(fmt.Errorf("invalid tenant_id: %w", err)), nil
	}

	rcu := &nextgenv1.RcuQuota{
		OrganizationId: tid,
	}

	if v, ok := instance.Params[opent.ParamInstanceMaxRCU]; ok && v != "" {
		parsed, err := strconv.ParseUint(v, 10, 64)
		if err != nil {
			return actions.FailedActionResult(fmt.Errorf("invalid instanceMaxRCU: %w", err)), nil
		}
		if parsed != 0 {
			rcu.InstanceMaxRcu = parsed
		}
	}
	if v, ok := instance.Params[opent.ParamTotalMaxRCU]; ok && v != "" {
		parsed, err := strconv.ParseUint(v, 10, 64)
		if err != nil {
			return actions.FailedActionResult(fmt.Errorf("invalid totalMaxRCU: %w", err)), nil
		}
		if parsed != 0 {
			rcu.TotalMaxRcu = parsed
		}
	}
	if v, ok := instance.Params[opent.ParamBurstyRatio]; ok && v != "" {
		parsedF, err := strconv.ParseFloat(v, 32)
		if err != nil {
			return actions.FailedActionResult(fmt.Errorf("invalid burstyRatio: %w", err)), nil
		}
		if parsedF != 0 {
			rcu.BurstyRatio = float32(parsedF)
		}
	}
	if v, ok := instance.Params[opent.ParamIdleResourcePoolTTLInHour]; ok && v != "" {
		parsed, err := strconv.ParseUint(v, 10, 64)
		if err != nil {
			return actions.FailedActionResult(fmt.Errorf("invalid idleResourcePoolTtlInHours: %w", err)), nil
		}
		if parsed != 0 {
			rcu.IdleResourcePoolTtlInHours = parsed
		}
	}
	if v, ok := instance.Params[opent.ParamVcpusLimitPerResourcePool]; ok && v != "" {
		parsed, err := strconv.ParseUint(v, 10, 64)
		if err != nil {
			return actions.FailedActionResult(fmt.Errorf("invalid vcpus_limit_per_resource_pool: %w", err)), nil
		}
		if parsed != 0 {
			rcu.VcpusLimitPerResourcePool = parsed
		}
	} else if v, ok := instance.Params[legacyParamVcpusLimitPerResourcePool]; ok && v != "" {
		parsed, err := strconv.ParseUint(v, 10, 64)
		if err != nil {
			return actions.FailedActionResult(fmt.Errorf("invalid vcpus_limit_per_resource_pool: %w", err)), nil
		}
		if parsed != 0 {
			rcu.VcpusLimitPerResourcePool = parsed
		}
	}
	if v, ok := instance.Params[opent.ParamMinimalRCUBaseline]; ok && v != "" {
		parsed, err := strconv.ParseUint(v, 10, 64)
		if err != nil {
			return actions.FailedActionResult(fmt.Errorf("invalid minimalRcuBaseline: %w", err)), nil
		}
		if parsed != 0 {
			rcu.MinimalRcuBaseline = parsed
		}
	}
	if v, ok := instance.Params[opent.ParamMinimalRCUCoefficient]; ok && v != "" {
		parsedF, err := strconv.ParseFloat(v, 32)
		if err != nil {
			return actions.FailedActionResult(fmt.Errorf("invalid minimalRcuCoefficient: %w", err)), nil
		}
		if parsedF != 0 {
			rcu.MinimalRcuCoefficient = float32(parsedF)
		}
	}
	if v, ok := instance.Params[opent.ParamScaleDownCooldownMinutes]; ok && v != "" {
		parsed, err := strconv.ParseUint(v, 10, 64)
		if err != nil {
			return actions.FailedActionResult(fmt.Errorf("invalid scaleDownCooldownMinutes: %w", err)), nil
		}
		if parsed != 0 {
			rcu.ScaleDownCooldownMinutes = parsed
		}
	}
	if v, ok := instance.Params[opent.ParamServicePlan]; ok && v != "" {
		sp, ok := commonv1.ServicePlan_value[v]
		if !ok {
			return actions.FailedActionResult(fmt.Errorf("invalid service_plan: %s", v)), nil
		}
		rcu.ServicePlan = commonv1.ServicePlan(sp)
	}

	req := &nextgenv1.UpdateRcuQuotaRequest{
		RcuQuota: rcu,
	}

	_, err = a.comps.CoreSvc.Mgmt.RcuQuota.UpdateRcuQuota(ctx, req)
	if err != nil {
		return actions.FailedActionResult(err), nil
	}

	listReq := &nextgenv1.ListOrgRcuQuotasRequest{OrganizationId: tid}
	listResp, err := a.comps.CoreSvc.Mgmt.RcuQuota.ListOrgRcuQuotas(ctx, listReq)
	if err != nil {
		output := map[opent.OutputName]string{
			opent.OutputName("result"): fmt.Sprintf("update succeeded; list failed: %v", err),
		}
		return actions.SucceededActionResult(output), nil
	}

	respBytes, err := protojson.MarshalOptions{UseEnumNumbers: false}.Marshal(listResp)
	if err != nil {
		output := map[opent.OutputName]string{
			opent.OutputName("result"): fmt.Sprintf("update succeeded; marshal failed: %v", err),
		}
		return actions.SucceededActionResult(output), nil
	}

	output := map[opent.OutputName]string{
		opent.OutputName("result"): string(respBytes),
	}

	return actions.SucceededActionResult(output), nil
}

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