package project

import (
	"context"
	"testing"

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

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

func newIssueSvc(repo *fakeRepo) (IssueService, RequirementService, *spyAudit) {
	spy := &spyAudit{}
	return NewIssueService(repo, spy), NewRequirementService(repo, spy, nil, nil), spy
}

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

	// 游离
	free, err := issueSvc.Create(context.Background(), caller, "demo", CreateIssueInput{Title: "Free"})
	require.NoError(t, err)
	require.Nil(t, free.RequirementID)

	// 挂载
	r, err := reqSvc.Create(context.Background(), caller, "demo", CreateRequirementInput{Title: "R"})
	require.NoError(t, err)
	num := r.Number
	att, err := issueSvc.Create(context.Background(), caller, "demo", CreateIssueInput{Title: "A", RequirementNumber: &num})
	require.NoError(t, err)
	require.NotNil(t, att.RequirementID)
	require.Equal(t, r.ID, *att.RequirementID)
}

func TestIssueService_Create_RejectsClosedRequirement(t *testing.T) {
	repo := newFakeRepo()
	owner := uuid.New()
	seedProject(t, repo, owner)
	issueSvc, reqSvc, _ := newIssueSvc(repo)
	caller := Caller{UserID: owner}
	r, err := reqSvc.Create(context.Background(), caller, "demo", CreateRequirementInput{Title: "R"})
	require.NoError(t, err)
	require.NoError(t, reqSvc.Close(context.Background(), caller, "demo", r.Number))
	num := r.Number
	_, err = issueSvc.Create(context.Background(), caller, "demo", CreateIssueInput{Title: "X", RequirementNumber: &num})
	ae, ok := errs.As(err)
	require.True(t, ok)
	require.Equal(t, errs.CodeRequirementClosed, ae.Code)
}

func TestIssueService_Update_MoveBetweenRequirementsAndDetach(t *testing.T) {
	repo := newFakeRepo()
	owner := uuid.New()
	seedProject(t, repo, owner)
	issueSvc, reqSvc, _ := newIssueSvc(repo)
	caller := Caller{UserID: owner}
	r1, _ := reqSvc.Create(context.Background(), caller, "demo", CreateRequirementInput{Title: "R1"})
	r2, _ := reqSvc.Create(context.Background(), caller, "demo", CreateRequirementInput{Title: "R2"})
	n1 := r1.Number
	issue, err := issueSvc.Create(context.Background(), caller, "demo", CreateIssueInput{Title: "I", RequirementNumber: &n1})
	require.NoError(t, err)

	// move r1 → r2
	n2 := r2.Number
	pn2 := &n2
	out, err := issueSvc.Update(context.Background(), caller, "demo", issue.Number, UpdateIssueInput{RequirementNumber: &pn2})
	require.NoError(t, err)
	require.Equal(t, r2.ID, *out.RequirementID)

	// detach (传 nil 指针 → 取消挂载)
	var nilPtr *int
	out, err = issueSvc.Update(context.Background(), caller, "demo", issue.Number, UpdateIssueInput{RequirementNumber: &nilPtr})
	require.NoError(t, err)
	require.Nil(t, out.RequirementID)
}

func TestIssueService_Assign_AndUnassign(t *testing.T) {
	repo := newFakeRepo()
	owner := uuid.New()
	seedProject(t, repo, owner)
	issueSvc, _, _ := newIssueSvc(repo)
	caller := Caller{UserID: owner}
	i, _ := issueSvc.Create(context.Background(), caller, "demo", CreateIssueInput{Title: "I"})

	dev := uuid.New()
	out, err := issueSvc.Assign(context.Background(), caller, "demo", i.Number, &dev)
	require.NoError(t, err)
	require.NotNil(t, out.AssigneeID)
	require.Equal(t, dev, *out.AssigneeID)

	out, err = issueSvc.Assign(context.Background(), caller, "demo", i.Number, nil)
	require.NoError(t, err)
	require.Nil(t, out.AssigneeID)
}

func TestIssueService_Update_BlockedWhenProjectArchived(t *testing.T) {
	repo := newFakeRepo()
	owner := uuid.New()
	seedProject(t, repo, owner)
	issueSvc, _, _ := newIssueSvc(repo)
	projSvc := NewProjectService(repo, nil)
	caller := Caller{UserID: owner}
	i, _ := issueSvc.Create(context.Background(), caller, "demo", CreateIssueInput{Title: "I"})

	require.NoError(t, projSvc.Archive(context.Background(), caller, "demo"))
	t1 := "Renamed"
	_, err := issueSvc.Update(context.Background(), caller, "demo", i.Number, UpdateIssueInput{Title: &t1})
	ae, ok := errs.As(err)
	require.True(t, ok)
	require.Equal(t, errs.CodeProjectArchived, ae.Code)
}

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

	wsID := uuid.New()
	out, err := issueSvc.Create(context.Background(), caller, "demo", CreateIssueInput{
		Title: "I", WorkspaceID: &wsID,
	})
	require.NoError(t, err)
	require.NotNil(t, out.WorkspaceID)
	require.Equal(t, wsID, *out.WorkspaceID)

	// 再 Get 一次确认持久化
	got, err := issueSvc.Get(context.Background(), caller, "demo", out.Number)
	require.NoError(t, err)
	require.NotNil(t, got.WorkspaceID)
	require.Equal(t, wsID, *got.WorkspaceID)

	// audit metadata 包含 workspace_id
	require.NotEmpty(t, spy.entries)
	last := spy.entries[len(spy.entries)-1]
	require.Equal(t, "issue.create", last.Action)
	require.Equal(t, wsID.String(), last.Metadata["workspace_id"])
}

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

	wsA := uuid.New()
	i, err := issueSvc.Create(context.Background(), caller, "demo", CreateIssueInput{
		Title: "I", WorkspaceID: &wsA,
	})
	require.NoError(t, err)
	require.NotNil(t, i.WorkspaceID)
	require.Equal(t, wsA, *i.WorkspaceID)

	// 1) nil → 不变（用 Title 强制 changed 非空，走持久化路径，验证 nextWS 不被错误清空）
	newTitle := "I-renamed"
	out, err := issueSvc.Update(context.Background(), caller, "demo", i.Number, UpdateIssueInput{Title: &newTitle})
	require.NoError(t, err)
	require.Equal(t, newTitle, out.Title)
	require.NotNil(t, out.WorkspaceID)
	require.Equal(t, wsA, *out.WorkspaceID)

	// 2) &id（非 Nil）→ 关联到新 workspace
	wsB := uuid.New()
	out, err = issueSvc.Update(context.Background(), caller, "demo", i.Number, UpdateIssueInput{
		WorkspaceID: &wsB,
	})
	require.NoError(t, err)
	require.NotNil(t, out.WorkspaceID)
	require.Equal(t, wsB, *out.WorkspaceID)

	// 3) &uuid.Nil → 显式解绑
	nilUUID := uuid.Nil
	out, err = issueSvc.Update(context.Background(), caller, "demo", i.Number, UpdateIssueInput{
		WorkspaceID: &nilUUID,
	})
	require.NoError(t, err)
	require.Nil(t, out.WorkspaceID)
}
