package orchestrator

import (
	"context"
	"testing"

	"github.com/google/uuid"

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

type fakeGateArtifacts struct {
	byPhase map[project.Phase]int // phase → 产物数
}

func (f fakeGateArtifacts) ListArtifacts(_ context.Context, _, _ uuid.UUID, phase project.Phase) ([]*project.Artifact, error) {
	n := f.byPhase[phase]
	out := make([]*project.Artifact, 0, n)
	for i := 0; i < n; i++ {
		out = append(out, &project.Artifact{Phase: phase, Version: i + 1})
	}
	return out, nil
}

type fakeGateIssues struct{ open int }

func (f fakeGateIssues) CountOpenIssues(_ context.Context, _ uuid.UUID) (int, error) {
	return f.open, nil
}

func gateRun() *Run {
	return &Run{ID: uuid.New(), ProjectID: uuid.New(), RequirementID: uuid.New()}
}

func TestGate_PassOnEndTurnWithArtifact(t *testing.T) {
	g := NewV1Gate(fakeGateArtifacts{byPhase: map[project.Phase]int{project.PhasePlanning: 1}}, nil)
	step := &Step{Phase: project.PhasePlanning}
	res, err := g.Evaluate(context.Background(), gateRun(), step, "end_turn")
	if err != nil {
		t.Fatal(err)
	}
	if !res.Pass {
		t.Errorf("expected pass; got %+v", res)
	}
}

func TestGate_RetryOnEndTurnMissingArtifact(t *testing.T) {
	g := NewV1Gate(fakeGateArtifacts{byPhase: map[project.Phase]int{}}, nil)
	step := &Step{Phase: project.PhasePlanning}
	res, err := g.Evaluate(context.Background(), gateRun(), step, "end_turn")
	if err != nil {
		t.Fatal(err)
	}
	if res.Pass || !res.Retry {
		t.Errorf("expected no-pass retryable; got %+v", res)
	}
}

func TestGate_FailNoRetryOnRefusalAndMaxTokens(t *testing.T) {
	g := NewV1Gate(fakeGateArtifacts{byPhase: map[project.Phase]int{project.PhasePlanning: 1}}, nil)
	step := &Step{Phase: project.PhasePlanning}
	for _, sr := range []string{"refusal", "max_tokens"} {
		res, err := g.Evaluate(context.Background(), gateRun(), step, sr)
		if err != nil {
			t.Fatal(err)
		}
		if res.Pass || res.Retry {
			t.Errorf("stopReason %q: expected fail no-retry; got %+v", sr, res)
		}
	}
}

func TestGate_RetryOnEmptyOrCancelled(t *testing.T) {
	g := NewV1Gate(fakeGateArtifacts{byPhase: map[project.Phase]int{project.PhasePlanning: 1}}, nil)
	step := &Step{Phase: project.PhasePlanning}
	for _, sr := range []string{"", "cancelled"} {
		res, err := g.Evaluate(context.Background(), gateRun(), step, sr)
		if err != nil {
			t.Fatal(err)
		}
		if res.Pass || !res.Retry {
			t.Errorf("stopReason %q: expected retryable; got %+v", sr, res)
		}
	}
}

func TestGate_ReviewingRequiresNoOpenIssues(t *testing.T) {
	arts := fakeGateArtifacts{byPhase: map[project.Phase]int{project.PhaseReviewing: 1}}
	step := &Step{Phase: project.PhaseReviewing}

	gOpen := NewV1Gate(arts, fakeGateIssues{open: 2})
	res, _ := gOpen.Evaluate(context.Background(), gateRun(), step, "end_turn")
	if res.Pass || !res.Retry {
		t.Errorf("open issues should block reviewing (retryable); got %+v", res)
	}

	gClean := NewV1Gate(arts, fakeGateIssues{open: 0})
	res, _ = gClean.Evaluate(context.Background(), gateRun(), step, "end_turn")
	if !res.Pass {
		t.Errorf("no open issues should pass reviewing; got %+v", res)
	}
}

func TestGate_DonePassesOnEndTurn(t *testing.T) {
	g := NewV1Gate(nil, nil)
	step := &Step{Phase: project.PhaseDone}
	res, _ := g.Evaluate(context.Background(), gateRun(), step, "end_turn")
	if !res.Pass {
		t.Errorf("done should pass on end_turn; got %+v", res)
	}
}
