package action

import (
	"context"
	"strconv"

	clusterngv1beta "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"
	"github.com/tidbcloud/fastflow/pkg/entity/run"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"

	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/adapter/eventbus"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/adapter/regionalserver"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/entity"
	clusterreop "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/cluster"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/rcuquota"
	regionrepo "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/region"
	resourcepoolrepo "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/resourcepool"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/resourcepoolpolicy"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/service/workflow/common"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	"github.com/tidbcloud/tidb-management-service/utilities/workflow"
)

const (
	UpdateTidbCluster = "update_tidb_cluster"
)

// UpdateTidbClusterAction is the action to delete specified tidb clusters from the region
type UpdateTidbClusterAction struct {
	clusterRepo      clusterreop.Repo
	regionRepo       regionrepo.Repo
	regionServerCli  regionalserver.Client
	eventCommitter   eventbus.WALCommitter
	rcuQuotaRepo     rcuquota.Repo
	resourcePoolRepo resourcepoolrepo.Repo
}

func NewUpdateTidbClusterAction(
	clusterRepo clusterreop.Repo,
	regionRepo regionrepo.Repo,
	regionServerCli regionalserver.Client,
	eventCommitter eventbus.WALCommitter,
	rcuQuotaRepo rcuquota.Repo,
	resourcePoolRepo resourcepoolrepo.Repo,
) *UpdateTidbClusterAction {
	return &UpdateTidbClusterAction{
		clusterRepo:      clusterRepo,
		regionRepo:       regionRepo,
		regionServerCli:  regionServerCli,
		eventCommitter:   eventCommitter,
		rcuQuotaRepo:     rcuQuotaRepo,
		resourcePoolRepo: resourcePoolRepo,
	}
}

func (c *UpdateTidbClusterAction) Name() string {
	return UpdateTidbCluster
}

func (c *UpdateTidbClusterAction) ParameterNew() any {
	return &common.ClusterIdParameter{}
}

func (c *UpdateTidbClusterAction) Run(ctx run.ExecuteContext, params any) error {
	cctx, span := workflow.GenerateContext(ctx)

	clusterId, err := common.GetClusterId(params)
	if err != nil {
		tilog.Error(cctx, "failed to get cluster id", zap.Error(err))
		return err
	}

	tilog.Info(
		cctx,
		"start to update tidb group",
		zap.Uint64("cluster_id", clusterId),
	)
	defer func() {
		tilog.Info(
			cctx,
			"end to update tidb group",
			zap.Uint64("cluster_id", clusterId),
		)
		span.End()
	}()

	cluster, err := c.clusterRepo.Get(cctx, clusterId, true)
	if err != nil {
		tilog.Error(cctx, "failed to get cluster", zap.Uint64("cluster_id", cluster.ClusterId), zap.Error(err))
		return err
	}

	region, err := c.regionRepo.Get(cctx, cluster.RegionId)
	if err != nil {
		tilog.Error(
			cctx,
			"failed to find region",
			zap.Uint64("cluster_id", cluster.ClusterId),
			zap.Error(err),
		)
		return err
	}

	if err := c.update(cctx, cluster, region); err != nil {
		tilog.Error(
			cctx,
			"failed to update tidb group",
			zap.Uint64("cluster_id", cluster.ClusterId),
			zap.Error(err),
		)
		return err
	}

	return nil
}

func (c *UpdateTidbClusterAction) update(
	ctx context.Context,
	tidbcluster *entity.Cluster,
	region *entity.Region,
) error {
	quota, err := c.rcuQuotaRepo.Load(ctx, tidbcluster.GetOrganizationId(), tidbcluster.ServicePlan.String())
	if err != nil {
		tilog.Error(ctx, "failed to load rcu quota",
			zap.Error(err),
			zap.Uint64("organization_id", tidbcluster.GetOrganizationId()))
		return err
	}
	realMaxRcuWithBurstyRatio := tidbcluster.CalculateRealMaxRcuWithBurstyRatio(ctx, quota.BurstyRatio)

	updateTidbClusterReq := &clusterngv1beta.UpdateTiDBClusterRequest{
		ClusterId: strconv.FormatUint(tidbcluster.ClusterId, 10),
		RequestUnit: &clusterngv1beta.Cluster_RequestUnit{
			Min: uint32(tidbcluster.MinRcu),
			Max: realMaxRcuWithBurstyRatio,
		},
		RuVersion: tidbcluster.RuVersion,
	}
	stop, guardErr := guardClusterResourcePoolMutation(
		ctx,
		c.clusterRepo,
		c.resourcePoolRepo,
		tidbcluster,
		resourcepoolpolicy.OperationUpdateInstance,
	)
	if guardErr != nil {
		return guardErr
	}
	if stop {
		return nil
	}
	if _, err := c.regionServerCli.UpdateTidbCluster(ctx, region, updateTidbClusterReq); err != nil {
		if err.ErrCode() == bizerr.ErrCodeRecordNotFound {
			tilog.Info(
				ctx,
				"tidb cluster not found",
				zap.Uint64("tidb_cluster_id", tidbcluster.ClusterId),
			)
			return nil
		}

		tilog.Error(
			ctx,
			"failed to update tidb cluster",
			zap.Uint64("tidb_cluster_id", tidbcluster.ClusterId),
			zap.Error(err),
		)
		return err
	}
	return nil
}
