package action

import (
	"context"
	"strconv"
	"strings"

	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"
	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 (
	DeleteTidbGroup = "delete_tidb_group"
)

// DeleteTidbGroupAction is the action to delete specified tidb groups from the cluster
type DeleteTidbGroupAction struct {
	clusterRepo      clusterreop.Repo
	regionRepo       regionrepo.Repo
	regionServerCli  regionalserver.Client
	eventCommitter   eventbus.WALCommitter
	resourcePoolRepo resourcepoolrepo.Repo
}

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

func (c *DeleteTidbGroupAction) Name() string {
	return DeleteTidbGroup
}

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

func (c *DeleteTidbGroupAction) 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
	}
	tidbGroupIdsStr, ok := ctx.GetVar(common.KeyTidbGroupIds)
	if !ok {
		tilog.Error(cctx, "not found the tidbgroup ids", zap.String("tidb_group_ids", tidbGroupIdsStr))
		return bizerr.New(bizerr.ErrCodeRecordNotFound, "not found the tidbgroup ids")
	}

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

	if tidbGroupIdsStr == "" {
		tilog.Warn(cctx, "no tidbgroup ids to delete")
		return nil
	}
	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
	}

	for tidbgroupId := range strings.SplitSeq(tidbGroupIdsStr, ",") {
		if err := c.delete(cctx, cluster, region, tidbgroupId); err != nil {
			tilog.Error(
				cctx,
				"failed to delete tidb group",
				zap.Uint64("cluster_id", cluster.ClusterId),
				zap.String("tidb_group_id", tidbgroupId),
				zap.Error(err),
			)
			return err
		}
	}

	return nil
}

func (c *DeleteTidbGroupAction) delete(
	ctx context.Context,
	cluster *entity.Cluster,
	region *entity.Region,
	tidbGroupId string,
) error {
	if cluster.GetDeleteTime() == nil {
		stop, guardErr := guardLoadedClusterResourcePoolMutation(
			ctx,
			c.resourcePoolRepo,
			cluster,
			resourcepoolpolicy.OperationDeleteInstance,
		)
		if guardErr != nil {
			return guardErr
		}
		if stop {
			return nil
		}
	}
	deleteTidbGroupReq := &clusterngv1beta.DeleteTiDBGroupRequest{
		ClusterId:   strconv.FormatUint(cluster.ClusterId, 10),
		TidbGroupId: tidbGroupId,
	}
	if err := c.regionServerCli.DeleteTidbGroup(ctx, region, deleteTidbGroupReq); err != nil {
		if err.ErrCode() == bizerr.ErrCodeRecordNotFound {
			tilog.Info(
				ctx,
				"tidb group not found",
				zap.Uint64("cluster_id", cluster.ClusterId),
				zap.String("tidb_group_id", tidbGroupId),
			)
			return nil
		}

		tilog.Error(
			ctx,
			"failed to delete tidb group",
			zap.Uint64("cluster_id", cluster.ClusterId),
			zap.String("tidb_group_id", tidbGroupId),
			zap.Error(err),
		)
		return err
	}
	return nil
}
