package action

import (
	"context"
	"fmt"
	"strconv"
	"strings"

	"github.com/tidbcloud/fastflow/pkg/entity/run"
	clusterdedicatedv1 "github.com/tidbcloud/infra-provider/api/tidbcloud/cluster-server/v1"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"

	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/adapter/eventbus"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/adapter/regionserver"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/entity"
	clusterreop "github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/repository/cluster"
	regionrepo "github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/repository/region"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/service/workflow/common"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	"github.com/tidbcloud/tidb-management-service/utilities/clusterevent"
	"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 regionserver.Client
	eventCommitter  eventbus.WALCommitter
}

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

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 work pool ids", zap.String("tidb_group_ids", tidbGroupIdsStr))
		return fmt.Errorf("not found the %s key", common.KeyTidbGroupIds)
	}

	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 work pool ids to delete")
		return nil
	}
	tidbGroupIds := strings.Split(tidbGroupIdsStr, ",")

	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.SelectRegionById(cctx, cluster.RegionId, cluster.GetOrganizationId())
	if err != nil {
		tilog.Error(cctx, "failed to find region", zap.Uint64("cluster_id", cluster.ClusterId), zap.Error(err))
		return err
	}

	for _, tidbGroupId := range tidbGroupIds {
		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
		}
	}

	// if there is only one tidb group, it means the delete non-default tidb group with default tidb group api
	if len(tidbGroupIds) == 1 {
		eb := entity.NewClusterEventBuilder(cctx, cluster, clusterevent.EventTypeScaled)
		if err := c.eventCommitter.Write(cctx, clusterevent.TopicClusterEvent, eb.Build()); err != nil {
			tilog.Error(
				cctx,
				"failed to write cluster event",
				zap.Uint64("cluster_id", cluster.ClusterId),
				zap.Error(err),
			)
			return err
		}
	}

	return nil
}

func (c *DeleteTidbGroupAction) delete(
	ctx context.Context,
	cluster *entity.Cluster,
	region *entity.Region,
	tidbGroupId string,
) error {
	deleteTidbGroupReq := &clusterdedicatedv1.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
}
