package orchestrator

import (
	"context"
	"testing"

	"github.com/google/uuid"
	"github.com/stretchr/testify/require"

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

// fakeApplier: 内存版 DecompositionApplier，记录建出的 issue 与边。
type fakeApplier struct {
	nextNum int
	created []string               // titles in order
	edges   [][2]int               // (blockedNumber, blockerNumber)
	byNum   map[int]*project.Issue // number → issue
}

func newFakeApplier() *fakeApplier {
	return &fakeApplier{byNum: map[int]*project.Issue{}}
}

func (f *fakeApplier) mk(in project.CreateIssueInput, parent *int) *project.Issue {
	f.nextNum++
	prio := 0
	if in.Priority != nil {
		prio = *in.Priority
	}
	var pid *uuid.UUID
	if parent != nil {
		id := uuid.New()
		pid = &id
	}
	iss := &project.Issue{ID: uuid.New(), Number: f.nextNum, Title: in.Title, Priority: prio, ParentID: pid}
	f.byNum[iss.Number] = iss
	f.created = append(f.created, in.Title)
	return iss
}

func (f *fakeApplier) Create(_ context.Context, _ project.Caller, _ string, in project.CreateIssueInput) (*project.Issue, error) {
	return f.mk(in, nil), nil
}
func (f *fakeApplier) CreateSubtask(_ context.Context, _ project.Caller, _ string, parentNumber int, in project.CreateIssueInput) (*project.Issue, error) {
	return f.mk(in, &parentNumber), nil
}
func (f *fakeApplier) AddDependency(_ context.Context, _ project.Caller, _ string, in project.AddDependencyInput) (*project.Dependency, error) {
	f.edges = append(f.edges, [2]int{in.BlockedNumber, in.BlockerNumber})
	return &project.Dependency{ID: uuid.New()}, nil
}

func TestApplyDecomposition_RejectsCycleBeforeAnyWrite(t *testing.T) {
	f := newFakeApplier()
	d := Decomposition{
		Subtasks: []SubtaskSpec{{Ref: "a", Title: "A"}, {Ref: "b", Title: "B"}},
		Edges: []EdgeSpec{
			{BlockedRef: "a", BlockerRef: "b"},
			{BlockedRef: "b", BlockerRef: "a"}, // 环
		},
	}
	_, err := ApplyDecomposition(context.Background(), f, project.Caller{}, "demo", d)
	ae, ok := errs.As(err)
	require.True(t, ok)
	require.Equal(t, errs.CodeInvalidInput, ae.Code)
	require.Empty(t, f.created, "校验失败前不应有任何写入")
	require.Empty(t, f.edges)
}

func TestApplyDecomposition_ValidDAGCreatesIssuesThenEdges(t *testing.T) {
	f := newFakeApplier()
	parent := 100
	f.byNum[parent] = &project.Issue{Number: parent}
	d := Decomposition{
		ParentNumber: &parent,
		Subtasks: []SubtaskSpec{
			{Ref: "a", Title: "A", Priority: 2},
			{Ref: "b", Title: "B"},
			{Ref: "c", Title: "C"},
		},
		Edges: []EdgeSpec{
			{BlockedRef: "a", BlockerRef: "b"},
			{BlockedRef: "a", BlockerRef: "c"},
		},
	}
	res, err := ApplyDecomposition(context.Background(), f, project.Caller{}, "demo", d)
	require.NoError(t, err)
	require.Len(t, f.created, 3)
	require.Len(t, f.edges, 2)
	// refs 映射到真实 number
	require.Contains(t, res.RefToNumber, "a")
	require.Contains(t, res.RefToNumber, "b")
	require.Contains(t, res.RefToNumber, "c")
	// 边以真实 number 落库
	na, nb := res.RefToNumber["a"], res.RefToNumber["b"]
	require.Contains(t, f.edges, [2]int{na, nb})
}

func TestApplyDecomposition_RejectsEmpty(t *testing.T) {
	f := newFakeApplier()
	_, err := ApplyDecomposition(context.Background(), f, project.Caller{}, "demo", Decomposition{})
	ae, ok := errs.As(err)
	require.True(t, ok)
	require.Equal(t, errs.CodeInvalidInput, ae.Code)
}

func TestApplyDecomposition_RejectsUnknownEdgeRef(t *testing.T) {
	f := newFakeApplier()
	d := Decomposition{
		Subtasks: []SubtaskSpec{{Ref: "a", Title: "A"}},
		Edges:    []EdgeSpec{{BlockedRef: "a", BlockerRef: "zzz"}},
	}
	_, err := ApplyDecomposition(context.Background(), f, project.Caller{}, "demo", d)
	ae, ok := errs.As(err)
	require.True(t, ok)
	require.Equal(t, errs.CodeInvalidInput, ae.Code)
	require.Empty(t, f.created)
}

func TestApplyDecomposition_RejectsDuplicateRef(t *testing.T) {
	f := newFakeApplier()
	d := Decomposition{
		Subtasks: []SubtaskSpec{{Ref: "a", Title: "A"}, {Ref: "a", Title: "A2"}},
	}
	_, err := ApplyDecomposition(context.Background(), f, project.Caller{}, "demo", d)
	ae, ok := errs.As(err)
	require.True(t, ok)
	require.Equal(t, errs.CodeInvalidInput, ae.Code)
}
