// decomposition.go 实现 ApplyDecomposition：让规划阶段的 agent 在一次 MCP 调用中
// 原子地提交整张任务分解图（子任务 + blocks/blocked-by 边 + 优先级），而不必发 N 次
// create_subtask + M 次 add_dependency。先在内存中对 in-batch 图做 DAG 校验（拓扑排序），
// 全部通过后再落库：先建全部子任务，再建全部依赖边。校验失败则在任何写入前返回，
// 不留半成品。
package orchestrator

import (
	"context"

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

// SubtaskSpec 描述分解图里的一个子任务。Ref 是 batch 内的本地引用键（如 "a"/"1"），
// 供 EdgeSpec 跨条目引用；落库后映射为真实 issue number。
type SubtaskSpec struct {
	Ref         string
	Title       string
	Description string
	Priority    int
}

// EdgeSpec 描述一条依赖边：BlockedRef 被 BlockerRef 阻塞。两端均为 batch 内 ref。
type EdgeSpec struct {
	BlockedRef string
	BlockerRef string
}

// Decomposition 是一次完整任务分解提交。
type Decomposition struct {
	// ParentNumber 非 nil 时所有子任务挂在该 issue 下（通常是 requirement 的锚定 issue）。
	ParentNumber *int
	Subtasks     []SubtaskSpec
	Edges        []EdgeSpec
}

// DecompositionResult 报告落库结果：ref → 真实 issue number。
type DecompositionResult struct {
	// RefToNumber 把每个 SubtaskSpec.Ref 映射到创建出的 issue number。
	RefToNumber map[string]int
}

// DecompositionApplier 是 ApplyDecomposition 依赖的窄 issue 服务接口（project.IssueService 满足）。
type DecompositionApplier interface {
	CreateSubtask(ctx context.Context, c project.Caller, projectSlug string, parentNumber int, in project.CreateIssueInput) (*project.Issue, error)
	Create(ctx context.Context, c project.Caller, projectSlug string, in project.CreateIssueInput) (*project.Issue, error)
	AddDependency(ctx context.Context, c project.Caller, projectSlug string, in project.AddDependencyInput) (*project.Dependency, error)
}

// ApplyDecomposition 原子（best-effort）地落库一张任务分解图。先校验 in-batch 图是 DAG，
// 再建子任务、建边。返回 ref→number 映射。校验在任何写入前完成，故无效图不留半成品。
func ApplyDecomposition(ctx context.Context, svc DecompositionApplier, c project.Caller, projectSlug string, d Decomposition) (DecompositionResult, error) {
	res := DecompositionResult{RefToNumber: map[string]int{}}
	if len(d.Subtasks) == 0 {
		return res, errs.New(errs.CodeInvalidInput, "decomposition 至少需要一个子任务")
	}

	// 1) ref 去重 + 建集合。
	refSet := make(map[string]struct{}, len(d.Subtasks))
	for _, st := range d.Subtasks {
		if st.Ref == "" {
			return res, errs.New(errs.CodeInvalidInput, "子任务 ref 不能为空")
		}
		if st.Title == "" {
			return res, errs.New(errs.CodeInvalidInput, "子任务 title 不能为空")
		}
		if _, dup := refSet[st.Ref]; dup {
			return res, errs.New(errs.CodeInvalidInput, "子任务 ref 重复: "+st.Ref)
		}
		if st.Priority < 0 || st.Priority > 3 {
			return res, errs.New(errs.CodeInvalidInput, "子任务 priority 必须在 0..3")
		}
		refSet[st.Ref] = struct{}{}
	}

	// 2) 校验边端点存在、非自环。
	for _, e := range d.Edges {
		if _, ok := refSet[e.BlockedRef]; !ok {
			return res, errs.New(errs.CodeInvalidInput, "边引用了未知 blocked ref: "+e.BlockedRef)
		}
		if _, ok := refSet[e.BlockerRef]; !ok {
			return res, errs.New(errs.CodeInvalidInput, "边引用了未知 blocker ref: "+e.BlockerRef)
		}
		if e.BlockedRef == e.BlockerRef {
			return res, errs.New(errs.CodeInvalidInput, "边不能自指: "+e.BlockedRef)
		}
	}

	// 3) DAG 校验：沿 blocked-by 边（blocked → blocker）做拓扑排序，存在环则拒绝。
	if err := validateDAG(d.Subtasks, d.Edges); err != nil {
		return res, err
	}

	// 4) 落库：先建全部子任务（记录 ref→number），再建全部依赖边。
	for _, st := range d.Subtasks {
		prio := st.Priority
		in := project.CreateIssueInput{
			Title:       st.Title,
			Description: st.Description,
			Priority:    &prio,
		}
		var iss *project.Issue
		var err error
		if d.ParentNumber != nil {
			iss, err = svc.CreateSubtask(ctx, c, projectSlug, *d.ParentNumber, in)
		} else {
			iss, err = svc.Create(ctx, c, projectSlug, in)
		}
		if err != nil {
			return res, err
		}
		res.RefToNumber[st.Ref] = iss.Number
	}

	for _, e := range d.Edges {
		if _, err := svc.AddDependency(ctx, c, projectSlug, project.AddDependencyInput{
			BlockedNumber: res.RefToNumber[e.BlockedRef],
			BlockerNumber: res.RefToNumber[e.BlockerRef],
		}); err != nil {
			return res, err
		}
	}

	return res, nil
}

// validateDAG 对 (子任务, 边) 做 Kahn 拓扑排序，存在环返回 CodeInvalidInput。
// 边语义 blocked blocked-by blocker，建图方向 blocker → blocked（依赖先于被依赖）。
func validateDAG(subtasks []SubtaskSpec, edges []EdgeSpec) error {
	indeg := make(map[string]int, len(subtasks))
	for _, st := range subtasks {
		indeg[st.Ref] = 0
	}
	adj := make(map[string][]string, len(subtasks))
	// blocker → blocked：blocked 的入度 +1。
	for _, e := range edges {
		adj[e.BlockerRef] = append(adj[e.BlockerRef], e.BlockedRef)
		indeg[e.BlockedRef]++
	}
	queue := make([]string, 0, len(subtasks))
	for ref, d := range indeg {
		if d == 0 {
			queue = append(queue, ref)
		}
	}
	visited := 0
	for len(queue) > 0 {
		cur := queue[0]
		queue = queue[1:]
		visited++
		for _, next := range adj[cur] {
			indeg[next]--
			if indeg[next] == 0 {
				queue = append(queue, next)
			}
		}
	}
	if visited != len(subtasks) {
		return errs.New(errs.CodeInvalidInput, "decomposition 边集合存在环")
	}
	return nil
}
