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"
	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"
	servicecommon "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/service/common"
	"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 (
	DeleteTidbCluster = "delete_tidb_cluster"
)

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

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

func (c *DeleteTidbClusterAction) Name() string {
	return DeleteTidbCluster
}

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

func (c *DeleteTidbClusterAction) 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 delete tidb cluster",
		zap.Uint64("cluster_id", clusterId),
	)
	defer func() {
		tilog.Info(
			cctx,
			"end to delete tidb cluster",
			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
	}
	resourcePoolID, parseErr := strconv.ParseUint(cluster.GetSharedPoolId(), 10, 64)
	if parseErr != nil || resourcePoolID == 0 {
		return bizerr.New(bizerr.ErrCodeUnknown, "cluster has an invalid resource pool id")
	}
	resourcePool, berr := c.resourcePoolRepo.Load(cctx, resourcePoolID, true)
	if berr != nil {
		tilog.Error(cctx, "failed to load resource pool",
			zap.Uint64("cluster_id", clusterId),
			zap.Uint64("resource_pool_id", resourcePoolID),
			zap.Error(berr),
		)
		return berr
	}
	if resourcePool.GetDeleteTime() == nil {
		if lifecycleErr := servicecommon.RequireResourcePoolLifecycle(
			resourcePool,
			resourcepoolpolicy.OperationDeleteInstance,
		); lifecycleErr != nil {
			tilog.Error(cctx, "resource pool lifecycle blocks cluster deletion",
				zap.Uint64("cluster_id", clusterId),
				zap.Uint64("resource_pool_id", resourcePoolID),
				zap.Error(lifecycleErr),
			)
			return lifecycleErr
		}
	}

	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.delete(cctx, cluster, region); err != nil {
		tilog.Error(
			cctx,
			"failed to delete tidb cluster",
			zap.Uint64("cluster_id", cluster.ClusterId),
			zap.Error(err),
		)
		return err
	}

	return nil
}

func (c *DeleteTidbClusterAction) delete(
	ctx context.Context,
	cluster *entity.Cluster,
	region *entity.Region,
) error {
	deleteTidbClusterReq := &clusterngv1beta.DeleteTiDBClusterRequest{
		ClusterId: strconv.FormatUint(cluster.ClusterId, 10),
	}
	if err := c.regionServerCli.DeleteTidbCluster(ctx, region, deleteTidbClusterReq); err != nil {
		if err.ErrCode() == bizerr.ErrCodeRecordNotFound {
			tilog.Info(
				ctx,
				"tidb cluster not found",
				zap.Uint64("cluster_id", cluster.ClusterId),
			)
			return nil
		}

		tilog.Error(
			ctx,
			"failed to delete tidb cluster",
			zap.Uint64("cluster_id", cluster.ClusterId),
			zap.Error(err),
		)
		return err
	}
	if berr := c.metadataHelper.writeDeleteMetadataByClusterID(ctx, cluster.ClusterId); berr != nil {
		tilog.Error(ctx, "failed to write premium reserved metadata after delete tidb cluster",
			zap.Uint64("cluster_id", cluster.ClusterId),
			zap.Error(berr),
		)
		return berr
	}
	return nil
}
