package action

import (
	"context"
	"fmt"
	"strconv"

	"braces.dev/errtrace"
	"github.com/tidbcloud/fastflow/pkg/entity/run"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"

	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/constant"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/repository"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/service/workflow/metric"
	"github.com/tidbcloud/tidb-management-service/utilities/workflow"
)

// BatchClusterMover is a minimal interface for moving clusters between projects.
// Using an interface avoids a circular import between workflow and serverless/cluster packages.
type BatchClusterMover interface {
	// BatchMoveClusters moves all Starter/Essential clusters from sourceProjectId to
	// targetProjectId.  servicePlans nil means all supported plans.
	BatchMoveClusters(ctx context.Context,
		orgId, sourceProjectId, targetProjectId uint64,
		servicePlans []string,
	) (total int32, successIds, failedIds []string, err error)
}

// MigrateProjectClustersParameter holds the task-level parameters for
// MigrateProjectClustersAction.  The fastflow framework deserializes the stored
// JSON task params into this struct before calling Run().
type MigrateProjectClustersParameter struct {
	ProjectIdx string `json:"project_migration_project_idx"`
}

// MigrateProjectClustersAction moves all Starter/Essential clusters from one
// source project to the resolved target project.
//
// The action is idempotent: clusters that have already been moved (their
// project_id already equals target) are silently skipped by BatchMoveClusters.
type MigrateProjectClustersAction struct {
	taskRepo     repository.ProjectMigrationTaskRepo
	clusterMover BatchClusterMover
}

func NewMigrateProjectClustersAction(
	taskRepo repository.ProjectMigrationTaskRepo,
	clusterMover BatchClusterMover,
) *MigrateProjectClustersAction {
	return &MigrateProjectClustersAction{
		taskRepo:     taskRepo,
		clusterMover: clusterMover,
	}
}

func (a *MigrateProjectClustersAction) Name() string {
	return constant.TaskProjectMigrationMigrateProject
}

func (a *MigrateProjectClustersAction) ParameterNew() any {
	return &MigrateProjectClustersParameter{}
}

func (a *MigrateProjectClustersAction) Run(ctx run.ExecuteContext, params any) error {
	cctx, span := workflow.GenerateContext(ctx)
	defer span.End()

	tilog.Info(cctx, "migrate project clusters action start")

	// Determine which migration index this task should process (passed as Params).
	migrateParams, ok := params.(*MigrateProjectClustersParameter)
	if !ok || migrateParams == nil {
		return fmt.Errorf("invalid migrate project clusters parameters")
	}
	idxStr := migrateParams.ProjectIdx
	if idxStr == "" {
		return fmt.Errorf("task param %q not found", constant.KeyProjectMigrationProjectIdx)
	}
	idx, err := strconv.Atoi(idxStr)
	if err != nil {
		return fmt.Errorf("parse project idx %q: %w", idxStr, err)
	}

	task, err := loadProjectMigrationTaskFromCtx(ctx, a.taskRepo)
	if err != nil {
		return errtrace.Wrap(err)
	}

	if idx < 0 || idx >= len(task.Migrations) {
		return fmt.Errorf("project migration index %d out of range (len=%d)", idx, len(task.Migrations))
	}

	m := task.Migrations[idx]
	if m.TargetProjectID == 0 {
		return fmt.Errorf("target project for source %d not resolved (run EnsureTargets first)", m.SourceProjectID)
	}

	orgID := task.OrgID
	orgIDStr := strconv.FormatUint(orgID, 10)
	srcIDStr := strconv.FormatUint(m.SourceProjectID, 10)

	_, successIDs, failedIDs, err := a.clusterMover.BatchMoveClusters(
		cctx,
		orgID,
		m.SourceProjectID,
		m.TargetProjectID,
		nil, // move all supported plans (Starter + Essential)
	)
	if err != nil {
		tilog.Error(cctx, "batch move clusters failed",
			zap.Uint64("sourceProjectID", m.SourceProjectID),
			zap.Uint64("targetProjectID", m.TargetProjectID),
			zap.Error(err))
		return errtrace.Wrap(err)
	}

	// Record per-step metrics.
	metric.ProjectMigrationClustersTotal.WithLabelValues(orgIDStr, srcIDStr, "success").Add(float64(len(successIDs)))
	metric.ProjectMigrationClustersTotal.WithLabelValues(orgIDStr, srcIDStr, "failure").Add(float64(len(failedIDs)))

	if len(failedIDs) > 0 {
		tilog.Warn(cctx, "some clusters failed to migrate",
			zap.Strings("failedIDs", failedIDs),
			zap.Uint64("sourceProjectID", m.SourceProjectID))
		return fmt.Errorf("%d cluster(s) failed to migrate from project %d", len(failedIDs), m.SourceProjectID)
	}

	tilog.Info(cctx, "migrate project clusters action end",
		zap.Int("migrated", len(successIDs)),
		zap.Uint64("sourceProjectID", m.SourceProjectID),
		zap.Uint64("targetProjectID", m.TargetProjectID),
	)
	return nil
}
