package project

import (
	"context"
	"testing"

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

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

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

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

	r1, err := svc.Create(context.Background(), caller, "demo", CreateRequirementInput{Title: "First"})
	require.NoError(t, err)
	require.Equal(t, 1, r1.Number)

	r2, err := svc.Create(context.Background(), caller, "demo", CreateRequirementInput{Title: "Second"})
	require.NoError(t, err)
	require.Equal(t, 2, r2.Number)

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

func TestRequirementService_PhaseChange_FollowsEnum(t *testing.T) {
	repo := newFakeRepo()
	owner := uuid.New()
	seedProject(t, repo, owner)
	svc, _, _ := newReqSvc(repo)
	caller := Caller{UserID: owner}
	r, err := svc.Create(context.Background(), caller, "demo", CreateRequirementInput{Title: "T"})
	require.NoError(t, err)

	// 任意跳跃合法：planning → reviewing
	out, err := svc.ChangePhase(context.Background(), caller, "demo", r.Number, PhaseReviewing)
	require.NoError(t, err)
	require.Equal(t, PhaseReviewing, out.Phase)

	// 非枚举字符串 → invalid
	_, err = svc.ChangePhase(context.Background(), caller, "demo", r.Number, "bogus")
	ae, ok := errs.As(err)
	require.True(t, ok)
	require.Equal(t, errs.CodeInvalidInput, ae.Code)
}

func TestRequirementService_PhaseChange_BlockedWhenClosed(t *testing.T) {
	repo := newFakeRepo()
	owner := uuid.New()
	seedProject(t, repo, owner)
	svc, _, _ := newReqSvc(repo)
	caller := Caller{UserID: owner}
	r, err := svc.Create(context.Background(), caller, "demo", CreateRequirementInput{Title: "T"})
	require.NoError(t, err)
	require.NoError(t, svc.Close(context.Background(), caller, "demo", r.Number))

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

func TestRequirementService_Write_BlockedWhenProjectArchived(t *testing.T) {
	repo := newFakeRepo()
	owner := uuid.New()
	seedProject(t, repo, owner)
	reqSvc, projSvc, _ := newReqSvc(repo)
	caller := Caller{UserID: owner}
	require.NoError(t, projSvc.Archive(context.Background(), caller, "demo"))

	_, err := reqSvc.Create(context.Background(), caller, "demo", CreateRequirementInput{Title: "X"})
	ae, ok := errs.As(err)
	require.True(t, ok)
	require.Equal(t, errs.CodeProjectArchived, ae.Code)
}

func TestRequirementService_Reopen_AllowsPhaseAgain(t *testing.T) {
	repo := newFakeRepo()
	owner := uuid.New()
	seedProject(t, repo, owner)
	svc, _, _ := newReqSvc(repo)
	caller := Caller{UserID: owner}
	r, err := svc.Create(context.Background(), caller, "demo", CreateRequirementInput{Title: "T"})
	require.NoError(t, err)
	require.NoError(t, svc.Close(context.Background(), caller, "demo", r.Number))
	require.NoError(t, svc.Reopen(context.Background(), caller, "demo", r.Number))

	out, err := svc.ChangePhase(context.Background(), caller, "demo", r.Number, PhaseDone)
	require.NoError(t, err)
	require.Equal(t, PhaseDone, out.Phase)
}

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

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

	// 再 Get 一次确认持久化
	got, err := svc.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)
	var createEntry *audit.Entry
	for i := range spy.entries {
		if spy.entries[i].Action == "requirement.create" {
			createEntry = &spy.entries[i]
		}
	}
	require.NotNil(t, createEntry)
	require.Equal(t, wsID.String(), createEntry.Metadata["workspace_id"])
}

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

	wsA := uuid.New()
	r, err := svc.Create(context.Background(), caller, "demo", CreateRequirementInput{
		Title: "R", WorkspaceID: &wsA,
	})
	require.NoError(t, err)
	require.NotNil(t, r.WorkspaceID)
	require.Equal(t, wsA, *r.WorkspaceID)

	// 1) nil → 不变（结合一个其它 patch 字段触发 update）
	newTitle := "R2"
	out, err := svc.Update(context.Background(), caller, "demo", r.Number, UpdateRequirementInput{
		Title: &newTitle,
	})
	require.NoError(t, err)
	require.NotNil(t, out.WorkspaceID)
	require.Equal(t, wsA, *out.WorkspaceID)

	// 2) &id（非 Nil）→ 关联到新 workspace
	wsB := uuid.New()
	out, err = svc.Update(context.Background(), caller, "demo", r.Number, UpdateRequirementInput{
		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 = svc.Update(context.Background(), caller, "demo", r.Number, UpdateRequirementInput{
		WorkspaceID: &nilUUID,
	})
	require.NoError(t, err)
	require.Nil(t, out.WorkspaceID)
}

// fakeEmitter 收集 EmitPhaseChange 调用，便于断言事件发出且仅发一次。
type fakeEmitter struct {
	events []PhaseChangeEvent
}

func (e *fakeEmitter) EmitPhaseChange(_ context.Context, ev PhaseChangeEvent) {
	e.events = append(e.events, ev)
}

func newReqSvcWith(repo *fakeRepo, emitter PhaseEventEmitter, gate GateRunner) (RequirementService, *spyAudit) {
	spy := &spyAudit{}
	return NewRequirementService(repo, spy, emitter, gate), spy
}

func TestRequirementService_ChangePhase_EmitsEvent(t *testing.T) {
	repo := newFakeRepo()
	owner := uuid.New()
	seedProject(t, repo, owner)
	emitter := &fakeEmitter{}
	svc, spy := newReqSvcWith(repo, emitter, nil)
	caller := Caller{UserID: owner}

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

	out, err := svc.ChangePhase(context.Background(), caller, "demo", r.Number, PhaseAuditing)
	require.NoError(t, err)
	require.Equal(t, PhaseAuditing, out.Phase)

	require.Len(t, emitter.events, 1)
	ev := emitter.events[0]
	require.Equal(t, PhasePlanning, ev.From)
	require.Equal(t, PhaseAuditing, ev.To)
	require.Equal(t, "demo", ev.ProjectSlug)
	require.Equal(t, r.Number, ev.RequirementNumber)
	require.Equal(t, owner, ev.ActorID)
	// 事件与审计互补：审计仍记一条。
	require.Contains(t, spy.actions(), "requirement.phase_change")
}

func TestRequirementService_ChangePhase_NoopEmitsNothing(t *testing.T) {
	repo := newFakeRepo()
	owner := uuid.New()
	seedProject(t, repo, owner)
	emitter := &fakeEmitter{}
	svc, _ := newReqSvcWith(repo, emitter, nil)
	caller := Caller{UserID: owner}

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

	// 当前已是 planning，切到 planning 是 no-op。
	_, err = svc.ChangePhase(context.Background(), caller, "demo", r.Number, PhasePlanning)
	require.NoError(t, err)
	require.Empty(t, emitter.events)
}

func TestRequirementService_ChangePhase_GateBlocksAndSkipsEmit(t *testing.T) {
	repo := newFakeRepo()
	owner := uuid.New()
	seedProject(t, repo, owner)
	emitter := &fakeEmitter{}
	gate := NewGateRunner(GateDeps{Repo: repo}, nil)
	svc, spy := newReqSvcWith(repo, emitter, gate)
	caller := Caller{UserID: owner}

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

	// implementing 需要已批准的 auditing 产物，缺失 → phase_gate_failed。
	_, err = svc.ChangePhase(context.Background(), caller, "demo", r.Number, PhaseImplementing)
	ae, ok := errs.As(err)
	require.True(t, ok)
	require.Equal(t, errs.CodePhaseGateFailed, ae.Code)

	// gate 失败不写库、不发事件、不记 phase_change 审计。
	require.Empty(t, emitter.events)
	require.NotContains(t, spy.actions(), "requirement.phase_change")
	got, err := svc.Get(context.Background(), caller, "demo", r.Number)
	require.NoError(t, err)
	require.Equal(t, PhasePlanning, got.Phase)
}

func TestRequirementService_ChangePhase_ClosedCheckedBeforeGate(t *testing.T) {
	repo := newFakeRepo()
	owner := uuid.New()
	seedProject(t, repo, owner)
	emitter := &fakeEmitter{}
	gate := NewGateRunner(GateDeps{Repo: repo}, nil)
	svc, _ := newReqSvcWith(repo, emitter, gate)
	caller := Caller{UserID: owner}

	r, err := svc.Create(context.Background(), caller, "demo", CreateRequirementInput{Title: "T"})
	require.NoError(t, err)
	require.NoError(t, svc.Close(context.Background(), caller, "demo", r.Number))

	// 关闭检查先于 gate：即便目标是被网关的 implementing，也返回 requirement_closed。
	_, err = svc.ChangePhase(context.Background(), caller, "demo", r.Number, PhaseImplementing)
	ae, ok := errs.As(err)
	require.True(t, ok)
	require.Equal(t, errs.CodeRequirementClosed, ae.Code)
	require.Empty(t, emitter.events)
}

func TestRequirementService_ChangePhase_GateAllowsAfterApproval(t *testing.T) {
	repo := newFakeRepo()
	owner := uuid.New()
	seedProject(t, repo, owner)
	emitter := &fakeEmitter{}
	gate := NewGateRunner(GateDeps{Repo: repo}, nil)
	svc, _ := newReqSvcWith(repo, emitter, gate)
	artSvc := NewArtifactService(repo, nil)
	caller := Caller{UserID: owner}

	r, err := svc.Create(context.Background(), caller, "demo", CreateRequirementInput{Title: "T"})
	require.NoError(t, err)
	_, 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, VerdictPass)
	require.NoError(t, err)

	out, err := svc.ChangePhase(context.Background(), caller, "demo", r.Number, PhaseImplementing)
	require.NoError(t, err)
	require.Equal(t, PhaseImplementing, out.Phase)
	require.Len(t, emitter.events, 1)
}
