// step_handler.go 把 orchestrator.step 接入 jobs：每个 step 由一个 orchestrator.step
// job 驱动，因而能跨重启恢复并复用既有 backoff/dead-letter 机制。
package orchestrator

import (
	"context"
	"encoding/json"
	"fmt"
	"log/slog"
	"time"

	"github.com/google/uuid"

	"github.com/yan1h/agent-coding-workflow/internal/infra/errs"
	"github.com/yan1h/agent-coding-workflow/internal/jobs"
	"github.com/yan1h/agent-coding-workflow/internal/project"
)

// JobTypeOrchestratorStep 是驱动单个编排 step 的 job 类型。
const JobTypeOrchestratorStep jobs.JobType = "orchestrator.step"

// stepPayload 是 orchestrator.step job 的 payload。
type stepPayload struct {
	RunID  uuid.UUID `json:"run_id"`
	StepID uuid.UUID `json:"step_id"`
}

// StepHandlerDeps 是 StepHandler 的依赖集合。
type StepHandlerDeps struct {
	Repo             Repository
	Runner           *runner
	Gate             PhaseGate
	PM               PMAccess
	Enqueue          StepEnqueuer
	DefaultAgentKind map[project.Phase]uuid.UUID
	Config           Config
	Audit            AuditRecorder
	Log              *slog.Logger
}

// StepHandler 实现 jobs.Handler，驱动一个阶段回合并推进/回环状态机。
type StepHandler struct {
	repo     Repository
	runner   *runner
	gate     PhaseGate
	pm       PMAccess
	enqueue  StepEnqueuer
	defaults map[project.Phase]uuid.UUID
	cfg      Config
	audit    AuditRecorder
	log      *slog.Logger
}

// NewStepHandler 构造 StepHandler。
func NewStepHandler(d StepHandlerDeps) *StepHandler {
	log := d.Log
	if log == nil {
		log = slog.Default()
	}
	return &StepHandler{
		repo: d.Repo, runner: d.Runner, gate: d.Gate, pm: d.PM,
		enqueue: d.Enqueue, defaults: d.DefaultAgentKind, cfg: d.Config,
		audit: d.Audit, log: log,
	}
}

// Type 返回 job 类型。
func (h *StepHandler) Type() jobs.JobType { return JobTypeOrchestratorStep }

// Handle 驱动一个 step：load step+run；run 非活跃 → no-op；spawn-or-resume 跑回合；
// 评估网关；通过 → ChangePhase + 入队下一 step（或 done → run 成功）；否则递增 attempt
// 并返回 error（attempt 耗尽 → jobs.Permanent → dead-letter）。
//
// 幂等：先按 step.status 短路，避免重复 leased 时重复 spawn（worker 已有 Complete fence）。
func (h *StepHandler) Handle(ctx context.Context, job jobs.Job) error {
	var p stepPayload
	if err := json.Unmarshal(job.Payload, &p); err != nil {
		return jobs.Permanent(errs.Wrap(err, errs.CodeInvalidInput, "orchestrator.step: bad payload"))
	}

	step, err := h.repo.GetStep(ctx, p.StepID)
	if err != nil {
		// step 不存在：永久失败（无法恢复）。
		return jobs.Permanent(err)
	}
	// 已到终态：no-op（重复投递 / 重启重入）。
	switch step.Status {
	case StepSucceeded, StepFailed, StepDead:
		return nil
	}

	run, err := h.repo.GetRun(ctx, step.RunID)
	if err != nil {
		return jobs.Permanent(err)
	}
	// run 已暂停/取消/终态：不驱动，完成 job（释放 worker）。
	if !run.Status.IsActive() {
		h.log.Info("orchestrator.step.skip_inactive_run",
			"run_id", run.ID, "step_id", step.ID, "run_status", run.Status)
		return nil
	}

	// run.status pending → running（首个 step 起跑）。
	if run.Status == RunPending {
		if _, uerr := h.repo.UpdateRunStatus(ctx, run.ID, RunRunning, nil); uerr != nil {
			h.log.Warn("orchestrator.run.mark_running_failed", "run_id", run.ID, "err", uerr.Error())
		}
	}

	// 解析需求（用于 prompt 上下文 + 阶段绑定 + ChangePhase）。
	req, err := h.pm.GetRequirementByID(ctx, run.RequirementID)
	if err != nil {
		return jobs.Permanent(err)
	}
	proj, err := h.pm.GetProjectByID(ctx, run.ProjectID)
	if err != nil {
		return jobs.Permanent(err)
	}

	// step.prompt 为空时（首建只填了占位）补建 prompt。通常 service 建 step 时已填。
	if step.Prompt == "" {
		artifacts, _ := h.pm.ListLatestArtifacts(ctx, run.RequirementID)
		override := run.Config.PromptOverrides[step.Phase]
		step.Prompt = BuildPrompt(step.Phase, req, artifacts, override)
	}

	if step.Status == StepAwaitingGate && step.GateResult != nil && step.GateResult.Pass {
		return h.advancePassedStep(ctx, run, step, req, proj, *step.GateResult, "")
	}

	// 标记 spawning（关联本 job）。
	jobID := job.ID
	step.Status = StepSpawning
	step.JobID = &jobID
	if updated, uerr := h.repo.UpdateStepStatus(ctx, step); uerr == nil {
		step = updated
	}

	// 驱动回合（spawn-or-resume + SendPromptAndWait）。
	res, terr := h.runner.driveTurn(ctx, run, step, req.Number)
	if terr != nil {
		return h.failStep(ctx, run, step, terr)
	}

	// 记录 session + stopReason，进入 awaiting_gate。
	step.ACPSessionID = &res.SessionID
	stop := res.StopReason
	step.StopReason = &stop
	step.Status = StepAwaitingGate
	if updated, uerr := h.repo.UpdateStepStatus(ctx, step); uerr == nil {
		step = updated
	}

	// 评估网关。
	gate, gerr := h.gate.Evaluate(ctx, run, step, res.StopReason)
	if gerr != nil {
		return h.failStep(ctx, run, step, gerr)
	}
	step.GateResult = &gate
	if !gate.Pass {
		return h.failStepGate(ctx, run, step, gate)
	}

	// 网关通过后先停在 awaiting_gate；只有 project.ChangePhase 与 run phase 都写成功后，
	// 才能把 step 标为 succeeded 并入队下一阶段，避免绕过 project 的 entry gate。
	step.Status = StepAwaitingGate
	step.GateResult = &gate
	step.LastError = nil
	if updated, uerr := h.repo.UpdateStepStatus(ctx, step); uerr == nil {
		step = updated
	}
	return h.advancePassedStep(ctx, run, step, req, proj, gate, res.StopReason)
}

func (h *StepHandler) advancePassedStep(ctx context.Context, run *Run, step *Step, req *project.Requirement, proj *project.Project, gate GateResult, stopReason string) error {
	// 推进阶段。
	next, terminal := NextPhase(step.Phase, gate)
	if terminal {
		step.Status = StepSucceeded
		step.LastError = nil
		if updated, uerr := h.repo.UpdateStepStatus(ctx, step); uerr == nil {
			step = updated
		}
		h.recordAudit(ctx, run, "orchestrator.step.advance", step.ID.String(), map[string]any{
			"phase":       string(step.Phase),
			"stop_reason": stopReason,
		})
		// 整条 run 完成。
		if _, uerr := h.repo.UpdateRunStatus(ctx, run.ID, RunSucceeded, nil); uerr != nil {
			h.log.Warn("orchestrator.run.mark_succeeded_failed", "run_id", run.ID, "err", uerr.Error())
		}
		h.recordAudit(ctx, run, "orchestrator.run.succeeded", run.ID.String(), nil)
		return nil
	}

	// 写需求阶段（run.owner 身份）。失败必须阻塞编排推进；project.ChangePhase 是
	// 更靠近领域状态的准入网关，不能被 orchestrator.run.current_phase 越过。
	if cerr := h.pm.ChangeRequirementPhase(ctx, run.OwnerID, false, proj.Slug, req.Number, next); cerr != nil {
		h.log.Warn("orchestrator.change_phase_failed",
			"run_id", run.ID, "to", next, "err", cerr.Error())
		return h.failPhaseAdvance(ctx, run, step, next, cerr)
	}
	if _, uerr := h.repo.UpdateRunPhase(ctx, run.ID, next); uerr != nil {
		h.log.Warn("orchestrator.run.update_phase_failed", "run_id", run.ID, "err", uerr.Error())
		return h.failPhaseAdvance(ctx, run, step, next, uerr)
	}

	step.Status = StepSucceeded
	step.LastError = nil
	if updated, uerr := h.repo.UpdateStepStatus(ctx, step); uerr == nil {
		step = updated
	}
	h.recordAudit(ctx, run, "orchestrator.step.advance", step.ID.String(), map[string]any{
		"phase":       string(step.Phase),
		"stop_reason": stopReason,
	})

	// 建并入队下一阶段 step。
	if _, eerr := h.enqueueNextStep(ctx, run, next); eerr != nil {
		return eerr
	}
	return nil
}

// enqueueNextStep 为 run 在 phase 上建一个新 step 并入队其 job。复用 service 的逻辑。
func (h *StepHandler) enqueueNextStep(ctx context.Context, run *Run, phase project.Phase) (*Step, error) {
	step, err := buildPhaseStep(ctx, h.repo, h.pm, run, phase, nil, 0, h.defaults)
	if err != nil {
		// 无法为下一阶段建 step（如缺 agent kind）：标 run 失败。
		msg := err.Error()
		if _, uerr := h.repo.UpdateRunStatus(ctx, run.ID, RunFailed, &msg); uerr != nil {
			h.log.Warn("orchestrator.run.mark_failed", "run_id", run.ID, "err", uerr.Error())
		}
		return nil, jobs.Permanent(err)
	}
	if eerr := h.enqueue.EnqueueStep(ctx, run.ID, step.ID, time.Now().UTC()); eerr != nil {
		return nil, eerr
	}
	return step, nil
}

// failStep 处理回合驱动/网关评估的错误：递增 attempt，返回可重试或 Permanent error。
func (h *StepHandler) failStep(ctx context.Context, run *Run, step *Step, cause error) error {
	msg := cause.Error()
	step.Status = StepRunning // 保持非终态，等待重试或 dead-letter 落定
	step.LastError = &msg
	_, _ = h.repo.UpdateStepStatus(ctx, step)
	updated, ierr := h.repo.IncrementStepAttempt(ctx, step.ID)
	if ierr == nil {
		step = updated
	}
	return h.retryOrDead(ctx, run, step, cause, false)
}

// failStepGate 处理网关未通过：Retry=false → 立即 Permanent；否则递增 attempt 后判断。
func (h *StepHandler) failStepGate(ctx context.Context, run *Run, step *Step, gate GateResult) error {
	reason := gate.Reason
	step.LastError = &reason
	step.Status = StepRunning
	_, _ = h.repo.UpdateStepStatus(ctx, step)
	cause := errs.New(errs.CodePhaseGateFailed, gate.Reason)
	if !gate.Retry {
		return h.markDead(ctx, run, step, cause)
	}
	updated, ierr := h.repo.IncrementStepAttempt(ctx, step.ID)
	if ierr == nil {
		step = updated
	}
	return h.retryOrDead(ctx, run, step, cause, true)
}

func (h *StepHandler) failPhaseAdvance(ctx context.Context, run *Run, step *Step, next project.Phase, cause error) error {
	msg := cause.Error()
	step.Status = StepAwaitingGate
	step.LastError = &msg
	_, _ = h.repo.UpdateStepStatus(ctx, step)

	if ae, ok := errs.As(cause); ok && ae.Code == errs.CodePhaseGateFailed {
		if _, uerr := h.repo.UpdateRunStatus(ctx, run.ID, RunPaused, &msg); uerr != nil {
			h.log.Warn("orchestrator.run.mark_paused_failed", "run_id", run.ID, "err", uerr.Error())
		}
		h.recordAudit(ctx, run, "orchestrator.step.phase_blocked", step.ID.String(), map[string]any{
			"phase": string(step.Phase),
			"next":  string(next),
			"err":   msg,
		})
		return nil
	}

	updated, ierr := h.repo.IncrementStepAttempt(ctx, step.ID)
	if ierr == nil {
		step = updated
	}
	return h.retryOrDead(ctx, run, step, cause, false)
}

// retryOrDead 根据 attempt 与 MaxAttemptsPerPhase 决定可重试还是 dead-letter。
func (h *StepHandler) retryOrDead(ctx context.Context, run *Run, step *Step, cause error, gateFail bool) error {
	maxAttempts := run.Config.MaxAttemptsPerPhase
	if maxAttempts <= 0 {
		maxAttempts = h.cfg.MaxAttemptsPerPhase
	}
	if maxAttempts <= 0 {
		maxAttempts = 3
	}
	if step.Attempt >= maxAttempts {
		return h.markDead(ctx, run, step, cause)
	}
	h.recordAudit(ctx, run, "orchestrator.step.retry", step.ID.String(), map[string]any{
		"phase":   string(step.Phase),
		"attempt": step.Attempt,
		"err":     cause.Error(),
		"gate":    gateFail,
	})
	// 返回非 Permanent error：jobs worker 走 backoff 重试，重新驱动本 step。
	return fmt.Errorf("orchestrator.step retry (attempt %d/%d): %w", step.Attempt, maxAttempts, cause)
}

// markDead 把 step 标 dead、run 标 failed，返回 jobs.Permanent（dead-letter）。
func (h *StepHandler) markDead(ctx context.Context, run *Run, step *Step, cause error) error {
	msg := cause.Error()
	step.Status = StepDead
	step.LastError = &msg
	_, _ = h.repo.UpdateStepStatus(ctx, step)
	if _, uerr := h.repo.UpdateRunStatus(ctx, run.ID, RunFailed, &msg); uerr != nil {
		h.log.Warn("orchestrator.run.mark_failed", "run_id", run.ID, "err", uerr.Error())
	}
	h.recordAudit(ctx, run, "orchestrator.step.dead_letter", step.ID.String(), map[string]any{
		"phase": string(step.Phase),
		"err":   msg,
	})
	return jobs.Permanent(cause)
}

func (h *StepHandler) recordAudit(ctx context.Context, run *Run, action, targetID string, meta map[string]any) {
	if h.audit == nil {
		return
	}
	uid := run.OwnerID
	h.audit.Record(ctx, uid, action, targetID, meta)
}
