package project

import (
	"context"
	"testing"

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

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

// fakeWorkspaceState is a controllable WorkspaceStateLookup for Done-gate tests.
type fakeWorkspaceState struct {
	merged bool
	err    error
}

func (f fakeWorkspaceState) PullRequestMerged(_ context.Context, _, _ uuid.UUID) (bool, error) {
	return f.merged, f.err
}

// seedRequirement creates an open requirement under the seeded "demo" project.
func seedRequirement(t *testing.T, repo *fakeRepo, owner uuid.UUID, ws *uuid.UUID) *Requirement {
	t.Helper()
	reqSvc, _, _ := newReqSvc(repo)
	r, err := reqSvc.Create(context.Background(), Caller{UserID: owner}, "demo", CreateRequirementInput{
		Title: "R", WorkspaceID: ws,
	})
	require.NoError(t, err)
	return r
}

func requireGateFailed(t *testing.T, err error) {
	t.Helper()
	ae, ok := errs.As(err)
	require.True(t, ok)
	require.Equal(t, errs.CodePhaseGateFailed, ae.Code)
}

func TestPhaseGate_Implementing_BlockedWithoutApprovedAuditing(t *testing.T) {
	repo := newFakeRepo()
	owner := uuid.New()
	seedProject(t, repo, owner)
	r := seedRequirement(t, repo, owner, nil)

	gate := NewGateRunner(GateDeps{Repo: repo}, nil)
	err := gate.Check(context.Background(), r, PhasePlanning, PhaseImplementing)
	requireGateFailed(t, err)
}

func TestPhaseGate_Implementing_AllowedAfterApproval(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, VerdictPass)
	require.NoError(t, err)

	gate := NewGateRunner(GateDeps{Repo: repo}, nil)
	require.NoError(t, gate.Check(context.Background(), r, PhasePlanning, PhaseImplementing))
}

func TestPhaseGate_Implementing_BlockedWhenVerdictFail(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)
	// verdict=fail 不写指针，gate 仍应阻止。
	_, err = artSvc.Approve(context.Background(), caller, "demo", r.Number, PhaseAuditing, 1, VerdictFail)
	require.NoError(t, err)

	gate := NewGateRunner(GateDeps{Repo: repo}, nil)
	requireGateFailed(t, gate.Check(context.Background(), r, PhasePlanning, PhaseImplementing))
}

func TestPhaseGate_Done_BlockedWhenNoWorkspace(t *testing.T) {
	repo := newFakeRepo()
	owner := uuid.New()
	seedProject(t, repo, owner)
	r := seedRequirement(t, repo, owner, nil) // no workspace

	gate := NewGateRunner(GateDeps{Repo: repo, Workspace: fakeWorkspaceState{merged: true}}, nil)
	requireGateFailed(t, gate.Check(context.Background(), r, PhaseReviewing, PhaseDone))
}

func TestPhaseGate_Done_BlockedWhenNotMerged(t *testing.T) {
	repo := newFakeRepo()
	owner := uuid.New()
	seedProject(t, repo, owner)
	ws := uuid.New()
	r := seedRequirement(t, repo, owner, &ws)

	gate := NewGateRunner(GateDeps{Repo: repo, Workspace: fakeWorkspaceState{merged: false}}, nil)
	requireGateFailed(t, gate.Check(context.Background(), r, PhaseReviewing, PhaseDone))
}

func TestPhaseGate_Done_AllowedWhenMerged(t *testing.T) {
	repo := newFakeRepo()
	owner := uuid.New()
	seedProject(t, repo, owner)
	ws := uuid.New()
	r := seedRequirement(t, repo, owner, &ws)

	gate := NewGateRunner(GateDeps{Repo: repo, Workspace: fakeWorkspaceState{merged: true}}, nil)
	require.NoError(t, gate.Check(context.Background(), r, PhaseReviewing, PhaseDone))
}

func TestPhaseGate_Done_BlockedWhenWorkspaceLookupNil(t *testing.T) {
	repo := newFakeRepo()
	owner := uuid.New()
	seedProject(t, repo, owner)
	ws := uuid.New()
	r := seedRequirement(t, repo, owner, &ws)

	// Workspace 缺失 → 优雅降级为"未合并"，阻止流转。
	gate := NewGateRunner(GateDeps{Repo: repo, Workspace: nil}, nil)
	requireGateFailed(t, gate.Check(context.Background(), r, PhaseReviewing, PhaseDone))
}

func TestPhaseGate_OpenPhases_AlwaysAllowed(t *testing.T) {
	repo := newFakeRepo()
	owner := uuid.New()
	seedProject(t, repo, owner)
	r := seedRequirement(t, repo, owner, nil)

	gate := NewGateRunner(GateDeps{Repo: repo}, nil)
	// planning/prototyping/auditing/reviewing 无 entry 网关 → 任意流转放行（回归 any-to-any）。
	for _, to := range []Phase{PhasePlanning, PhasePrototyping, PhaseAuditing, PhaseReviewing} {
		require.NoError(t, gate.Check(context.Background(), r, PhasePlanning, to), "to=%s", to)
	}
}
