package project

import (
	"context"
	"testing"

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

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

// mkIssue 在 demo 项目创建一个游离 issue，返回其编号。
func mkIssue(t *testing.T, svc IssueService, caller Caller, title string) int {
	t.Helper()
	i, err := svc.Create(context.Background(), caller, "demo", CreateIssueInput{Title: title})
	require.NoError(t, err)
	return i.Number
}

func TestCreateSubtask_SetsParentAndPriority(t *testing.T) {
	repo := newFakeRepo()
	owner := uuid.New()
	seedProject(t, repo, owner)
	svc, _, _ := newIssueSvc(repo)
	caller := Caller{UserID: owner}

	parentNum := mkIssue(t, svc, caller, "Parent")
	prio := 2
	sub, err := svc.CreateSubtask(context.Background(), caller, "demo", parentNum,
		CreateIssueInput{Title: "Child", Priority: &prio})
	require.NoError(t, err)
	require.NotNil(t, sub.ParentID)
	require.Equal(t, 2, sub.Priority)

	// 父不存在 → NotFound
	_, err = svc.CreateSubtask(context.Background(), caller, "demo", 9999, CreateIssueInput{Title: "X"})
	ae, ok := errs.As(err)
	require.True(t, ok)
	require.Equal(t, errs.CodeNotFound, ae.Code)
}

func TestCreateIssue_RejectsBadPriority(t *testing.T) {
	repo := newFakeRepo()
	owner := uuid.New()
	seedProject(t, repo, owner)
	svc, _, _ := newIssueSvc(repo)
	caller := Caller{UserID: owner}

	bad := 5
	_, err := svc.Create(context.Background(), caller, "demo", CreateIssueInput{Title: "X", Priority: &bad})
	ae, ok := errs.As(err)
	require.True(t, ok)
	require.Equal(t, errs.CodeInvalidInput, ae.Code)
}

func TestAddDependency_RejectsSelf(t *testing.T) {
	repo := newFakeRepo()
	owner := uuid.New()
	seedProject(t, repo, owner)
	svc, _, _ := newIssueSvc(repo)
	caller := Caller{UserID: owner}

	a := mkIssue(t, svc, caller, "A")
	_, err := svc.AddDependency(context.Background(), caller, "demo",
		AddDependencyInput{BlockedNumber: a, BlockerNumber: a})
	ae, ok := errs.As(err)
	require.True(t, ok)
	require.Equal(t, errs.CodeInvalidInput, ae.Code)
}

func TestAddDependency_RejectsDuplicate(t *testing.T) {
	repo := newFakeRepo()
	owner := uuid.New()
	seedProject(t, repo, owner)
	svc, _, _ := newIssueSvc(repo)
	caller := Caller{UserID: owner}

	a := mkIssue(t, svc, caller, "A")
	b := mkIssue(t, svc, caller, "B")
	_, err := svc.AddDependency(context.Background(), caller, "demo",
		AddDependencyInput{BlockedNumber: a, BlockerNumber: b})
	require.NoError(t, err)
	_, err = svc.AddDependency(context.Background(), caller, "demo",
		AddDependencyInput{BlockedNumber: a, BlockerNumber: b})
	ae, ok := errs.As(err)
	require.True(t, ok)
	require.Equal(t, errs.CodeInvalidInput, ae.Code)
}

func TestAddDependency_RejectsDirectCycle(t *testing.T) {
	repo := newFakeRepo()
	owner := uuid.New()
	seedProject(t, repo, owner)
	svc, _, _ := newIssueSvc(repo)
	caller := Caller{UserID: owner}

	a := mkIssue(t, svc, caller, "A")
	b := mkIssue(t, svc, caller, "B")
	// A blocked-by B
	_, err := svc.AddDependency(context.Background(), caller, "demo",
		AddDependencyInput{BlockedNumber: a, BlockerNumber: b})
	require.NoError(t, err)
	// B blocked-by A → 直接环，应拒绝
	_, err = svc.AddDependency(context.Background(), caller, "demo",
		AddDependencyInput{BlockedNumber: b, BlockerNumber: a})
	ae, ok := errs.As(err)
	require.True(t, ok)
	require.Equal(t, errs.CodeInvalidInput, ae.Code)
}

func TestAddDependency_RejectsTransitiveCycle(t *testing.T) {
	repo := newFakeRepo()
	owner := uuid.New()
	seedProject(t, repo, owner)
	svc, _, _ := newIssueSvc(repo)
	caller := Caller{UserID: owner}

	a := mkIssue(t, svc, caller, "A")
	b := mkIssue(t, svc, caller, "B")
	c := mkIssue(t, svc, caller, "C")
	// A blocked-by B, B blocked-by C
	_, err := svc.AddDependency(context.Background(), caller, "demo",
		AddDependencyInput{BlockedNumber: a, BlockerNumber: b})
	require.NoError(t, err)
	_, err = svc.AddDependency(context.Background(), caller, "demo",
		AddDependencyInput{BlockedNumber: b, BlockerNumber: c})
	require.NoError(t, err)
	// C blocked-by A → A->B->C->A 传递环，应拒绝
	_, err = svc.AddDependency(context.Background(), caller, "demo",
		AddDependencyInput{BlockedNumber: c, BlockerNumber: a})
	ae, ok := errs.As(err)
	require.True(t, ok)
	require.Equal(t, errs.CodeInvalidInput, ae.Code)
}

func TestAddDependency_AllowsValidDAG(t *testing.T) {
	repo := newFakeRepo()
	owner := uuid.New()
	seedProject(t, repo, owner)
	svc, _, _ := newIssueSvc(repo)
	caller := Caller{UserID: owner}

	a := mkIssue(t, svc, caller, "A")
	b := mkIssue(t, svc, caller, "B")
	c := mkIssue(t, svc, caller, "C")
	// A blocked-by B, A blocked-by C —— 合法 DAG（无环）
	_, err := svc.AddDependency(context.Background(), caller, "demo",
		AddDependencyInput{BlockedNumber: a, BlockerNumber: b})
	require.NoError(t, err)
	_, err = svc.AddDependency(context.Background(), caller, "demo",
		AddDependencyInput{BlockedNumber: a, BlockerNumber: c})
	require.NoError(t, err)

	blockedBy, blocks, err := svc.ListDependencies(context.Background(), caller, "demo", a)
	require.NoError(t, err)
	require.Len(t, blockedBy, 2)
	require.Len(t, blocks, 0)
}

func TestRemoveDependency(t *testing.T) {
	repo := newFakeRepo()
	owner := uuid.New()
	seedProject(t, repo, owner)
	svc, _, _ := newIssueSvc(repo)
	caller := Caller{UserID: owner}

	a := mkIssue(t, svc, caller, "A")
	b := mkIssue(t, svc, caller, "B")
	_, err := svc.AddDependency(context.Background(), caller, "demo",
		AddDependencyInput{BlockedNumber: a, BlockerNumber: b})
	require.NoError(t, err)

	require.NoError(t, svc.RemoveDependency(context.Background(), caller, "demo", a, b))
	// 再删 → NotFound
	err = svc.RemoveDependency(context.Background(), caller, "demo", a, b)
	ae, ok := errs.As(err)
	require.True(t, ok)
	require.Equal(t, errs.CodeNotFound, ae.Code)

	blockedBy, _, err := svc.ListDependencies(context.Background(), caller, "demo", a)
	require.NoError(t, err)
	require.Len(t, blockedBy, 0)
}

func TestListReadyLeafSubtasks_OnlyUnblockedOpen(t *testing.T) {
	repo := newFakeRepo()
	owner := uuid.New()
	seedProject(t, repo, owner)
	svc, _, _ := newIssueSvc(repo)
	caller := Caller{UserID: owner}

	parent := mkIssue(t, svc, caller, "Parent")
	prio1 := 1
	prio3 := 3
	s1, err := svc.CreateSubtask(context.Background(), caller, "demo", parent, CreateIssueInput{Title: "S1", Priority: &prio1})
	require.NoError(t, err)
	s2, err := svc.CreateSubtask(context.Background(), caller, "demo", parent, CreateIssueInput{Title: "S2", Priority: &prio3})
	require.NoError(t, err)
	blocker := mkIssue(t, svc, caller, "Blocker")
	s3, err := svc.CreateSubtask(context.Background(), caller, "demo", parent, CreateIssueInput{Title: "S3"})
	require.NoError(t, err)
	// S3 blocked-by Blocker（未关闭）→ 不就绪
	_, err = svc.AddDependency(context.Background(), caller, "demo",
		AddDependencyInput{BlockedNumber: s3.Number, BlockerNumber: blocker})
	require.NoError(t, err)

	proj, err := repo.GetProjectBySlug(context.Background(), "demo")
	require.NoError(t, err)
	ready, err := repo.ListReadyLeafSubtasks(context.Background(), proj.ID, 10)
	require.NoError(t, err)

	got := map[uuid.UUID]bool{}
	for _, i := range ready {
		got[i.ID] = true
	}
	require.True(t, got[s1.ID])
	require.True(t, got[s2.ID])
	require.False(t, got[s3.ID], "S3 被未关闭 blocker 阻塞，不应就绪")
}
