package handlers

import (
	"context"
	"encoding/json"
	"strings"
	"testing"

	"switchboard-core/database"
	"switchboard-core/models"
	"switchboard-core/workflow"
)

// testEngine creates a workflow engine with nil bus and runner (safe for tests).
func testEngine(t *testing.T) *workflow.Engine {
	t.Helper()
	s := testStores(t)
	return workflow.NewEngine(s, nil, nil)
}

// seedEngineFixture creates a 3-stage workflow (form → review → form),
// publishes it, and returns (workflowID, userID, teamID).
func seedEngineFixture(t *testing.T, slug string) (string, string, string) {
	t.Helper()
	s := testStores(t)
	ctx := context.Background()

	userID := database.SeedTestUser(t, "Eng-"+slug, slug+"@test.com")
	teamID := database.SeedTestTeam(t, "Team-"+slug, userID)

	wf := &models.Workflow{
		TeamID:    &teamID,
		Name:      "Engine " + slug,
		Slug:      slug,
		EntryMode: "team_only",
		IsActive:  true,
		CreatedBy: userID,
	}
	if err := s.Workflows.Create(ctx, wf); err != nil {
		t.Fatalf("create workflow: %v", err)
	}

	stages := []models.WorkflowStage{
		{WorkflowID: wf.ID, Ordinal: 0, Name: "intake", StageMode: models.StageModeForm, Audience: models.AudienceTeam, StageType: models.StageTypeSimple},
		{WorkflowID: wf.ID, Ordinal: 1, Name: "review", StageMode: models.StageModeReview, Audience: models.AudienceTeam, StageType: models.StageTypeSimple, AssignmentTeamID: &teamID},
		{WorkflowID: wf.ID, Ordinal: 2, Name: "final", StageMode: models.StageModeForm, Audience: models.AudienceTeam, StageType: models.StageTypeSimple},
	}
	for i := range stages {
		if err := s.Workflows.CreateStage(ctx, &stages[i]); err != nil {
			t.Fatalf("create stage %d: %v", i, err)
		}
	}

	snapshot, _ := json.Marshal(stages)
	ver := &models.WorkflowVersion{WorkflowID: wf.ID, VersionNumber: 1, Snapshot: snapshot}
	if err := s.Workflows.Publish(ctx, ver); err != nil {
		t.Fatalf("publish: %v", err)
	}

	return wf.ID, userID, teamID
}

// ── Full Lifecycle ──────────────────────────

func TestEngine_FullLifecycle(t *testing.T) {
	database.RequireTestDB(t)
	database.TruncateAll(t)
	eng := testEngine(t)
	ctx := context.Background()

	wfID, userID, _ := seedEngineFixture(t, "lifecycle")

	// Start
	inst, err := eng.Start(ctx, wfID, json.RawMessage(`{"name":"Alice"}`), userID)
	if err != nil {
		t.Fatalf("start: %v", err)
	}
	if inst.Status != models.InstanceStatusActive {
		t.Fatalf("status = %q, want active", inst.Status)
	}
	if inst.CurrentStage != "intake" {
		t.Fatalf("stage = %q, want intake", inst.CurrentStage)
	}

	// Advance intake → review
	inst, err = eng.Advance(ctx, inst.ID, json.RawMessage(`{"submitted":true}`), userID)
	if err != nil {
		t.Fatalf("advance 1: %v", err)
	}
	if inst.CurrentStage != "review" {
		t.Fatalf("stage = %q, want review", inst.CurrentStage)
	}

	// Advance review → final
	inst, err = eng.Advance(ctx, inst.ID, json.RawMessage(`{"reviewed":true}`), userID)
	if err != nil {
		t.Fatalf("advance 2: %v", err)
	}
	if inst.CurrentStage != "final" {
		t.Fatalf("stage = %q, want final", inst.CurrentStage)
	}

	// Advance final → completed (past last stage)
	inst, err = eng.Advance(ctx, inst.ID, json.RawMessage(`{}`), userID)
	if err != nil {
		t.Fatalf("advance 3: %v", err)
	}
	if inst.Status != models.InstanceStatusCompleted {
		t.Fatalf("status = %q, want completed", inst.Status)
	}
}

// ── Branch Routing ──────────────────────────

func TestEngine_BranchRouting(t *testing.T) {
	database.RequireTestDB(t)
	database.TruncateAll(t)
	s := testStores(t)
	eng := workflow.NewEngine(s, nil, nil)
	ctx := context.Background()

	userID := database.SeedTestUser(t, "Branch", "branch@test.com")
	teamID := database.SeedTestTeam(t, "Branch Team", userID)

	wf := &models.Workflow{
		TeamID: &teamID, Name: "Branching WF", Slug: "branch-wf",
		EntryMode: "team_only", IsActive: true, CreatedBy: userID,
	}
	s.Workflows.Create(ctx, wf)

	branchRules, _ := json.Marshal([]map[string]any{
		{"field": "priority", "op": "eq", "value": "high", "target_stage": "escalation"},
	})

	stages := []models.WorkflowStage{
		{WorkflowID: wf.ID, Ordinal: 0, Name: "intake", StageMode: models.StageModeForm, Audience: models.AudienceTeam, StageType: models.StageTypeSimple, BranchRules: branchRules},
		{WorkflowID: wf.ID, Ordinal: 1, Name: "normal-review", StageMode: models.StageModeReview, Audience: models.AudienceTeam, StageType: models.StageTypeSimple},
		{WorkflowID: wf.ID, Ordinal: 2, Name: "escalation", StageMode: models.StageModeReview, Audience: models.AudienceTeam, StageType: models.StageTypeSimple},
	}
	for i := range stages {
		s.Workflows.CreateStage(ctx, &stages[i])
	}
	snapshot, _ := json.Marshal(stages)
	s.Workflows.Publish(ctx, &models.WorkflowVersion{WorkflowID: wf.ID, VersionNumber: 1, Snapshot: snapshot})

	// Test: priority=high → escalation
	inst, _ := eng.Start(ctx, wf.ID, json.RawMessage(`{}`), userID)
	inst, err := eng.Advance(ctx, inst.ID, json.RawMessage(`{"priority":"high"}`), userID)
	if err != nil {
		t.Fatalf("advance: %v", err)
	}
	if inst.CurrentStage != "escalation" {
		t.Fatalf("stage = %q, want escalation", inst.CurrentStage)
	}

	// Test: no priority → normal-review (ordinal+1)
	inst2, _ := eng.Start(ctx, wf.ID, json.RawMessage(`{}`), userID)
	inst2, err = eng.Advance(ctx, inst2.ID, json.RawMessage(`{"priority":"low"}`), userID)
	if err != nil {
		t.Fatalf("advance2: %v", err)
	}
	if inst2.CurrentStage != "normal-review" {
		t.Fatalf("stage = %q, want normal-review", inst2.CurrentStage)
	}
}

// ── Public Entry ────────────────────────────

func TestEngine_PublicEntry(t *testing.T) {
	database.RequireTestDB(t)
	database.TruncateAll(t)
	s := testStores(t)
	eng := workflow.NewEngine(s, nil, nil)
	ctx := context.Background()

	userID := database.SeedTestUser(t, "PubUser", "pub@test.com")
	teamID := database.SeedTestTeam(t, "Pub Team", userID)

	wf := &models.Workflow{
		TeamID: &teamID, Name: "Public WF", Slug: "public-wf",
		EntryMode: "public_link", IsActive: true, CreatedBy: userID,
	}
	s.Workflows.Create(ctx, wf)

	stages := []models.WorkflowStage{
		{WorkflowID: wf.ID, Ordinal: 0, Name: "public-form", StageMode: models.StageModeForm, Audience: models.AudiencePublic, StageType: models.StageTypeSimple},
		{WorkflowID: wf.ID, Ordinal: 1, Name: "team-review", StageMode: models.StageModeReview, Audience: models.AudienceTeam, StageType: models.StageTypeSimple},
	}
	for i := range stages {
		s.Workflows.CreateStage(ctx, &stages[i])
	}
	snapshot, _ := json.Marshal(stages)
	s.Workflows.Publish(ctx, &models.WorkflowVersion{WorkflowID: wf.ID, VersionNumber: 1, Snapshot: snapshot})

	// StartPublic
	inst, err := eng.StartPublic(ctx, wf.ID, json.RawMessage(`{"email":"anon@example.com"}`))
	if err != nil {
		t.Fatalf("start public: %v", err)
	}
	if inst.EntryToken == nil || *inst.EntryToken == "" {
		t.Fatal("expected entry_token")
	}
	token := *inst.EntryToken

	// ResumePublic
	resumed, err := eng.ResumePublic(ctx, token)
	if err != nil {
		t.Fatalf("resume: %v", err)
	}
	if resumed.ID != inst.ID {
		t.Fatalf("resumed different instance")
	}

	// AdvancePublic on public stage — should succeed
	inst, err = eng.AdvancePublic(ctx, token, json.RawMessage(`{"submitted":true}`))
	if err != nil {
		t.Fatalf("advance public: %v", err)
	}
	if inst.CurrentStage != "team-review" {
		t.Fatalf("stage = %q, want team-review", inst.CurrentStage)
	}

	// AdvancePublic on team stage — should fail
	_, err = eng.AdvancePublic(ctx, token, json.RawMessage(`{}`))
	if err == nil {
		t.Fatal("expected error for non-public stage")
	}
	if !strings.Contains(err.Error(), "authenticated") {
		t.Fatalf("error = %q, want 'authenticated' mention", err.Error())
	}
}

// ── Signoff Validation Gate ─────────────────

func TestEngine_SignoffGate(t *testing.T) {
	database.RequireTestDB(t)
	database.TruncateAll(t)
	s := testStores(t)
	eng := workflow.NewEngine(s, nil, nil)
	ctx := context.Background()

	user1 := database.SeedTestUser(t, "Signer1", "s1@test.com")
	user2 := database.SeedTestUser(t, "Signer2", "s2@test.com")
	teamID := database.SeedTestTeam(t, "Gate Team", user1)

	wf := &models.Workflow{
		TeamID: &teamID, Name: "Gate WF", Slug: "gate-wf",
		EntryMode: "team_only", IsActive: true, CreatedBy: user1,
	}
	s.Workflows.Create(ctx, wf)

	validationCfg, _ := json.Marshal(map[string]any{
		"validation": map[string]any{"required_approvals": 2},
	})
	stages := []models.WorkflowStage{
		{WorkflowID: wf.ID, Ordinal: 0, Name: "gated-stage", StageMode: models.StageModeReview, Audience: models.AudienceTeam, StageType: models.StageTypeSimple, StageConfig: validationCfg},
		{WorkflowID: wf.ID, Ordinal: 1, Name: "done", StageMode: models.StageModeForm, Audience: models.AudienceTeam, StageType: models.StageTypeSimple},
	}
	for i := range stages {
		s.Workflows.CreateStage(ctx, &stages[i])
	}
	snapshot, _ := json.Marshal(stages)
	s.Workflows.Publish(ctx, &models.WorkflowVersion{WorkflowID: wf.ID, VersionNumber: 1, Snapshot: snapshot})

	inst, _ := eng.Start(ctx, wf.ID, json.RawMessage(`{}`), user1)

	// 1 approval — advance should fail
	eng.SubmitSignoff(ctx, inst.ID, user1, models.SignoffApprove, "ok")
	_, err := eng.Advance(ctx, inst.ID, json.RawMessage(`{}`), user1)
	if err == nil {
		t.Fatal("expected insufficient approvals error")
	}
	if !strings.Contains(err.Error(), "insufficient approvals") {
		t.Fatalf("error = %q, want 'insufficient approvals'", err.Error())
	}

	// 2nd approval — advance should succeed
	eng.SubmitSignoff(ctx, inst.ID, user2, models.SignoffApprove, "also ok")
	inst, err = eng.Advance(ctx, inst.ID, json.RawMessage(`{}`), user1)
	if err != nil {
		t.Fatalf("advance with 2 approvals: %v", err)
	}
	if inst.CurrentStage != "done" {
		t.Fatalf("stage = %q, want done", inst.CurrentStage)
	}
}

// ── Signoff Rejection ───────────────────────

func TestEngine_SignoffRejection(t *testing.T) {
	database.RequireTestDB(t)
	database.TruncateAll(t)
	s := testStores(t)
	eng := workflow.NewEngine(s, nil, nil)
	ctx := context.Background()

	userID := database.SeedTestUser(t, "Rejector", "rej@test.com")
	teamID := database.SeedTestTeam(t, "Reject Team", userID)

	wf := &models.Workflow{
		TeamID: &teamID, Name: "Reject WF", Slug: "reject-wf",
		EntryMode: "team_only", IsActive: true, CreatedBy: userID,
	}
	s.Workflows.Create(ctx, wf)

	validationCfg, _ := json.Marshal(map[string]any{
		"validation": map[string]any{"required_approvals": 1, "reject_action": "cancel"},
	})
	stages := []models.WorkflowStage{
		{WorkflowID: wf.ID, Ordinal: 0, Name: "review", StageMode: models.StageModeReview, Audience: models.AudienceTeam, StageType: models.StageTypeSimple, StageConfig: validationCfg},
		{WorkflowID: wf.ID, Ordinal: 1, Name: "approved", StageMode: models.StageModeForm, Audience: models.AudienceTeam, StageType: models.StageTypeSimple},
	}
	for i := range stages {
		s.Workflows.CreateStage(ctx, &stages[i])
	}
	snapshot, _ := json.Marshal(stages)
	s.Workflows.Publish(ctx, &models.WorkflowVersion{WorkflowID: wf.ID, VersionNumber: 1, Snapshot: snapshot})

	inst, _ := eng.Start(ctx, wf.ID, json.RawMessage(`{}`), userID)

	// Submit rejection
	eng.SubmitSignoff(ctx, inst.ID, userID, models.SignoffReject, "not good")

	// Advance should cancel the instance
	inst, err := eng.Advance(ctx, inst.ID, json.RawMessage(`{}`), userID)
	if err != nil {
		t.Fatalf("advance after reject: %v", err)
	}
	if inst.Status != models.InstanceStatusCancelled {
		t.Fatalf("status = %q, want cancelled", inst.Status)
	}
}

// ── Cancel Clears Assignments ───────────────

func TestEngine_CancelClearsAssignments(t *testing.T) {
	database.RequireTestDB(t)
	database.TruncateAll(t)
	s := testStores(t)
	eng := workflow.NewEngine(s, nil, nil)
	ctx := context.Background()

	wfID, userID, _ := seedEngineFixture(t, "cancel-assign")

	inst, _ := eng.Start(ctx, wfID, json.RawMessage(`{}`), userID)
	// Advance to review stage (which has assignment_team_id)
	inst, _ = eng.Advance(ctx, inst.ID, json.RawMessage(`{}`), userID)
	if inst.CurrentStage != "review" {
		t.Fatalf("stage = %q, want review", inst.CurrentStage)
	}

	// Verify assignment was created
	assignments, _ := s.Workflows.ListAssignmentsByInstance(ctx, inst.ID)
	openCount := 0
	for _, a := range assignments {
		if a.Status == models.AssignmentStatusUnassigned || a.Status == models.AssignmentStatusClaimed {
			openCount++
		}
	}
	if openCount == 0 {
		t.Fatal("expected at least one open assignment")
	}

	// Claim one
	for _, a := range assignments {
		if a.Status == models.AssignmentStatusUnassigned && a.Stage == "review" {
			s.Workflows.ClaimAssignment(ctx, a.ID, userID)
			break
		}
	}

	// Cancel instance
	if err := eng.Cancel(ctx, inst.ID, userID); err != nil {
		t.Fatalf("cancel: %v", err)
	}

	// Verify all open assignments are cancelled
	assignments, _ = s.Workflows.ListAssignmentsByInstance(ctx, inst.ID)
	for _, a := range assignments {
		if a.Stage == "review" && a.Status != models.AssignmentStatusCancelled {
			t.Errorf("assignment %s status = %q, want cancelled", a.ID, a.Status)
		}
	}

	// Verify instance is cancelled
	inst2, _ := s.Workflows.GetInstance(ctx, inst.ID)
	if inst2.Status != models.InstanceStatusCancelled {
		t.Fatalf("instance status = %q, want cancelled", inst2.Status)
	}
}

// ── Error Cases ─────────────────────────────

func TestEngine_ErrorCases(t *testing.T) {
	database.RequireTestDB(t)
	database.TruncateAll(t)
	s := testStores(t)
	eng := workflow.NewEngine(s, nil, nil)
	ctx := context.Background()

	wfID, userID, _ := seedEngineFixture(t, "errors")

	// Start on inactive workflow
	inactive := &models.Workflow{
		Name: "Inactive", Slug: "inactive-wf", EntryMode: "team_only",
		IsActive: false, CreatedBy: userID,
	}
	s.Workflows.Create(ctx, inactive)
	_, err := eng.Start(ctx, inactive.ID, json.RawMessage(`{}`), userID)
	if err == nil {
		t.Fatal("expected error starting inactive workflow")
	}

	// Advance on completed instance
	inst, _ := eng.Start(ctx, wfID, json.RawMessage(`{}`), userID)
	s.Workflows.CompleteInstance(ctx, inst.ID)
	_, err = eng.Advance(ctx, inst.ID, json.RawMessage(`{}`), userID)
	if err == nil {
		t.Fatal("expected error advancing completed instance")
	}
	if !strings.Contains(err.Error(), "not active") {
		t.Fatalf("error = %q, want 'not active'", err.Error())
	}

	// Cancel on already-cancelled instance
	inst2, _ := eng.Start(ctx, wfID, json.RawMessage(`{}`), userID)
	eng.Cancel(ctx, inst2.ID, userID)
	err = eng.Cancel(ctx, inst2.ID, userID)
	if err == nil {
		t.Fatal("expected error cancelling already-cancelled instance")
	}

	// Double claim
	inst3, _ := eng.Start(ctx, wfID, json.RawMessage(`{}`), userID)
	eng.Advance(ctx, inst3.ID, json.RawMessage(`{}`), userID) // → review (creates assignment)
	assignments, _ := s.Workflows.ListAssignmentsByInstance(ctx, inst3.ID)
	var reviewAssign string
	for _, a := range assignments {
		if a.Stage == "review" && a.Status == models.AssignmentStatusUnassigned {
			reviewAssign = a.ID
			break
		}
	}
	if reviewAssign != "" {
		s.Workflows.ClaimAssignment(ctx, reviewAssign, userID)
		err = s.Workflows.ClaimAssignment(ctx, reviewAssign, userID)
		if err == nil {
			t.Fatal("expected error on double claim")
		}
	}

	// Duplicate signoff
	inst4, _ := eng.Start(ctx, wfID, json.RawMessage(`{}`), userID)
	so := &models.WorkflowSignoff{InstanceID: inst4.ID, Stage: "intake", UserID: userID, Decision: models.SignoffApprove}
	s.Workflows.CreateSignoff(ctx, so)
	dup := &models.WorkflowSignoff{InstanceID: inst4.ID, Stage: "intake", UserID: userID, Decision: models.SignoffApprove}
	err = s.Workflows.CreateSignoff(ctx, dup)
	if err == nil {
		t.Fatal("expected UNIQUE violation for duplicate signoff")
	}

	// StartPublic on team_only workflow
	_, err = eng.StartPublic(ctx, wfID, json.RawMessage(`{}`))
	if err == nil {
		t.Fatal("expected error for StartPublic on team_only workflow")
	}
	if !strings.Contains(err.Error(), "public entry") {
		t.Fatalf("error = %q, want 'public entry'", err.Error())
	}
}

// ── Automated Stage Context (started_by) ────

func TestEngine_AutomatedStageContextIncludesStartedBy(t *testing.T) {
	database.RequireTestDB(t)
	database.TruncateAll(t)
	s := testStores(t)
	ctx := context.Background()

	userID := database.SeedTestUser(t, "AutoCtx", "autoctx@test.com")
	teamID := database.SeedTestTeam(t, "AutoCtx Team", userID)

	wf := &models.Workflow{
		TeamID: &teamID, Name: "AutoCtx WF", Slug: "autoctx-wf",
		EntryMode: "team_only", IsActive: true, CreatedBy: userID,
	}
	s.Workflows.Create(ctx, wf)

	hook := "test-pkg:on_run"
	stages := []models.WorkflowStage{
		{WorkflowID: wf.ID, Ordinal: 0, Name: "intake", StageMode: models.StageModeForm, Audience: models.AudienceTeam, StageType: models.StageTypeSimple},
		{WorkflowID: wf.ID, Ordinal: 1, Name: "auto-stage", StageMode: models.StageModeAutomated, Audience: models.AudienceSystem, StageType: models.StageTypeAutomated, AutoTransition: true, StarlarkHook: &hook},
		{WorkflowID: wf.ID, Ordinal: 2, Name: "done", StageMode: models.StageModeForm, Audience: models.AudienceTeam, StageType: models.StageTypeSimple},
	}
	for i := range stages {
		s.Workflows.CreateStage(ctx, &stages[i])
	}
	snapshot, _ := json.Marshal(stages)
	s.Workflows.Publish(ctx, &models.WorkflowVersion{WorkflowID: wf.ID, VersionNumber: 1, Snapshot: snapshot})

	// Engine with no runner — automated stage silently skips, instance stays at auto-stage
	eng := workflow.NewEngine(s, nil, nil)
	inst, err := eng.Start(ctx, wf.ID, json.RawMessage(`{}`), userID)
	if err != nil {
		t.Fatalf("start: %v", err)
	}

	// Advance intake → auto-stage
	inst, err = eng.Advance(ctx, inst.ID, json.RawMessage(`{"submitted":true}`), userID)
	if err != nil {
		t.Fatalf("advance to auto: %v", err)
	}

	// Verify the instance records started_by
	got, _ := s.Workflows.GetInstance(ctx, inst.ID)
	if got.StartedBy != userID {
		t.Fatalf("started_by = %q, want %q", got.StartedBy, userID)
	}
}

// ── SLA Seconds in Package Install ──────────

func TestPackageInstall_SLASeconds(t *testing.T) {
	database.RequireTestDB(t)
	database.TruncateAll(t)
	s := testStores(t)
	ctx := context.Background()

	userID := database.SeedTestUser(t, "SLAPkg", "slapkg@test.com")
	teamID := database.SeedTestTeam(t, "SLA Team", userID)

	// Simulate workflow package install with sla_seconds
	wf := &models.Workflow{
		TeamID: &teamID, Name: "SLA WF", Slug: "sla-wf",
		EntryMode: "team_only", IsActive: true, CreatedBy: userID,
	}
	s.Workflows.Create(ctx, wf)

	sla := 3600
	stage := &models.WorkflowStage{
		WorkflowID: wf.ID, Ordinal: 0, Name: "critical-fix",
		StageMode: models.StageModeForm, Audience: models.AudienceTeam,
		StageType: models.StageTypeSimple, SLASeconds: &sla,
	}
	if err := s.Workflows.CreateStage(ctx, stage); err != nil {
		t.Fatalf("create stage: %v", err)
	}

	// Read it back
	stages, err := s.Workflows.ListStages(ctx, wf.ID)
	if err != nil {
		t.Fatalf("list stages: %v", err)
	}
	if len(stages) != 1 {
		t.Fatalf("got %d stages, want 1", len(stages))
	}
	if stages[0].SLASeconds == nil || *stages[0].SLASeconds != 3600 {
		t.Fatalf("sla_seconds = %v, want 3600", stages[0].SLASeconds)
	}
}

// ── Workflow Package Manifest Install ────────

func TestPackageInstall_ManifestRoundtrip(t *testing.T) {
	database.RequireTestDB(t)
	database.TruncateAll(t)
	s := testStores(t)
	ctx := context.Background()

	userID := database.SeedTestUser(t, "PkgInst", "pkginst@test.com")
	_ = database.SeedTestTeam(t, "PkgInst Team", userID)

	// Create a workflow manually matching bug-report-triage structure
	wf := &models.Workflow{
		Name: "Bug Report Triage", Slug: "bug-report-triage-test",
		EntryMode: "public_link", IsActive: true, CreatedBy: userID,
	}
	if err := s.Workflows.Create(ctx, wf); err != nil {
		t.Fatalf("create workflow: %v", err)
	}

	sla := 3600
	branchRules, _ := json.Marshal([]map[string]any{
		{"field": "severity", "op": "eq", "value": "critical", "target_stage": "fix-critical"},
		{"field": "severity", "op": "neq", "value": "critical", "target_stage": "fix-normal"},
	})

	stages := []models.WorkflowStage{
		{WorkflowID: wf.ID, Ordinal: 0, Name: "submit", StageMode: models.StageModeForm, Audience: models.AudiencePublic, StageType: models.StageTypeSimple},
		{WorkflowID: wf.ID, Ordinal: 1, Name: "classify", StageMode: models.StageModeForm, Audience: models.AudienceTeam, StageType: models.StageTypeSimple, BranchRules: branchRules},
		{WorkflowID: wf.ID, Ordinal: 2, Name: "fix-critical", StageMode: models.StageModeForm, Audience: models.AudienceTeam, StageType: models.StageTypeSimple, SLASeconds: &sla},
		{WorkflowID: wf.ID, Ordinal: 3, Name: "fix-normal", StageMode: models.StageModeForm, Audience: models.AudienceTeam, StageType: models.StageTypeSimple},
		{WorkflowID: wf.ID, Ordinal: 4, Name: "verify", StageMode: models.StageModeReview, Audience: models.AudienceTeam, StageType: models.StageTypeSimple},
	}
	for i := range stages {
		if err := s.Workflows.CreateStage(ctx, &stages[i]); err != nil {
			t.Fatalf("create stage %d: %v", i, err)
		}
	}

	// Verify all stages
	got, err := s.Workflows.ListStages(ctx, wf.ID)
	if err != nil {
		t.Fatalf("list stages: %v", err)
	}
	if len(got) != 5 {
		t.Fatalf("got %d stages, want 5", len(got))
	}

	// Verify branch rules on classify
	for _, g := range got {
		if g.Name == "classify" {
			if len(g.BranchRules) == 0 {
				t.Fatal("classify missing branch_rules")
			}
			if !strings.Contains(string(g.BranchRules), "fix-critical") {
				t.Fatalf("branch_rules missing fix-critical target: %s", string(g.BranchRules))
			}
		}
		if g.Name == "fix-critical" {
			if g.SLASeconds == nil || *g.SLASeconds != 3600 {
				t.Fatalf("fix-critical sla_seconds = %v, want 3600", g.SLASeconds)
			}
		}
		if g.Name == "submit" && g.Audience != models.AudiencePublic {
			t.Fatalf("submit audience = %q, want public", g.Audience)
		}
	}
}
