package project

import (
	"context"
	"testing"

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

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

func newArtifactSvc(repo *fakeRepo) (ArtifactService, RequirementService, ProjectService, *spyAudit) {
	spy := &spyAudit{}
	return NewArtifactService(repo, spy), NewRequirementService(repo, spy, nil, nil), NewProjectService(repo, spy), spy
}

func TestArtifactService_Create_VersionAutoIncrement(t *testing.T) {
	repo := newFakeRepo()
	owner := uuid.New()
	seedProject(t, repo, owner)
	artSvc, reqSvc, _, spy := newArtifactSvc(repo)
	caller := Caller{UserID: owner}

	r, err := reqSvc.Create(context.Background(), caller, "demo", CreateRequirementInput{Title: "R"})
	require.NoError(t, err)

	a1, err := artSvc.Create(context.Background(), caller, "demo", r.Number, CreateArtifactInput{Phase: PhasePlanning, Content: "v1"})
	require.NoError(t, err)
	require.Equal(t, 1, a1.Version)

	a2, err := artSvc.Create(context.Background(), caller, "demo", r.Number, CreateArtifactInput{Phase: PhasePlanning, Content: "v2", Note: "second"})
	require.NoError(t, err)
	require.Equal(t, 2, a2.Version)
	require.Equal(t, "second", a2.Note)

	require.Contains(t, spy.actions(), "requirement_artifact.create")
}

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

	r, err := reqSvc.Create(context.Background(), caller, "demo", CreateRequirementInput{Title: "R"})
	require.NoError(t, err)

	// planning 走到 v2，prototyping 的版本序列从 1 重新开始。
	_, err = artSvc.Create(context.Background(), caller, "demo", r.Number, CreateArtifactInput{Phase: PhasePlanning, Content: "p1"})
	require.NoError(t, err)
	_, err = artSvc.Create(context.Background(), caller, "demo", r.Number, CreateArtifactInput{Phase: PhasePlanning, Content: "p2"})
	require.NoError(t, err)

	proto, err := artSvc.Create(context.Background(), caller, "demo", r.Number, CreateArtifactInput{Phase: PhasePrototyping, Content: "x1"})
	require.NoError(t, err)
	require.Equal(t, 1, proto.Version)
}

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

	r, err := reqSvc.Create(context.Background(), caller, "demo", CreateRequirementInput{Title: "R"})
	require.NoError(t, err)

	for _, phase := range []Phase{PhaseDone, Phase("bogus"), Phase("")} {
		_, err = artSvc.Create(context.Background(), caller, "demo", r.Number, CreateArtifactInput{Phase: phase, Content: "x"})
		ae, ok := errs.As(err)
		require.True(t, ok, "phase=%s", phase)
		require.Equal(t, errs.CodeInvalidInput, ae.Code, "phase=%s", phase)
	}
}

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

	r, err := reqSvc.Create(context.Background(), caller, "demo", CreateRequirementInput{Title: "R"})
	require.NoError(t, err)

	// 首轮 CreateArtifact 撞 UNIQUE，service 应捕获并重试一次后成功。
	repo.createArtifactFailOnce = true
	a, err := artSvc.Create(context.Background(), caller, "demo", r.Number, CreateArtifactInput{Phase: PhasePlanning, Content: "v1"})
	require.NoError(t, err)
	require.Equal(t, 1, a.Version)
	require.False(t, repo.createArtifactFailOnce, "失败开关应已被消费")
}

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

	r, err := reqSvc.Create(context.Background(), caller, "demo", CreateRequirementInput{Title: "R"})
	require.NoError(t, err)

	_, err = artSvc.Create(context.Background(), caller, "demo", r.Number, CreateArtifactInput{Phase: PhasePlanning, Content: ""})
	ae, ok := errs.As(err)
	require.True(t, ok)
	require.Equal(t, errs.CodeInvalidInput, ae.Code)
}

func TestArtifactService_Create_BlockedWhenRequirementClosed(t *testing.T) {
	repo := newFakeRepo()
	owner := uuid.New()
	seedProject(t, repo, owner)
	artSvc, reqSvc, _, _ := newArtifactSvc(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))

	_, err = artSvc.Create(context.Background(), caller, "demo", r.Number, CreateArtifactInput{Phase: PhasePlanning, Content: "v1"})
	ae, ok := errs.As(err)
	require.True(t, ok)
	require.Equal(t, errs.CodeRequirementClosed, ae.Code)
}

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

	r, err := reqSvc.Create(context.Background(), caller, "demo", CreateRequirementInput{Title: "R"})
	require.NoError(t, err)

	srcID := int64(42)
	a, err := artSvc.Create(context.Background(), caller, "demo", r.Number, CreateArtifactInput{
		Phase: PhaseAuditing, Content: "from chat", SourceMessageID: &srcID,
	})
	require.NoError(t, err)
	require.NotNil(t, a.SourceMessageID)
	require.Equal(t, srcID, *a.SourceMessageID)
}

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

	r, err := reqSvc.Create(context.Background(), caller, "demo", CreateRequirementInput{Title: "R"})
	require.NoError(t, err)
	for i := 0; i < 2; i++ {
		_, err := artSvc.Create(context.Background(), caller, "demo", r.Number, CreateArtifactInput{Phase: PhasePlanning, Content: "x"})
		require.NoError(t, err)
	}
	_, err = artSvc.Create(context.Background(), caller, "demo", r.Number, CreateArtifactInput{Phase: PhaseAuditing, Content: "y"})
	require.NoError(t, err)

	planning, err := artSvc.List(context.Background(), caller, "demo", r.Number, PhasePlanning)
	require.NoError(t, err)
	require.Len(t, planning, 2)
	require.Equal(t, 1, planning[0].Version)
	require.Equal(t, 2, planning[1].Version)

	all, err := artSvc.List(context.Background(), caller, "demo", r.Number, "")
	require.NoError(t, err)
	require.Len(t, all, 3)

	_, err = artSvc.List(context.Background(), caller, "demo", r.Number, Phase("bogus"))
	ae, ok := errs.As(err)
	require.True(t, ok)
	require.Equal(t, errs.CodeInvalidInput, ae.Code)
}

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

	r, err := reqSvc.Create(context.Background(), caller, "demo", CreateRequirementInput{Title: "R"})
	require.NoError(t, err)

	_, err = artSvc.GetByVersion(context.Background(), caller, "demo", r.Number, PhasePlanning, 99)
	ae, ok := errs.As(err)
	require.True(t, ok)
	require.Equal(t, errs.CodeNotFound, ae.Code)
}

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

	r, err := reqSvc.Create(context.Background(), caller, "demo", CreateRequirementInput{Title: "R"})
	require.NoError(t, err)
	_, err = artSvc.Create(context.Background(), caller, "demo", r.Number, CreateArtifactInput{Phase: PhasePrototyping, Content: "hello", Note: "n"})
	require.NoError(t, err)

	got, err := artSvc.GetByVersion(context.Background(), caller, "demo", r.Number, PhasePrototyping, 1)
	require.NoError(t, err)
	require.Equal(t, "hello", got.Content)
	require.Equal(t, "n", got.Note)
	require.Equal(t, owner, got.CreatedBy)
}

func TestArtifactService_Create_ForbiddenForOutsider(t *testing.T) {
	repo := newFakeRepo()
	owner := uuid.New()
	seedProject(t, repo, owner)
	artSvc, reqSvc, _, _ := newArtifactSvc(repo)
	owns := Caller{UserID: owner}

	r, err := reqSvc.Create(context.Background(), owns, "demo", CreateRequirementInput{Title: "R"})
	require.NoError(t, err)

	// 私有项目下，非 owner / 非 admin 调用者写入被拒（统一返回 not_found 防探测）。
	outsider := Caller{UserID: uuid.New()}
	_, err = artSvc.Create(context.Background(), outsider, "demo", r.Number, CreateArtifactInput{Phase: PhasePlanning, Content: "v1"})
	ae, ok := errs.As(err)
	require.True(t, ok)
	require.Equal(t, errs.CodeNotFound, ae.Code)
}

func TestArtifactService_List_ForbiddenForOutsider(t *testing.T) {
	repo := newFakeRepo()
	owner := uuid.New()
	seedProject(t, repo, owner)
	artSvc, reqSvc, _, _ := newArtifactSvc(repo)
	owns := Caller{UserID: owner}

	r, err := reqSvc.Create(context.Background(), owns, "demo", CreateRequirementInput{Title: "R"})
	require.NoError(t, err)

	outsider := Caller{UserID: uuid.New()}
	_, err = artSvc.List(context.Background(), outsider, "demo", r.Number, "")
	ae, ok := errs.As(err)
	require.True(t, ok)
	require.Equal(t, errs.CodeNotFound, ae.Code)
}

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

	r, err := reqSvc.Create(context.Background(), caller, "demo", CreateRequirementInput{Title: "R"})
	require.NoError(t, err)

	// 扩展后的 ArtifactPhases 接受 implementing/reviewing。
	require.True(t, PhaseImplementing.IsArtifactPhase())
	require.True(t, PhaseReviewing.IsArtifactPhase())
	for _, phase := range []Phase{PhaseImplementing, PhaseReviewing} {
		a, err := artSvc.Create(context.Background(), caller, "demo", r.Number, CreateArtifactInput{Phase: phase, Content: "x"})
		require.NoError(t, err, "phase=%s", phase)
		require.Equal(t, VerdictNone, a.Verdict)
	}
}

func TestArtifactService_Approve_SetsVerdictAndPointer(t *testing.T) {
	repo := newFakeRepo()
	owner := uuid.New()
	seedProject(t, repo, owner)
	artSvc, _, _, spy := newArtifactSvc(repo)
	r := seedRequirement(t, repo, owner, nil)
	caller := Caller{UserID: owner}

	_, err := artSvc.Create(context.Background(), caller, "demo", r.Number, CreateArtifactInput{Phase: PhaseAuditing, Content: "report"})
	require.NoError(t, err)

	out, err := artSvc.Approve(context.Background(), caller, "demo", r.Number, PhaseAuditing, 1, VerdictPass)
	require.NoError(t, err)
	require.Equal(t, VerdictPass, out.Verdict)

	ptr, err := repo.GetPhasePointer(context.Background(), r.ID, PhaseAuditing)
	require.NoError(t, err)
	require.NotNil(t, ptr.ApprovedArtifactID)
	require.Equal(t, out.ID, *ptr.ApprovedArtifactID)
	require.Contains(t, spy.actions(), "requirement_artifact.approve")
}

func TestArtifactService_Approve_FailDoesNotSetPointer(t *testing.T) {
	repo := newFakeRepo()
	owner := uuid.New()
	seedProject(t, repo, owner)
	artSvc, _, _, _ := newArtifactSvc(repo)
	r := seedRequirement(t, repo, owner, nil)
	caller := Caller{UserID: owner}

	_, err := artSvc.Create(context.Background(), caller, "demo", r.Number, CreateArtifactInput{Phase: PhaseAuditing, Content: "report"})
	require.NoError(t, err)

	out, err := artSvc.Approve(context.Background(), caller, "demo", r.Number, PhaseAuditing, 1, VerdictFail)
	require.NoError(t, err)
	require.Equal(t, VerdictFail, out.Verdict)

	_, err = repo.GetPhasePointer(context.Background(), r.ID, PhaseAuditing)
	ae, ok := errs.As(err)
	require.True(t, ok)
	require.Equal(t, errs.CodeNotFound, ae.Code)
}

func TestArtifactService_Approve_MovesPointerToNewerVersion(t *testing.T) {
	repo := newFakeRepo()
	owner := uuid.New()
	seedProject(t, repo, owner)
	artSvc, _, _, _ := newArtifactSvc(repo)
	r := seedRequirement(t, repo, owner, nil)
	caller := Caller{UserID: owner}

	_, err := artSvc.Create(context.Background(), caller, "demo", r.Number, CreateArtifactInput{Phase: PhaseAuditing, Content: "v1"})
	require.NoError(t, err)
	v2, err := artSvc.Create(context.Background(), caller, "demo", r.Number, CreateArtifactInput{Phase: PhaseAuditing, Content: "v2"})
	require.NoError(t, err)

	_, err = artSvc.Approve(context.Background(), caller, "demo", r.Number, PhaseAuditing, 1, VerdictPass)
	require.NoError(t, err)
	_, err = artSvc.Approve(context.Background(), caller, "demo", r.Number, PhaseAuditing, 2, VerdictPass)
	require.NoError(t, err)

	ptr, err := repo.GetPhasePointer(context.Background(), r.ID, PhaseAuditing)
	require.NoError(t, err)
	require.Equal(t, v2.ID, *ptr.ApprovedArtifactID)
}

func TestArtifactService_Approve_BlockedWhenClosed(t *testing.T) {
	repo := newFakeRepo()
	owner := uuid.New()
	seedProject(t, repo, owner)
	artSvc, reqSvc, _, _ := newArtifactSvc(repo)
	r := seedRequirement(t, repo, owner, nil)
	caller := Caller{UserID: owner}

	_, err := artSvc.Create(context.Background(), caller, "demo", r.Number, CreateArtifactInput{Phase: PhaseAuditing, Content: "report"})
	require.NoError(t, err)
	require.NoError(t, reqSvc.Close(context.Background(), caller, "demo", r.Number))

	_, err = artSvc.Approve(context.Background(), caller, "demo", r.Number, PhaseAuditing, 1, VerdictPass)
	ae, ok := errs.As(err)
	require.True(t, ok)
	require.Equal(t, errs.CodeRequirementClosed, ae.Code)
}

func TestArtifactService_Approve_RejectsInvalidVerdict(t *testing.T) {
	repo := newFakeRepo()
	owner := uuid.New()
	seedProject(t, repo, owner)
	artSvc, _, _, _ := newArtifactSvc(repo)
	r := seedRequirement(t, repo, owner, nil)
	caller := Caller{UserID: owner}

	_, err := artSvc.Create(context.Background(), caller, "demo", r.Number, CreateArtifactInput{Phase: PhaseAuditing, Content: "report"})
	require.NoError(t, err)

	_, err = artSvc.Approve(context.Background(), caller, "demo", r.Number, PhaseAuditing, 1, VerdictNone)
	ae, ok := errs.As(err)
	require.True(t, ok)
	require.Equal(t, errs.CodeInvalidInput, ae.Code)
}
