// Package project 内的 phase_gate.go 实现"阶段状态机网关"：把原来 ChangePhase
// 的 any-to-any 替换为按目标 phase 声明 entry 网关的谓词引擎。
//
// 设计取舍：
//   - 网关只在"流转进入"目标 phase 时运行，不校验已有状态——历史上已处于
//     implementing/done 的需求（迁移前数据）天然被豁免，无需数据迁移。
//   - 未声明网关的目标 phase（planning/prototyping/auditing/reviewing）保持
//     any-to-any 旧行为（空网关列表 = 永远放行）。
//   - 网关依赖通过 GateDeps 注入（Repo 查阶段指针、Workspace 查 PR 合并状态），
//     便于单测用 fake 覆盖；Done 网关在 Workspace 缺失时按"未合并"优雅降级。
package project

import (
	"context"

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

// PhaseGate 是单个 entry 谓词：返回 nil 表示 from->to 对 r 允许。
type PhaseGate interface {
	Check(ctx context.Context, dep GateDeps, r *Requirement, from, to Phase) error
}

// GateDeps 是网关运行所需的外部依赖。Workspace 可为 nil（Done 网关据此降级为
// "未合并"，从而阻止流转——更安全的默认）。
type GateDeps struct {
	Repo      Repository
	Workspace WorkspaceStateLookup
}

// phaseGates 是 target phase -> entry 网关列表的转换表。只有 implementing/done
// 声明网关；其余 phase 列表为空（保持 any-to-any）。
//
//	implementing: 需要 auditing 阶段有 verdict=pass 的已批准产物（阶段指针就绪）
//	done:         需要 requirement 关联 workspace 且其 PR 已合并
var phaseGates = map[Phase][]PhaseGate{
	PhaseImplementing: {requireApprovedArtifact(PhaseAuditing)},
	PhaseDone:         {requireWorkspaceMerged{}},
}

// gateRunner 是 GateRunner 的默认实现：解析 to 的网关列表并逐个运行。
type gateRunner struct {
	gates map[Phase][]PhaseGate
	dep   GateDeps
}

// NewGateRunner 用转换表与依赖构造 GateRunner。gates 传 nil 时用包内默认表。
func NewGateRunner(dep GateDeps, gates map[Phase][]PhaseGate) GateRunner {
	if gates == nil {
		gates = phaseGates
	}
	return &gateRunner{gates: gates, dep: dep}
}

// Check 运行目标 phase 的全部 entry 网关，遇首个失败即返回。
func (g *gateRunner) Check(ctx context.Context, r *Requirement, from, to Phase) error {
	for _, gate := range g.gates[to] {
		if err := gate.Check(ctx, g.dep, r, from, to); err != nil {
			return err
		}
	}
	return nil
}

// requireApprovedArtifact 网关：命名 phase 必须存在已批准（指针非空）且其产物
// verdict==pass 的产物。implementing 据此要求 auditing 报告先过审。
type requireApprovedArtifact Phase

func (g requireApprovedArtifact) Check(ctx context.Context, dep GateDeps, r *Requirement, _, to Phase) error {
	gatedPhase := Phase(g)
	ptr, err := dep.Repo.GetPhasePointer(ctx, r.ID, gatedPhase)
	if err != nil {
		if ae, ok := errs.As(err); ok && ae.Code == errs.CodeNotFound {
			return errs.New(errs.CodePhaseGateFailed,
				"流转到 "+string(to)+" 前需先批准 "+string(gatedPhase)+" 阶段产物（verdict=pass）")
		}
		return err
	}
	if ptr.ApprovedArtifactID == nil {
		return errs.New(errs.CodePhaseGateFailed,
			"流转到 "+string(to)+" 前需先批准 "+string(gatedPhase)+" 阶段产物（verdict=pass）")
	}
	art, err := dep.Repo.GetArtifactByID(ctx, *ptr.ApprovedArtifactID)
	if err != nil {
		return err
	}
	if art.Verdict != VerdictPass {
		return errs.New(errs.CodePhaseGateFailed,
			"流转到 "+string(to)+" 前 "+string(gatedPhase)+" 阶段已批准产物的 verdict 必须为 pass")
	}
	return nil
}

// requireWorkspaceMerged 网关：requirement 必须关联 workspace，且该 workspace 的
// PR 已合并。Workspace 依赖缺失或查询失败时优雅降级为"未合并"（阻止流转）。
type requireWorkspaceMerged struct{}

func (requireWorkspaceMerged) Check(ctx context.Context, dep GateDeps, r *Requirement, _, to Phase) error {
	if r.WorkspaceID == nil {
		return errs.New(errs.CodePhaseGateFailed,
			"流转到 "+string(to)+" 前需先关联 workspace 并合入其 PR")
	}
	if dep.Workspace == nil {
		return errs.New(errs.CodePhaseGateFailed,
			"无法确认 workspace PR 合并状态，暂不允许流转到 "+string(to))
	}
	merged, err := dep.Workspace.PullRequestMerged(ctx, *r.WorkspaceID, r.ID)
	if err != nil {
		return errs.Wrap(err, errs.CodePhaseGateFailed, "查询 workspace PR 合并状态失败")
	}
	if !merged {
		return errs.New(errs.CodePhaseGateFailed,
			"流转到 "+string(to)+" 前需先合入 workspace 的 PR")
	}
	return nil
}
