// runner.go 是回合驱动器：给定一个 Step，要么经 acp.SessionService.Create 以特权
// owner 身份创建新 session，要么在崩溃 session 的同一 worktree 上 ResumeSession，然后
// 经 acp.TurnRunner.SendPromptAndWait 阻塞跑完一个回合。封装重试/恢复策略。
package orchestrator

import (
	"context"
	"time"

	"github.com/google/uuid"

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

// ACPSessions 是 runner 创建 session 所需的窄接口（acp.SessionService 满足）。
type ACPSessions interface {
	Create(ctx context.Context, c acp.Caller, in acp.CreateSessionInput) (*acp.Session, error)
}

// ACPSessionLookup 是 runner 查找 step 已有 session 所需的窄接口（acp.Repository 满足）。
type ACPSessionLookup interface {
	GetSessionByStepID(ctx context.Context, stepID uuid.UUID) (*acp.Session, error)
	GetCrashedSessionForResume(ctx context.Context, stepID uuid.UUID) (*acp.Session, error)
}

// turnResult 是一次回合驱动的结果。
type turnResult struct {
	SessionID  uuid.UUID
	StopReason string
}

// runner 封装 spawn-or-resume + SendPromptAndWait。
type runner struct {
	sessions    ACPSessions
	turns       acp.TurnRunner
	lookup      ACPSessionLookup
	turnTimeout time.Duration
}

// NewRunner 构造回合驱动器（由 app.go 装配 StepHandler 时调用）。turnTimeout<=0 时
// 退回 10 分钟默认值。
func NewRunner(sessions ACPSessions, turns acp.TurnRunner, lookup ACPSessionLookup, turnTimeout time.Duration) *runner {
	if turnTimeout <= 0 {
		turnTimeout = 10 * time.Minute
	}
	return &runner{sessions: sessions, turns: turns, lookup: lookup, turnTimeout: turnTimeout}
}

// driveTurn 为 step 驱动一个回合：
//
//  1. 若该 step 已有崩溃/退出 session → ResumeSession（同 worktree）。
//  2. 否则若该 step 已有活跃 session → 复用（幂等：避免重试重复 spawn）。
//  3. 否则 → Create 一个新 session（特权 owner 身份；OrchestratorStepID 反向关联）。
//
// requirementNumber 用于把新 session 绑定到 req/<slug>-N worktree，使同一 run 的各
// 阶段 step 共享同一 worktree（resume 也据此回到原 worktree）。
//
// 然后 SendPromptAndWait（受 turnTimeout 约束）。返回 sessionID + 原始 stopReason。
func (r *runner) driveTurn(ctx context.Context, run *Run, step *Step, requirementNumber int) (turnResult, error) {
	owner := acp.Caller{UserID: run.OwnerID, IsAdmin: false}

	var sessionID uuid.UUID

	// 1. 崩溃 session → resume（同一 worktree）。
	if crashed, err := r.lookup.GetCrashedSessionForResume(ctx, step.ID); err == nil && crashed != nil {
		resumed, rerr := r.turns.ResumeSession(ctx, owner, crashed.ID)
		if rerr != nil {
			return turnResult{}, rerr
		}
		sessionID = resumed.ID
	} else if live, lerr := r.lookup.GetSessionByStepID(ctx, step.ID); lerr == nil && live != nil && live.Status.IsActive() {
		// 2. 已有活跃 session → 复用。
		sessionID = live.ID
	} else {
		// 3. 创建新 session（特权 owner；反向关联到 step）。
		stepID := step.ID
		in := acp.CreateSessionInput{
			WorkspaceID:        run.WorkspaceID,
			AgentKindID:        step.AgentKindID,
			InitialPrompt:      nil, // 由 SendPromptAndWait 单独发送，确保阻塞等待回合
			OrchestratorStepID: &stepID,
		}
		if requirementNumber > 0 {
			// 绑定到 req 分支 worktree，使同一 run 的各阶段共享同一 worktree。
			rn := requirementNumber
			in.RequirementNumber = &rn
		}
		created, cerr := r.sessions.Create(ctx, owner, in)
		if cerr != nil {
			return turnResult{}, cerr
		}
		sessionID = created.ID
	}

	if sessionID == uuid.Nil {
		return turnResult{}, errs.New(errs.CodeInternal, "orchestrator: no session id after spawn/resume")
	}

	turnCtx, cancel := context.WithTimeout(ctx, r.turnTimeout)
	defer cancel()
	stop, err := r.turns.SendPromptAndWait(turnCtx, sessionID, step.Prompt)
	if err != nil {
		return turnResult{SessionID: sessionID}, err
	}
	return turnResult{SessionID: sessionID, StopReason: stop}, nil
}
