package action

import (
	"context"
	"strconv"

	"github.com/tidbcloud/fastflow/pkg/entity/run"
	regionaldedicatedv1 "github.com/tidbcloud/infra-provider/api/tidbcloud/regional-server/v1"
	"github.com/tidbcloud/pkgv2/db"
	"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"
	clusterrepo "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 (
	DeleteCluster = "delete_cluster"
)

type DeleteClusterAction struct {
	regionalCli regionserver.Client
	clusterRepo clusterrepo.Repo
	regionRepo  regionrepo.Repo

	// Caution: If you publish event in the action, you should put code inside a transaction.
	eventCommitter eventbus.WALCommitter
	transaction    db.Transactioner
}

func NewDeleteClusterAction(
	regionalCli regionserver.Client,
	clusterRepo clusterrepo.Repo,
	regionRepo regionrepo.Repo,
	committer eventbus.WALCommitter,
	transaction db.Transactioner,
) *DeleteClusterAction {
	return &DeleteClusterAction{
		regionalCli:    regionalCli,
		clusterRepo:    clusterRepo,
		regionRepo:     regionRepo,
		eventCommitter: committer,
		transaction:    transaction,
	}
}

func (a *DeleteClusterAction) Name() string {
	return DeleteCluster
}

func (a *DeleteClusterAction) ParameterNew() any {
	return &common.ClusterIdParameter{}
}

func (a *DeleteClusterAction) 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 cluster", zap.Uint64("cluster_id", clusterId))
	defer func() {
		tilog.Info(cctx, "end to delete cluster", zap.Uint64("cluster_id", clusterId))
		span.End()
	}()

	if err := a.transaction.Transaction(cctx, func(_ context.Context) error {
		clusters, err := a.clusterRepo.Query(cctx, &clusterrepo.Filter{ClusterIds: []uint64{clusterId}, ShowDeleted: true})
		if err != nil || len(clusters) != 1 {
			tilog.Error(cctx, "failed to query deleted cluster", zap.String("cluster_id", strconv.FormatUint(clusterId, 10)), zap.Error(err))
			return err
		}
		cluster := clusters[0]

		if err := a.deleteResources(cctx, cluster); err != nil {
			tilog.Error(cctx, "failed to deleted cluster resource", zap.String("cluster_id", strconv.FormatUint(clusterId, 10)), zap.Error(err))
			return err
		}

		// send delete event
		deletedEvent := entity.NewClusterEventBuilder(cctx, cluster, clusterevent.EventTypeDeleted).Build()
		if err := a.eventCommitter.Write(cctx, clusterevent.TopicClusterEvent, deletedEvent); err != nil {
			tilog.Error(cctx, "failed to publish cluster event", zap.Error(err))
			return err
		}

		return nil
	}); err != nil {
		tilog.Error(cctx, "failed to delete cluster", zap.Error(err), zap.Uint64("cluster_id", clusterId))
		return err
	}

	return nil
}

func (a *DeleteClusterAction) deleteResources(ctx context.Context, cluster *entity.Cluster) error {
	region, err := a.regionRepo.SelectRegionById(ctx, cluster.RegionId, cluster.GetOrganizationId())
	if err != nil {
		tilog.Error(ctx, "failed to find region", zap.String("region_id", cluster.RegionId.String()), zap.Error(err))
		return err
	}

	clusterIdStr := strconv.FormatUint(cluster.ClusterId, 10)

	req := &regionaldedicatedv1.DeleteClusterRequest{
		ClusterId: clusterIdStr,
	}

	if err = a.regionalCli.DeleteCluster(ctx, region, req); err != nil {
		if err.ErrCode() != bizerr.ErrCodeRecordNotFound {
			tilog.Error(ctx, "failed to delete cluster", zap.String("cluster_id", clusterIdStr), zap.Error(err))
			return err
		}

		tilog.Info(ctx, "regional cluster is not found", zap.String("cluster_id", clusterIdStr))
	}

	return nil
}
