package tenantaction

import (
	"context"
	"fmt"
	"strconv"

	"github.com/tidbcloud/core-svc/idl/pbgen/proto/core"

	"github.com/tidbcloud/devops/api"
	opent "github.com/tidbcloud/devops/entity/operation"
	"github.com/tidbcloud/devops/pkg/infra/errs"
	"github.com/tidbcloud/devops/pkg/infra/utils/grpcutil"
	"github.com/tidbcloud/devops/pkg/service/operation/actions"
)

var (
	ActionUpdateTenantQuota opent.ActionMetadata = opent.ActionMetadata{
		Name:             "UpdateTenantQuota",
		Type:             opent.ActionTypeAuto,
		AllowAnyoneApply: true,
	}
)

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

func NewUpdateTenantQuota(comps *actions.Components) actions.AutoAction {
	return &updateTenantQuota{comps: comps}
}

func (a *updateTenantQuota) Metadata() opent.ActionMetadata {
	return ActionUpdateTenantQuota
}

func (a *updateTenantQuota) PreCheck(ctx context.Context, instance *opent.Instance) error {
	tid, reqs, err := a.getParam(instance)
	if err != nil {
		return err
	}

	resp, err := a.comps.CoreSvc.Core.ListTenantQuota(ctx, &core.ListTenantQuotaReq{TenantId: tid})
	if err != nil {
		return fmt.Errorf("list tenant quota failed: %w", err)
	}
	if err := grpcutil.RespToErr(resp.BaseResp); err != nil {
		return fmt.Errorf("list tenant quota failed: %w", err)
	}

	anyChanged := false
	for _, req := range reqs {
		for _, cur := range resp.TenantQuotas {
			if cur.Component == req.Component {
				anyChanged = anyChanged || cur.Quota != req.Quota
			}
		}
	}

	if !anyChanged {
		return errs.APIErrorf(api.ErrCodeInvalidArgument, "no quota changed")
	}

	return nil
}

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

	for _, req := range reqs {
		resp, err := a.comps.CoreSvc.Core.UpdateTenantQuota(ctx, req)
		if err != nil {
			return actions.FailedActionResult(err), nil
		}
		if grpcutil.RespToErr(resp.BaseResp) != nil {
			return actions.FailedActionResult(err), nil
		}
	}

	return actions.SucceededActionResult(nil), nil // return succeeded directly
}

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

func (a *updateTenantQuota) getParam(instance *opent.Instance) (uint64, []*core.UpdateTenantQuotaReq, error) {
	tid, err := actions.GetTenantID(instance)
	if err != nil {
		return 0, nil, err
	}

	reqs := []*core.UpdateTenantQuotaReq{}
	for paramName, value := range instance.Params {
		var compType core.ComponentType
		switch paramName {
		case opent.ParamTiDBNodeQuota:
			compType = core.ComponentType_COMPONENT_TYPE_TIDB
		case opent.ParamTiKVNodeQuota:
			compType = core.ComponentType_COMPONENT_TYPE_TIKV
		case opent.ParamTiFlashNodeQuota:
			compType = core.ComponentType_COMPONENT_TYPE_TIFLASH
		case opent.ParamTiProxyNodeQuota:
			compType = core.ComponentType_COMPONENT_TYPE_TIPROXY
		default:
			continue
		}

		quota, err := strconv.ParseUint(value, 10, 64)
		if err != nil {
			return 0, nil, fmt.Errorf("parse quota '%s' failed: %s", value, err)
		}

		if quota != 0 {
			req := &core.UpdateTenantQuotaReq{
				TenantId:  tid,
				Component: compType,
				Quota:     uint32(quota),
			}
			reqs = append(reqs, req)
		}
	}

	return tid, reqs, nil
}
