package automation

import (
	"context"
	"errors"
	"fmt"
	"net/http"
	"strings"
	"testing"
	"time"

	"github.com/stretchr/testify/require"
	"github.com/tidbcloud/AgentTeam/internal/automation/dsl"
	"github.com/tidbcloud/AgentTeam/internal/service/automation/runtime"
	automationstore "github.com/tidbcloud/AgentTeam/internal/store/automation"
	"github.com/tidbcloud/AgentTeam/internal/store/resource/repos"
)

func TestDecodeAutomationSpecRejectsUnsupportedStepFields(t *testing.T) {
	cases := []struct {
		name  string
		field string
		value string
	}{
		{name: "shell", field: "shell", value: "bash"},
		{name: "working directory", field: "working-directory", value: "subdir"},
		{name: "timeout minutes", field: "timeout-minutes", value: "5"},
		{name: "continue on error", field: "continue-on-error", value: "true"},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			raw := fmt.Sprintf("name: Unsupported step field\non:\n  manual: {}\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - run: echo hello\n        %s: %s\n", tc.field, tc.value)
			_, err := decodeAutomationSpec(raw)
			require.Error(t, err)
			require.Contains(t, err.Error(), "unknown field")
		})
	}
}

func TestAutomationDashboardIsWorkspaceScoped(t *testing.T) {
	ctx := context.Background()
	db, err := openServiceTestSQLiteDB(t)
	require.NoError(t, err)
	migrateAutomationServiceTestSchema(t, db, &automationstore.AutomationRunner{}, &automationstore.AutomationRunnerSession{}, &automationstore.AutomationLogChunk{}, &automationstore.AutomationArtifact{})
	store := automationstore.New(db)
	now := time.Date(2026, 6, 3, 11, 0, 0, 0, time.UTC)
	completedAt := now.Add(-time.Minute)

	_, err = store.CreateRun(ctx, automationstore.AutomationRun{ID: "run-ws-1", WorkspaceID: "ws-1", Name: "Dashboard", Status: automationstore.AutomationRunStatusRunning, CreatedAt: now, UpdatedAt: now})
	require.NoError(t, err)
	require.NoError(t, store.CreateJobRuns(ctx, []automationstore.AutomationJobRun{
		{ID: "job-ws-1", WorkspaceID: "ws-1", RunID: "run-ws-1", JobID: "build", JobType: "github_actions_job", Status: automationstore.AutomationJobRunStatusSucceeded, ScheduledAt: now, CreatedAt: now, UpdatedAt: now, CompletedAt: &completedAt},
	}))
	_, err = store.EnqueueJob(ctx, automationstore.AutomationJob{WorkspaceID: "ws-1", RunID: "run-ws-1", JobRunID: "job-ws-1", QueueName: "default", Status: automationstore.AutomationJobStatusReady, Attempt: 1, ScheduledAt: now, MaxAttempts: 3, IdempotencyKey: "run-ws-1:build", ShardBucket: 0, CreatedAt: now, UpdatedAt: now})
	require.NoError(t, err)

	_, err = store.CreateRun(ctx, automationstore.AutomationRun{ID: "run-ws-2", WorkspaceID: "ws-2", Name: "Dashboard", Status: automationstore.AutomationRunStatusRunning, CreatedAt: now, UpdatedAt: now})
	require.NoError(t, err)
	require.NoError(t, store.CreateJobRuns(ctx, []automationstore.AutomationJobRun{
		{ID: "job-ws-2", WorkspaceID: "ws-2", RunID: "run-ws-2", JobID: "build", JobType: "github_actions_job", Status: automationstore.AutomationJobRunStatusSucceeded, ScheduledAt: now, CreatedAt: now, UpdatedAt: now, CompletedAt: &completedAt},
	}))
	_, err = store.EnqueueJob(ctx, automationstore.AutomationJob{WorkspaceID: "ws-2", RunID: "run-ws-2", JobRunID: "job-ws-2", QueueName: "default", Status: automationstore.AutomationJobStatusReady, Attempt: 1, ScheduledAt: now, MaxAttempts: 3, IdempotencyKey: "run-ws-2:build", ShardBucket: 0, CreatedAt: now, UpdatedAt: now})
	require.NoError(t, err)
	require.NoError(t, db.Create([]automationstore.AutomationWait{
		{ID: "wait-ws-1", WorkspaceID: "ws-1", RunID: "run-ws-1", JobRunID: "job-ws-1", WaitKind: automationstore.AutomationWaitKindAgentTask, Status: automationstore.AutomationWaitStatusActive, CreatedAt: now, UpdatedAt: now},
		{ID: "wait-ws-2", WorkspaceID: "ws-2", RunID: "run-ws-2", JobRunID: "job-ws-2", WaitKind: automationstore.AutomationWaitKindAgentTask, Status: automationstore.AutomationWaitStatusActive, CreatedAt: now, UpdatedAt: now},
	}).Error)
	require.NoError(t, db.Create([]automationstore.AutomationRunner{
		{WorkspaceID: "ws-1", RunnerID: "runner-ws-1", Name: "ws 1", LabelsJSON: `[]`, Status: automationstore.AutomationRunnerStatusActive, RegisteredAt: now, CreatedAt: now, UpdatedAt: now},
		{WorkspaceID: "ws-2", RunnerID: "runner-ws-2", Name: "ws 2", LabelsJSON: `[]`, Status: automationstore.AutomationRunnerStatusActive, RegisteredAt: now, CreatedAt: now, UpdatedAt: now},
	}).Error)
	require.NoError(t, db.Create([]automationstore.AutomationRunnerSession{
		{WorkspaceID: "ws-1", RunnerID: "runner-ws-1", SessionID: "session-ws-1", LeaseOwner: "lease-ws-1", LabelsJSON: `[]`, Status: automationstore.AutomationRunnerSessionStatusActive, HeartbeatAt: now, ExpiresAt: now.Add(time.Minute), CreatedAt: now, UpdatedAt: now},
		{WorkspaceID: "ws-2", RunnerID: "runner-ws-2", SessionID: "session-ws-2", LeaseOwner: "lease-ws-2", LabelsJSON: `[]`, Status: automationstore.AutomationRunnerSessionStatusActive, HeartbeatAt: now, ExpiresAt: now.Add(time.Minute), CreatedAt: now, UpdatedAt: now},
	}).Error)

	service := NewAutomationsService(store, func() time.Time { return now }, nil)
	dashboard, err := service.GetAutomationDashboard(ctx, "ws-1", "user-1")
	require.NoError(t, err)
	require.Equal(t, now, dashboard.ObservedAt)
	require.Equal(t, AutomationDashboardHealthCritical, dashboard.Health.Status)
	require.EqualValues(t, 1, dashboard.Metrics.QueueMetrics.Ready)
	require.EqualValues(t, 1, dashboard.Metrics.QueueMetrics.RetryReady)
	require.EqualValues(t, 1, dashboard.Metrics.RunMetrics.Running)
	require.EqualValues(t, 1, dashboard.Metrics.RunMetrics.TerminalInconsistent)
	require.EqualValues(t, 1, dashboard.Metrics.WaitMetrics.Active)
	require.EqualValues(t, 1, dashboard.Metrics.RunnerMetrics.Registered)
	require.EqualValues(t, 1, dashboard.Metrics.RunnerMetrics.ActiveSessions)
	require.NotEmpty(t, dashboard.AttentionItems)
}

func TestDeliverAutomationWaitSignalResolvesAgentTaskAndSchedulesDownstream(t *testing.T) {
	ctx := context.Background()
	db, err := openServiceTestSQLiteDB(t)
	require.NoError(t, err)
	migrateAutomationServiceTestSchema(t, db)
	store := automationstore.New(db)
	now := time.Date(2026, 6, 7, 9, 30, 0, 0, time.UTC)
	ids := 0
	newID := func(prefix string) (string, error) {
		ids++
		return fmt.Sprintf("%sdeliver%03d", prefix, ids), nil
	}
	spec := dsl.DefinitionSpec{Name: "Task wait", Jobs: map[string]dsl.JobSpec{
		"task":   {Steps: []dsl.StepSpec{{Uses: "agentteam/agent-task", With: map[string]any{"task_id": "tsk_123"}}}},
		"deploy": {Needs: []string{"task"}, Steps: []dsl.StepSpec{{Run: "make deploy"}}},
	}}
	run := automationstore.AutomationRun{ID: "run-deliver-task", WorkspaceID: "ws-1", Name: "Task wait", Status: automationstore.AutomationRunStatusWaiting, CreatedByPrincipalID: "user-1", CreatedAt: now, UpdatedAt: now}
	rt, err := runtime.BuildInitialRuntime(run, spec, now, newID)
	require.NoError(t, err)
	require.Len(t, rt.Waits, 1)
	require.Equal(t, "tsk_123", rt.Waits[0].ResourceID)
	require.NotEmpty(t, rt.Waits[0].SignalKey)
	_, err = store.CreateRunWithRuntime(ctx, run, rt.JobRuns, rt.Events, rt.Jobs, rt.Waits)
	require.NoError(t, err)

	service := NewAutomationsService(store, func() time.Time { return now.Add(time.Minute) }, nil)
	var signals []string
	service.SetRunnerSignal(func(workspaceID string) {
		signals = append(signals, workspaceID)
	})
	delivered, err := service.DeliverAutomationWaitSignal(ctx, "ws-1", "automation-eventer", AutomationWaitSignalMutation{WaitKind: automationstore.AutomationWaitKindAgentTask, ResourceID: "tsk_123", TaskNumber: 42, Status: "done"})
	require.NoError(t, err)
	require.Equal(t, automationstore.AutomationWaitStatusResolved, delivered.Status)
	require.Contains(t, delivered.ResultJSON, `"task_id":"tsk_123"`)

	again, err := service.DeliverAutomationWaitSignal(ctx, "ws-1", "automation-eventer", AutomationWaitSignalMutation{WaitKind: automationstore.AutomationWaitKindAgentTask, ResourceID: "tsk_123", TaskNumber: 42, Status: "done"})
	require.NoError(t, err)
	require.Equal(t, delivered.ID, again.ID)
	require.Equal(t, []string{"ws-1"}, signals)

	jobRuns, err := store.ListJobRuns(ctx, "ws-1", "run-deliver-task")
	require.NoError(t, err)
	require.Equal(t, automationstore.AutomationJobRunStatusSucceeded, jobRuns[0].Status)
	require.Equal(t, automationstore.AutomationJobRunStatusQueued, jobRuns[1].Status)
	events, err := store.ListEvents(ctx, "ws-1", "run-deliver-task", 1, 20)
	require.NoError(t, err)
	var resolvedEvents int
	for _, event := range events {
		if event.EventType == "wait_resolved" {
			resolvedEvents++
		}
	}
	require.Equal(t, 1, resolvedEvents, "repeat delivery should not append duplicate wait_resolved events")
}

func TestResumeAutomationWaitSignalsRunnerAfterSchedulingDownstream(t *testing.T) {
	ctx := context.Background()
	db, err := openServiceTestSQLiteDB(t)
	require.NoError(t, err)
	migrateAutomationServiceTestSchema(t, db)
	store := automationstore.New(db)
	now := time.Date(2026, 6, 7, 9, 32, 0, 0, time.UTC)
	ids := 0
	newID := func(prefix string) (string, error) {
		ids++
		return fmt.Sprintf("%sresume%03d", prefix, ids), nil
	}
	spec := dsl.DefinitionSpec{Name: "Manual wait", Jobs: map[string]dsl.JobSpec{
		"approve": {Steps: []dsl.StepSpec{{Uses: "agentteam/wait-event", With: map[string]any{"event_type": "automation_event_received", "resource_type": "event", "resource_id": "ship-approval"}}}},
		"deploy":  {Needs: []string{"approve"}, Steps: []dsl.StepSpec{{Run: "make deploy"}}},
	}}
	run := automationstore.AutomationRun{ID: "run-resume-wait", WorkspaceID: "ws-1", Name: "Manual wait", Status: automationstore.AutomationRunStatusWaiting, CreatedByPrincipalID: "user-1", CreatedAt: now, UpdatedAt: now}
	rt, err := runtime.BuildInitialRuntime(run, spec, now, newID)
	require.NoError(t, err)
	require.Len(t, rt.Waits, 1)
	_, err = store.CreateRunWithRuntime(ctx, run, rt.JobRuns, rt.Events, rt.Jobs, rt.Waits)
	require.NoError(t, err)

	service := NewAutomationsService(store, func() time.Time { return now.Add(time.Minute) }, nil)
	var signals []string
	service.SetRunnerSignal(func(workspaceID string) {
		signals = append(signals, workspaceID)
	})
	resolved, err := service.ResumeAutomationWait(ctx, "ws-1", "approver-1", run.ID, rt.Waits[0].ID, `{"approved":true}`)
	require.NoError(t, err)
	require.Equal(t, automationstore.AutomationWaitStatusResolved, resolved.Status)
	require.Equal(t, []string{"ws-1"}, signals)

	jobRuns, err := store.ListJobRuns(ctx, "ws-1", run.ID)
	require.NoError(t, err)
	require.Equal(t, automationstore.AutomationJobRunStatusSucceeded, jobRuns[0].Status)
	require.Equal(t, automationstore.AutomationJobRunStatusQueued, jobRuns[1].Status)
}

func TestDeliverAutomationWaitSignalReplaysSchedulerForResolvedWait(t *testing.T) {
	ctx := context.Background()
	db, err := openServiceTestSQLiteDB(t)
	require.NoError(t, err)
	migrateAutomationServiceTestSchema(t, db)
	store := automationstore.New(db)
	now := time.Date(2026, 6, 7, 9, 35, 0, 0, time.UTC)
	ids := 0
	newID := func(prefix string) (string, error) {
		ids++
		return fmt.Sprintf("%sreplay%03d", prefix, ids), nil
	}
	spec := dsl.DefinitionSpec{Name: "Task wait replay", Jobs: map[string]dsl.JobSpec{
		"task":   {Steps: []dsl.StepSpec{{Uses: "agentteam/agent-task", With: map[string]any{"task_id": "tsk_replay"}}}},
		"deploy": {Needs: []string{"task"}, Steps: []dsl.StepSpec{{Run: "make deploy"}}},
	}}
	run := automationstore.AutomationRun{ID: "run-deliver-replay", WorkspaceID: "ws-1", Name: "Task wait replay", Status: automationstore.AutomationRunStatusWaiting, CreatedByPrincipalID: "user-1", CreatedAt: now, UpdatedAt: now}
	rt, err := runtime.BuildInitialRuntime(run, spec, now, newID)
	require.NoError(t, err)
	require.Len(t, rt.Waits, 1)
	_, err = store.CreateRunWithRuntime(ctx, run, rt.JobRuns, rt.Events, rt.Jobs, rt.Waits)
	require.NoError(t, err)

	_, _, err = store.ResolveWait(ctx, "ws-1", rt.Waits[0].ID, "automation-eventer", `{"task_id":"tsk_replay","task_number":42,"status":"done"}`, now.Add(time.Minute))
	require.NoError(t, err)
	before, err := store.ListJobRuns(ctx, "ws-1", "run-deliver-replay")
	require.NoError(t, err)
	require.Equal(t, automationstore.AutomationJobRunStatusSucceeded, before[0].Status)
	require.Equal(t, automationstore.AutomationJobRunStatusPending, before[1].Status)

	service := NewAutomationsService(store, func() time.Time { return now.Add(2 * time.Minute) }, nil)
	delivered, err := service.DeliverAutomationWaitSignal(ctx, "ws-1", "automation-eventer", AutomationWaitSignalMutation{WaitKind: automationstore.AutomationWaitKindAgentTask, ResourceID: "tsk_replay", TaskNumber: 42, Status: "done"})
	require.NoError(t, err)
	require.Equal(t, automationstore.AutomationWaitStatusResolved, delivered.Status)

	after, err := store.ListJobRuns(ctx, "ws-1", "run-deliver-replay")
	require.NoError(t, err)
	require.Equal(t, automationstore.AutomationJobRunStatusSucceeded, after[0].Status)
	require.Equal(t, automationstore.AutomationJobRunStatusQueued, after[1].Status)
	events, err := store.ListEvents(ctx, "ws-1", "run-deliver-replay", 1, 20)
	require.NoError(t, err)
	var resolvedEvents int
	for _, event := range events {
		if event.EventType == "wait_resolved" {
			resolvedEvents++
		}
	}
	require.Equal(t, 1, resolvedEvents, "scheduler replay should not append a duplicate wait_resolved event")
}

func TestDeliverAutomationWaitSignalRepairsQueuedJobMissingQueueRow(t *testing.T) {
	ctx := context.Background()
	db, err := openServiceTestSQLiteDB(t)
	require.NoError(t, err)
	migrateAutomationServiceTestSchema(t, db)
	store := automationstore.New(db)
	now := time.Date(2026, 6, 7, 9, 40, 0, 0, time.UTC)
	ids := 0
	newID := func(prefix string) (string, error) {
		ids++
		return fmt.Sprintf("%squeued%03d", prefix, ids), nil
	}
	spec := dsl.DefinitionSpec{Name: "Task wait queued repair", Jobs: map[string]dsl.JobSpec{
		"task":   {Steps: []dsl.StepSpec{{Uses: "agentteam/agent-task", With: map[string]any{"task_id": "tsk_queued_repair"}}}},
		"deploy": {Needs: []string{"task"}, Steps: []dsl.StepSpec{{Run: "make deploy"}}},
	}}
	run := automationstore.AutomationRun{ID: "run-deliver-queued-repair", WorkspaceID: "ws-1", Name: "Task wait queued repair", Status: automationstore.AutomationRunStatusWaiting, CreatedByPrincipalID: "user-1", CreatedAt: now, UpdatedAt: now}
	rt, err := runtime.BuildInitialRuntime(run, spec, now, newID)
	require.NoError(t, err)
	require.Len(t, rt.Waits, 1)
	_, err = store.CreateRunWithRuntime(ctx, run, rt.JobRuns, rt.Events, rt.Jobs, rt.Waits)
	require.NoError(t, err)

	_, _, err = store.ResolveWait(ctx, "ws-1", rt.Waits[0].ID, "automation-eventer", `{"task_id":"tsk_queued_repair","task_number":42,"status":"done"}`, now.Add(time.Minute))
	require.NoError(t, err)
	jobRuns, err := store.ListJobRuns(ctx, "ws-1", "run-deliver-queued-repair")
	require.NoError(t, err)
	require.Len(t, jobRuns, 2)
	jobRuns[1].Status = automationstore.AutomationJobRunStatusQueued
	jobRuns[1].UpdatedAt = now.Add(90 * time.Second)
	_, err = store.UpdateJobRun(ctx, jobRuns[1])
	require.NoError(t, err)
	claimed, err := store.ClaimJobs(ctx, automationstore.ClaimJobsRequest{WorkspaceID: "ws-1", QueueName: runtime.DefaultQueueName, ShardBuckets: allAutomationClaimShards(), LeaseOwner: "runner-before", LeaseDuration: time.Minute, Limit: 1, Now: now.Add(2 * time.Minute)})
	require.NoError(t, err)
	require.Empty(t, claimed)

	service := NewAutomationsService(store, func() time.Time { return now.Add(3 * time.Minute) }, nil)
	delivered, err := service.DeliverAutomationWaitSignal(ctx, "ws-1", "automation-eventer", AutomationWaitSignalMutation{WaitKind: automationstore.AutomationWaitKindAgentTask, ResourceID: "tsk_queued_repair", TaskNumber: 42, Status: "done"})
	require.NoError(t, err)
	require.Equal(t, automationstore.AutomationWaitStatusResolved, delivered.Status)

	claimed, err = store.ClaimJobs(ctx, automationstore.ClaimJobsRequest{WorkspaceID: "ws-1", QueueName: runtime.DefaultQueueName, ShardBuckets: allAutomationClaimShards(), LeaseOwner: "runner-after", LeaseDuration: time.Minute, Limit: 1, Now: now.Add(4 * time.Minute)})
	require.NoError(t, err)
	require.Len(t, claimed, 1)
	require.Equal(t, jobRuns[1].ID, claimed[0].JobRunID)
}

func TestDeliverAutomationWaitSignalResolvesAllActiveSubscribers(t *testing.T) {
	ctx := context.Background()
	db, err := openServiceTestSQLiteDB(t)
	require.NoError(t, err)
	migrateAutomationServiceTestSchema(t, db)
	store := automationstore.New(db)
	now := time.Date(2026, 6, 7, 9, 45, 0, 0, time.UTC)
	ids := 0
	newID := func(prefix string) (string, error) {
		ids++
		return fmt.Sprintf("%sshared%03d", prefix, ids), nil
	}
	spec := dsl.DefinitionSpec{Name: "Shared task wait", Jobs: map[string]dsl.JobSpec{
		"task":   {Steps: []dsl.StepSpec{{Uses: "agentteam/agent-task", With: map[string]any{"task_id": "tsk_shared"}}}},
		"deploy": {Needs: []string{"task"}, Steps: []dsl.StepSpec{{Run: "make deploy"}}},
	}}
	createRun := func(runID string) {
		t.Helper()
		run := automationstore.AutomationRun{ID: runID, WorkspaceID: "ws-1", Name: "Shared task wait", Status: automationstore.AutomationRunStatusWaiting, CreatedByPrincipalID: "user-1", CreatedAt: now, UpdatedAt: now}
		rt, err := runtime.BuildInitialRuntime(run, spec, now, newID)
		require.NoError(t, err)
		require.Len(t, rt.Waits, 1)
		require.Equal(t, "tsk_shared", rt.Waits[0].ResourceID)
		_, err = store.CreateRunWithRuntime(ctx, run, rt.JobRuns, rt.Events, rt.Jobs, rt.Waits)
		require.NoError(t, err)
	}
	createRun("run-deliver-shared-a")
	createRun("run-deliver-shared-b")

	service := NewAutomationsService(store, func() time.Time { return now.Add(time.Minute) }, nil)
	delivered, err := service.DeliverAutomationWaitSignal(ctx, "ws-1", "automation-eventer", AutomationWaitSignalMutation{WaitKind: automationstore.AutomationWaitKindAgentTask, ResourceID: "tsk_shared", TaskNumber: 42, Status: "done"})
	require.NoError(t, err)
	require.Equal(t, automationstore.AutomationWaitStatusResolved, delivered.Status)

	for _, runID := range []string{"run-deliver-shared-a", "run-deliver-shared-b"} {
		waits, err := store.ListWaits(ctx, "ws-1", runID, "")
		require.NoError(t, err)
		require.Len(t, waits, 1)
		require.Equal(t, automationstore.AutomationWaitStatusResolved, waits[0].Status)

		jobRuns, err := store.ListJobRuns(ctx, "ws-1", runID)
		require.NoError(t, err)
		require.Equal(t, automationstore.AutomationJobRunStatusSucceeded, jobRuns[0].Status)
		require.Equal(t, automationstore.AutomationJobRunStatusQueued, jobRuns[1].Status)
	}

	again, err := service.DeliverAutomationWaitSignal(ctx, "ws-1", "automation-eventer", AutomationWaitSignalMutation{WaitKind: automationstore.AutomationWaitKindAgentTask, ResourceID: "tsk_shared", TaskNumber: 42, Status: "done"})
	require.NoError(t, err)
	require.Equal(t, automationstore.AutomationWaitStatusResolved, again.Status)

	var resolvedEvents int
	for _, runID := range []string{"run-deliver-shared-a", "run-deliver-shared-b"} {
		events, err := store.ListEvents(ctx, "ws-1", runID, 1, 20)
		require.NoError(t, err)
		for _, event := range events {
			if event.EventType == "wait_resolved" {
				resolvedEvents++
			}
		}
	}
	require.Equal(t, 2, resolvedEvents, "repeat delivery should not append duplicate wait_resolved events")
}

func TestDeliverAutomationWaitSignalReplaysResolvedSubscriberWithActiveSubscribers(t *testing.T) {
	ctx := context.Background()
	db, err := openServiceTestSQLiteDB(t)
	require.NoError(t, err)
	migrateAutomationServiceTestSchema(t, db)
	store := automationstore.New(db)
	now := time.Date(2026, 6, 7, 9, 50, 0, 0, time.UTC)
	ids := 0
	newID := func(prefix string) (string, error) {
		ids++
		return fmt.Sprintf("%smixed%03d", prefix, ids), nil
	}
	spec := dsl.DefinitionSpec{Name: "Mixed task wait", Jobs: map[string]dsl.JobSpec{
		"task":   {Steps: []dsl.StepSpec{{Uses: "agentteam/agent-task", With: map[string]any{"task_id": "tsk_mixed"}}}},
		"deploy": {Needs: []string{"task"}, Steps: []dsl.StepSpec{{Run: "make deploy"}}},
	}}
	createRun := func(runID string) runtime.InitialRuntime {
		t.Helper()
		run := automationstore.AutomationRun{ID: runID, WorkspaceID: "ws-1", Name: "Mixed task wait", Status: automationstore.AutomationRunStatusWaiting, CreatedByPrincipalID: "user-1", CreatedAt: now, UpdatedAt: now}
		rt, err := runtime.BuildInitialRuntime(run, spec, now, newID)
		require.NoError(t, err)
		require.Len(t, rt.Waits, 1)
		_, err = store.CreateRunWithRuntime(ctx, run, rt.JobRuns, rt.Events, rt.Jobs, rt.Waits)
		require.NoError(t, err)
		return rt
	}
	resolvedRuntime := createRun("run-deliver-mixed-resolved")
	createRun("run-deliver-mixed-active")

	_, _, err = store.ResolveWait(ctx, "ws-1", resolvedRuntime.Waits[0].ID, "automation-eventer", `{"task_id":"tsk_mixed","task_number":42,"status":"done"}`, now.Add(time.Minute))
	require.NoError(t, err)

	service := NewAutomationsService(store, func() time.Time { return now.Add(2 * time.Minute) }, nil)
	delivered, err := service.DeliverAutomationWaitSignal(ctx, "ws-1", "automation-eventer", AutomationWaitSignalMutation{WaitKind: automationstore.AutomationWaitKindAgentTask, ResourceID: "tsk_mixed", TaskNumber: 42, Status: "done"})
	require.NoError(t, err)
	require.Equal(t, automationstore.AutomationWaitStatusResolved, delivered.Status)

	for _, runID := range []string{"run-deliver-mixed-resolved", "run-deliver-mixed-active"} {
		jobRuns, err := store.ListJobRuns(ctx, "ws-1", runID)
		require.NoError(t, err)
		require.Equal(t, automationstore.AutomationJobRunStatusSucceeded, jobRuns[0].Status)
		require.Equal(t, automationstore.AutomationJobRunStatusQueued, jobRuns[1].Status)
	}
}

func TestDeliverAutomationWaitSignalSkipsHistoricalResolvedSubscribers(t *testing.T) {
	ctx := context.Background()
	db, err := openServiceTestSQLiteDB(t)
	require.NoError(t, err)
	migrateAutomationServiceTestSchema(t, db)
	store := automationstore.New(db)
	now := time.Date(2026, 6, 7, 9, 55, 0, 0, time.UTC)
	ids := 0
	newID := func(prefix string) (string, error) {
		ids++
		return fmt.Sprintf("%shistory%03d", prefix, ids), nil
	}
	spec := dsl.DefinitionSpec{Name: "Historical task wait", Jobs: map[string]dsl.JobSpec{
		"task":   {Steps: []dsl.StepSpec{{Uses: "agentteam/agent-task", With: map[string]any{"task_id": "tsk_history"}}}},
		"deploy": {Needs: []string{"task"}, Steps: []dsl.StepSpec{{Run: "make deploy"}}},
	}}
	createRun := func(runID string) runtime.InitialRuntime {
		t.Helper()
		run := automationstore.AutomationRun{ID: runID, WorkspaceID: "ws-1", Name: "Historical task wait", Status: automationstore.AutomationRunStatusWaiting, CreatedByPrincipalID: "user-1", CreatedAt: now, UpdatedAt: now}
		rt, err := runtime.BuildInitialRuntime(run, spec, now, newID)
		require.NoError(t, err)
		require.Len(t, rt.Waits, 1)
		_, err = store.CreateRunWithRuntime(ctx, run, rt.JobRuns, rt.Events, rt.Jobs, rt.Waits)
		require.NoError(t, err)
		return rt
	}
	historicalRuntime := createRun("run-deliver-history-resolved")
	activeRuntime := createRun("run-deliver-history-active")

	_, _, err = store.ResolveWait(ctx, "ws-1", historicalRuntime.Waits[0].ID, "automation-eventer", `{"task_id":"tsk_history","task_number":42,"status":"done"}`, now.Add(time.Minute))
	require.NoError(t, err)
	historicalJobRuns, err := store.ListJobRuns(ctx, "ws-1", "run-deliver-history-resolved")
	require.NoError(t, err)
	require.Len(t, historicalJobRuns, 2)
	completedAt := now.Add(90 * time.Second)
	historicalJobRuns[1].Status = automationstore.AutomationJobRunStatusSucceeded
	historicalJobRuns[1].CompletedAt = &completedAt
	historicalJobRuns[1].UpdatedAt = completedAt
	_, err = store.UpdateJobRun(ctx, historicalJobRuns[1])
	require.NoError(t, err)

	authorizer := &recordingAutomationAuthorizer{}
	service := NewAutomationsService(store, func() time.Time { return now.Add(2 * time.Minute) }, nil)
	service.SetAuthorizer(authorizer)
	delivered, err := service.DeliverAutomationWaitSignal(ctx, "ws-1", "automation-eventer", AutomationWaitSignalMutation{WaitKind: automationstore.AutomationWaitKindAgentTask, ResourceID: "tsk_history", TaskNumber: 42, Status: "done"})
	require.NoError(t, err)
	require.Equal(t, automationstore.AutomationWaitStatusResolved, delivered.Status)
	require.Contains(t, authorizer.resumeWaitIDs, activeRuntime.Waits[0].ID)
	require.NotContains(t, authorizer.resumeWaitIDs, historicalRuntime.Waits[0].ID)

	activeJobRuns, err := store.ListJobRuns(ctx, "ws-1", "run-deliver-history-active")
	require.NoError(t, err)
	require.Equal(t, automationstore.AutomationJobRunStatusSucceeded, activeJobRuns[0].Status)
	require.Equal(t, automationstore.AutomationJobRunStatusQueued, activeJobRuns[1].Status)
}

func TestDeliverAutomationWaitSignalRejectsWrongResourceAndWorkspace(t *testing.T) {
	ctx := context.Background()
	db, err := openServiceTestSQLiteDB(t)
	require.NoError(t, err)
	migrateAutomationServiceTestSchema(t, db)
	store := automationstore.New(db)
	now := time.Date(2026, 6, 7, 10, 0, 0, 0, time.UTC)
	activeKey := "ws-1:run-deliver-event:job-wait"
	_, err = store.CreateRun(ctx, automationstore.AutomationRun{ID: "run-deliver-event", WorkspaceID: "ws-1", Name: "Event wait", Status: automationstore.AutomationRunStatusWaiting, CreatedAt: now, UpdatedAt: now})
	require.NoError(t, err)
	_, err = store.CreateWait(ctx,
		automationstore.AutomationWait{ID: "wait-deliver-event", WorkspaceID: "ws-1", RunID: "run-deliver-event", JobRunID: "job-wait", WaitKind: automationstore.AutomationWaitKindWaitEvent, Status: automationstore.AutomationWaitStatusActive, ActiveKey: &activeKey, ResourceType: "provider", ResourceID: "installation-1", ResumeEventType: "provider.ready", CreatedAt: now, UpdatedAt: now},
		automationstore.AutomationJobRun{ID: "job-wait", WorkspaceID: "ws-1", RunID: "run-deliver-event", JobID: "wait", Status: automationstore.AutomationJobRunStatusWaiting, ScheduledAt: now, CreatedAt: now, UpdatedAt: now},
		automationstore.AutomationEvent{ID: "event-wait", WorkspaceID: "ws-1", RunID: "run-deliver-event", EventType: "wait_started", CreatedAt: now})
	require.NoError(t, err)

	service := NewAutomationsService(store, func() time.Time { return now }, nil)
	_, err = service.DeliverAutomationWaitSignal(ctx, "ws-1", "automation-eventer", AutomationWaitSignalMutation{WaitKind: automationstore.AutomationWaitKindWaitEvent, EventType: "provider.ready", ResourceType: "provider", ResourceID: "installation-2"})
	require.True(t, errors.Is(err, ErrNotFound), "wrong resource should not resume a different wait: %v", err)
	_, err = service.DeliverAutomationWaitSignal(ctx, "ws-2", "automation-eventer", AutomationWaitSignalMutation{WaitKind: automationstore.AutomationWaitKindWaitEvent, EventType: "provider.ready", ResourceType: "provider", ResourceID: "installation-1"})
	require.True(t, errors.Is(err, ErrNotFound), "wrong workspace should not resume a wait: %v", err)
	_, err = service.DeliverAutomationWaitSignal(ctx, "ws-1", "automation-eventer", AutomationWaitSignalMutation{WaitKind: automationstore.AutomationWaitKindAgentTask, ResourceID: "tsk_1", TaskID: "tsk_2"})
	require.True(t, errors.Is(err, ErrInvalidArgument), "mismatched task/resource ids should be rejected: %v", err)
}

func TestAutomationLimitsRejectSourceAndRunInput(t *testing.T) {
	ctx := context.Background()
	db, err := openServiceTestSQLiteDB(t)
	require.NoError(t, err)
	migrateAutomationServiceTestSchema(t, db)
	store := automationstore.New(db)
	now := time.Date(2026, 6, 5, 10, 0, 0, 0, time.UTC)

	service := NewAutomationsService(store, func() time.Time { return now }, staticAutomationSourceResolver{content: strings.Repeat("x", 32)})
	service.SetLimits(AutomationLimits{MaxAutomationSourceBytes: 8, MaxRunInputBytes: 1024})
	_, err = service.StartRepositoryAutomationRun(ctx, "ws-1", "user-1", AutomationRepositoryRunMutation{RepositoryOwner: "pingcap", RepositoryName: "agentteam", DefinitionPath: ".agentteam/build.yml", GitRef: "main"})
	require.ErrorIs(t, err, ErrInvalidArgument)
	require.Contains(t, err.Error(), "source exceeds")

	report, err := service.ValidateRepositoryAutomationContent(ctx, "ws-1", "user-1", AutomationRepositoryAutomationValidateMutation{RepositoryOwner: "pingcap", RepositoryName: "agentteam", DefinitionPath: ".agentteam/build.yml", Content: strings.Repeat("x", 32)})
	require.NoError(t, err)
	require.False(t, report.Valid)
	require.Equal(t, "automation_source_limit_exceeded", report.Errors[0].Code)

	service.SetLimits(AutomationLimits{MaxAutomationSourceBytes: 1024, MaxRunInputBytes: 4})
	_, err = service.StartRepositoryAutomationRun(ctx, "ws-1", "user-1", AutomationRepositoryRunMutation{RepositoryOwner: "pingcap", RepositoryName: "agentteam", DefinitionPath: ".agentteam/build.yml", GitRef: "main", InputJSON: `{"a":1}`})
	require.ErrorIs(t, err, ErrInvalidArgument)
	require.Contains(t, err.Error(), "input_json exceeds")

	_, err = store.CreateRunWithRuntimeWithCapacity(ctx, 1, automationstore.AutomationRun{ID: "active-run", WorkspaceID: "ws-1", Name: "Active", Status: automationstore.AutomationRunStatusRunning, CreatedAt: now, UpdatedAt: now}, nil, nil, nil)
	require.NoError(t, err)
	service.sourceResolver = staticAutomationSourceResolver{content: "name: Limited\non:\n  manual: {}\njobs:\n  build:\n    steps:\n      - run: echo hi\n"}
	service.SetLimits(AutomationLimits{MaxAutomationSourceBytes: 1024, MaxRunInputBytes: 1024, MaxActiveRunsPerWorkspace: 1})
	_, err = service.StartRepositoryAutomationRun(ctx, "ws-1", "user-1", AutomationRepositoryRunMutation{RepositoryOwner: "pingcap", RepositoryName: "agentteam", DefinitionPath: ".agentteam/build.yml", GitRef: "main"})
	require.ErrorIs(t, err, ErrConflict)
	require.Equal(t, http.StatusConflict, HTTPStatus(err))
	require.Contains(t, err.Error(), "active run limit")
}

func TestStartRepositoryAutomationRunRejectsNonAutomationFilePath(t *testing.T) {
	ctx := context.Background()
	db, err := openServiceTestSQLiteDB(t)
	require.NoError(t, err)
	migrateAutomationServiceTestSchema(t, db)
	store := automationstore.New(db)
	now := time.Date(2026, 6, 5, 10, 30, 0, 0, time.UTC)

	service := NewAutomationsService(store, func() time.Time { return now }, staticAutomationSourceResolver{content: "name: Build\non:\n  manual: {}\njobs:\n  build:\n    steps:\n      - run: echo hi\n"})
	_, err = service.StartRepositoryAutomationRun(ctx, "ws-1", "user-1", AutomationRepositoryRunMutation{RepositoryOwner: "pingcap", RepositoryName: "agentteam", DefinitionPath: ".agentteam/build.txt", GitRef: "main"})
	require.ErrorIs(t, err, ErrInvalidArgument)
	require.Contains(t, err.Error(), "Automation definition path must be under .agentteam and end in .yml or .yaml")
}

func TestStartRepositoryAutomationRunRejectsMissingManual(t *testing.T) {
	ctx := context.Background()
	db, err := openServiceTestSQLiteDB(t)
	require.NoError(t, err)
	migrateAutomationServiceTestSchema(t, db)
	store := automationstore.New(db)
	now := time.Date(2026, 6, 5, 10, 45, 0, 0, time.UTC)

	service := NewAutomationsService(store, func() time.Time { return now }, staticAutomationSourceResolver{content: "name: Build\non: push\njobs:\n  build:\n    steps:\n      - run: echo hi\n"})
	_, err = service.StartRepositoryAutomationRun(ctx, "ws-1", "user-1", AutomationRepositoryRunMutation{RepositoryOwner: "pingcap", RepositoryName: "agentteam", DefinitionPath: ".agentteam/build.yml", GitRef: "main"})
	require.ErrorIs(t, err, ErrInvalidArgument)
	require.Contains(t, err.Error(), "manual")

	runs, err := store.ListRuns(ctx, "ws-1", "", "", "", 1, 10)
	require.NoError(t, err)
	require.Empty(t, runs.Runs)
}

func TestStartRepositoryAutomationRunRejectsMissingManualButPersistsAutomationRepositoryBinding(t *testing.T) {
	ctx := context.Background()
	db, err := openServiceTestSQLiteDB(t)
	require.NoError(t, err)
	migrateAutomationServiceTestSchema(t, db)
	store := automationstore.New(db)
	now := time.Date(2026, 6, 5, 10, 46, 0, 0, time.UTC)
	repoStore := newAutomationRepositoryStoreForAutomationsTest(t)

	service := NewAutomationsService(store, func() time.Time { return now }, staticAutomationSourceResolver{content: "name: Build\non: push\njobs:\n  build:\n    steps:\n      - run: echo hi\n"})
	service.SetRepositoryStore(repoStore)
	_, err = service.StartRepositoryAutomationRun(ctx, "ws-1", "user-1", AutomationRepositoryRunMutation{RepositoryOwner: "pingcap", RepositoryName: "agentteam", DefinitionPath: ".agentteam/build.yml", GitRef: "refs/heads/main"})
	require.ErrorIs(t, err, ErrInvalidArgument)
	require.Contains(t, err.Error(), "manual")

	binding, err := repoStore.Get(ctx, "ws-1", repos.ResourceTypeAutomationRepository, repos.AutomationRepositoryResourceID("pingcap/agentteam"), repos.RepoRolePrimary)
	require.NoError(t, err)
	require.Equal(t, "pingcap/agentteam", binding.RepoFullName)
	require.Equal(t, "main", binding.DefaultRef)
}

func TestStartRepositoryAutomationRunIsIdempotentByKey(t *testing.T) {
	ctx := context.Background()
	db, err := openServiceTestSQLiteDB(t)
	require.NoError(t, err)
	migrateAutomationServiceTestSchema(t, db)
	store := automationstore.New(db)
	now := time.Date(2026, 6, 5, 10, 50, 0, 0, time.UTC)
	resolver := &countingAutomationSourceResolver{content: "name: Build\non:\n  manual: {}\njobs:\n  build:\n    steps:\n      - run: echo hi\n"}
	service := NewAutomationsService(store, func() time.Time { return now }, resolver)
	service.SetLimits(AutomationLimits{MaxActiveRunsPerWorkspace: 1})
	var signals []string
	service.SetRunnerSignal(func(workspaceID string) {
		signals = append(signals, workspaceID)
	})

	first, err := service.StartRepositoryAutomationRun(ctx, "ws-1", "user-1", AutomationRepositoryRunMutation{
		RepositoryOwner: "pingcap",
		RepositoryName:  "agentteam",
		DefinitionPath:  ".agentteam/build.yml",
		GitRef:          "main",
		IdempotencyKey:  "schedule:sch_1:2026-06-05T10:50:00Z",
		TriggerKind:     "schedule",
		TriggerRef:      "arun_1",
		TargetKind:      "schedule",
		TargetRef:       "sch_1",
	})
	require.NoError(t, err)
	require.NotNil(t, first.IdempotencyKey)
	require.Equal(t, "schedule:sch_1:2026-06-05T10:50:00Z", *first.IdempotencyKey)

	second, err := service.StartRepositoryAutomationRun(ctx, "ws-1", "user-1", AutomationRepositoryRunMutation{
		RepositoryOwner: "pingcap",
		RepositoryName:  "agentteam",
		DefinitionPath:  ".agentteam/build.yml",
		GitRef:          "main",
		IdempotencyKey:  "schedule:sch_1:2026-06-05T10:50:00Z",
		TriggerKind:     "schedule",
		TriggerRef:      "arun_1",
		TargetKind:      "schedule",
		TargetRef:       "sch_1",
	})
	require.NoError(t, err)
	require.Equal(t, first.ID, second.ID)
	require.Equal(t, "main", second.GitRef)
	require.Equal(t, "arun_1", second.TriggerRef)
	require.Equal(t, 1, resolver.calls)

	runs, err := store.ListRuns(ctx, "ws-1", "", "", "", 1, 10)
	require.NoError(t, err)
	require.Len(t, runs.Runs, 1)
	jobRuns, err := store.ListJobRuns(ctx, "ws-1", first.ID)
	require.NoError(t, err)
	require.Len(t, jobRuns, 1)
	require.Equal(t, []string{"ws-1", "ws-1"}, signals)
}

func TestStartRepositoryAutomationRunRejectsIdempotencyReplayMismatch(t *testing.T) {
	ctx := context.Background()
	db, err := openServiceTestSQLiteDB(t)
	require.NoError(t, err)
	migrateAutomationServiceTestSchema(t, db)
	store := automationstore.New(db)
	now := time.Date(2026, 6, 5, 11, 0, 0, 0, time.UTC)
	resolver := &countingAutomationSourceResolver{content: "name: Build\non:\n  manual: {}\njobs:\n  build:\n    steps:\n      - run: echo hi\n"}
	service := NewAutomationsService(store, func() time.Time { return now }, resolver)

	input := AutomationRepositoryRunMutation{
		RepositoryOwner: "pingcap",
		RepositoryName:  "agentteam",
		DefinitionPath:  ".agentteam/build.yml",
		GitRef:          "main",
		IdempotencyKey:  "schedule:sch_1:2026-06-05T11:00:00Z",
		TriggerKind:     "schedule",
		TriggerRef:      "arun_1",
		TargetKind:      "schedule",
		TargetRef:       "sch_1",
	}
	first, err := service.StartRepositoryAutomationRun(ctx, "ws-1", "agentteam-automation-scheduler", input)
	require.NoError(t, err)
	require.NotEmpty(t, first.ID)
	require.JSONEq(t, `{"trigger_context":{"trigger_kind":"schedule","schedule_definition_id":"sch_1","automation_run_id":"arun_1"}}`, first.Metadata)

	_, err = service.StartRepositoryAutomationRun(ctx, "ws-1", "other-service", input)
	require.ErrorIs(t, err, ErrConflict)
	require.Contains(t, err.Error(), "idempotency key already belongs")

	mismatched := input
	mismatched.TriggerRef = "arun_guess"
	_, err = service.StartRepositoryAutomationRun(ctx, "ws-1", "agentteam-automation-scheduler", mismatched)
	require.ErrorIs(t, err, ErrConflict)
	require.Contains(t, err.Error(), "idempotency key already belongs")
	require.Equal(t, 1, resolver.calls)
}

func TestPreflightRepositoryAutomationReturnsStructuredManualStartIssue(t *testing.T) {
	ctx := context.Background()
	db, err := openServiceTestSQLiteDB(t)
	require.NoError(t, err)
	migrateAutomationServiceTestSchema(t, db)
	store := automationstore.New(db)
	now := time.Date(2026, 6, 5, 10, 50, 0, 0, time.UTC)

	service := NewAutomationsService(store, func() time.Time { return now }, staticAutomationSourceResolver{content: "name: Build\non: push\njobs:\n  build:\n    steps:\n      - run: echo hi\n"})
	report, err := service.PreflightRepositoryAutomation(ctx, "ws-1", "user-1", AutomationRepositoryAutomationPreflightMutation{
		RepositoryOwner: "pingcap",
		RepositoryName:  "agentteam",
		DefinitionPath:  ".agentteam/build.yml",
		GitRef:          "main",
		Scenario:        automationPreflightScenarioManualStart,
	})
	require.NoError(t, err)
	require.False(t, report.Runnable)
	require.Equal(t, automationPreflightStartModeSavedSourceOnly, report.StartMode)
	require.Equal(t, automationPreflightStatusOK, report.Sections.SourceResolution.Status)
	require.Equal(t, automationPreflightStatusOK, report.Sections.AutomationSyntax.Status)
	require.Equal(t, automationPreflightStatusError, report.Sections.TriggerSupport.Status)
	require.Equal(t, automationCodeMissingManual, report.Overall.PrimaryIssueCode)
	require.Len(t, report.Issues, 1)
	require.Equal(t, automationCodeMissingManual, report.Issues[0].Code)
	require.Equal(t, "trigger_support", report.Issues[0].Section)
}

func TestStartRepositoryAutomationRunAllowsScheduleWithoutManual(t *testing.T) {
	ctx := context.Background()
	db, err := openServiceTestSQLiteDB(t)
	require.NoError(t, err)
	migrateAutomationServiceTestSchema(t, db)
	store := automationstore.New(db)
	now := time.Date(2026, 6, 5, 11, 0, 0, 0, time.UTC)

	service := NewAutomationsService(store, func() time.Time { return now }, staticAutomationSourceResolver{content: "name: Build\non: push\njobs:\n  build:\n    steps:\n      - run: echo hi\n"})
	run, err := service.StartRepositoryAutomationRun(ctx, "ws-1", "user-1", AutomationRepositoryRunMutation{
		RepositoryOwner: "pingcap",
		RepositoryName:  "agentteam",
		DefinitionPath:  ".agentteam/build.yml",
		GitRef:          "main",
		TriggerKind:     "schedule",
		TriggerRef:      "arun_1",
		TargetKind:      "schedule",
		TargetRef:       "sched_1",
	})
	require.NoError(t, err)
	require.Equal(t, automationstore.AutomationRunStatusRunning, run.Status)
	triggerContext := ParseAutomationRunTriggerContext(run.Metadata)
	require.NotNil(t, triggerContext)
	require.Equal(t, "schedule", triggerContext.TriggerKind)
	require.Equal(t, "sched_1", triggerContext.ScheduleDefinitionID)
	require.Equal(t, "arun_1", triggerContext.AutomationRunID)
}

func TestStartRepositoryAutomationRunCreatesFailedPushRunForUnsupportedSyntax(t *testing.T) {
	ctx := context.Background()
	db, err := openServiceTestSQLiteDB(t)
	require.NoError(t, err)
	migrateAutomationServiceTestSchema(t, db)
	store := automationstore.New(db)
	now := time.Date(2026, 6, 5, 11, 15, 0, 0, time.UTC)

	service := NewAutomationsService(store, func() time.Time { return now }, staticAutomationSourceResolver{content: "name: Build\non:\n  push:\n    branches:\n      - main\njobs:\n  build:\n    steps:\n      - uses: actions/checkout@v4\n"})
	run, err := service.StartRepositoryAutomationRun(ctx, "ws-1", "user-1", AutomationRepositoryRunMutation{
		RepositoryOwner: "pingcap",
		RepositoryName:  "agentteam",
		DefinitionPath:  ".agentteam/build.yml",
		GitRef:          "0123456789abcdef0123456789abcdef01234567",
		Branch:          "main",
		BeforeSHA:       "1111111111111111111111111111111111111111",
		ActorRef:        "user:1",
		ActorDisplay:    "Alice",
		TriggerKind:     "push",
		TriggerRef:      "evt_1",
	})
	require.NoError(t, err)
	require.Equal(t, automationstore.AutomationRunStatusFailed, run.Status)
	structured := ParseAutomationRunError(run.ErrorJSON)
	require.NotNil(t, structured)
	require.Equal(t, automationCodeUnsupportedSyntax, structured.Code)
	triggerContext := ParseAutomationRunTriggerContext(run.Metadata)
	require.NotNil(t, triggerContext)
	require.Equal(t, "push", triggerContext.TriggerKind)
	require.Equal(t, "main", triggerContext.Branch)
	require.Equal(t, "refs/heads/main", triggerContext.Ref)
	require.Equal(t, "1111111111111111111111111111111111111111", triggerContext.BeforeSHA)
	require.Equal(t, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", triggerContext.AfterSHA)
	require.Equal(t, "Alice", triggerContext.ActorDisplay)

	runs, err := store.ListRuns(ctx, "ws-1", "", "", "", 1, 10)
	require.NoError(t, err)
	require.Len(t, runs.Runs, 1)
	require.Equal(t, automationstore.AutomationRunStatusFailed, runs.Runs[0].Status)
}

func TestStartRepositoryAutomationRunRejectsUnmatchedPushBranch(t *testing.T) {
	ctx := context.Background()
	db, err := openServiceTestSQLiteDB(t)
	require.NoError(t, err)
	migrateAutomationServiceTestSchema(t, db)
	store := automationstore.New(db)
	now := time.Date(2026, 6, 5, 11, 30, 0, 0, time.UTC)

	service := NewAutomationsService(store, func() time.Time { return now }, staticAutomationSourceResolver{content: "name: Build\non:\n  push:\n    branches:\n      - main\njobs:\n  build:\n    steps:\n      - run: echo hi\n"})
	_, err = service.StartRepositoryAutomationRun(ctx, "ws-1", "user-1", AutomationRepositoryRunMutation{
		RepositoryOwner: "pingcap",
		RepositoryName:  "agentteam",
		DefinitionPath:  ".agentteam/build.yml",
		GitRef:          "0123456789abcdef0123456789abcdef01234567",
		Branch:          "dev",
		TriggerKind:     "push",
		TriggerRef:      "evt_1",
	})
	require.ErrorIs(t, err, ErrInvalidArgument)
	require.Contains(t, err.Error(), "does not match")

	runs, err := store.ListRuns(ctx, "ws-1", "", "", "", 1, 10)
	require.NoError(t, err)
	require.Empty(t, runs.Runs)
}

func TestStartRepositoryAutomationRunRejectsManualStartWhenNoMatchingRunner(t *testing.T) {
	ctx := context.Background()
	db, err := openServiceTestSQLiteDB(t)
	require.NoError(t, err)
	migrateAutomationServiceTestSchema(t, db, &automationstore.AutomationRunner{}, &automationstore.AutomationRunnerSession{})
	store := automationstore.New(db)
	now := time.Date(2026, 6, 5, 11, 40, 0, 0, time.UTC)

	service := NewAutomationsService(store, func() time.Time { return now }, staticAutomationSourceResolver{content: "name: Build\non:\n  manual: {}\njobs:\n  build:\n    runs-on:\n      - self-hosted\n      - linux\n    steps:\n      - run: echo hi\n"})
	_, err = service.StartRepositoryAutomationRun(ctx, "ws-1", "user-1", AutomationRepositoryRunMutation{
		RepositoryOwner: "pingcap",
		RepositoryName:  "agentteam",
		DefinitionPath:  ".agentteam/build.yml",
		GitRef:          "main",
	})
	require.ErrorIs(t, err, ErrInvalidArgument)
	require.Equal(t, "runner_no_matching_runner", AutomationCode(err))

	runs, err := store.ListRuns(ctx, "ws-1", "", "", "", 1, 10)
	require.NoError(t, err)
	require.Empty(t, runs.Runs)
}

func TestStartRepositoryAutomationRunRejectsScheduleWhenNoMatchingRunner(t *testing.T) {
	ctx := context.Background()
	db, err := openServiceTestSQLiteDB(t)
	require.NoError(t, err)
	migrateAutomationServiceTestSchema(t, db, &automationstore.AutomationRunner{}, &automationstore.AutomationRunnerSession{})
	store := automationstore.New(db)
	now := time.Date(2026, 6, 5, 11, 42, 0, 0, time.UTC)

	service := NewAutomationsService(store, func() time.Time { return now }, staticAutomationSourceResolver{content: "name: Build\non: push\njobs:\n  build:\n    runs-on:\n      - self-hosted\n      - linux\n    steps:\n      - run: echo hi\n"})
	_, err = service.StartRepositoryAutomationRun(ctx, "ws-1", "user-1", AutomationRepositoryRunMutation{
		RepositoryOwner: "pingcap",
		RepositoryName:  "agentteam",
		DefinitionPath:  ".agentteam/build.yml",
		GitRef:          "main",
		TriggerKind:     "schedule",
		TriggerRef:      "arun_1",
		TargetKind:      "schedule",
		TargetRef:       "sched_1",
	})
	require.ErrorIs(t, err, ErrInvalidArgument)
	require.Equal(t, "runner_no_matching_runner", AutomationCode(err))

	runs, err := store.ListRuns(ctx, "ws-1", "", "", "", 1, 10)
	require.NoError(t, err)
	require.Empty(t, runs.Runs)
}

func TestStartRepositoryAutomationRunCreatesFailedPushRunForNoMatchingRunner(t *testing.T) {
	ctx := context.Background()
	db, err := openServiceTestSQLiteDB(t)
	require.NoError(t, err)
	migrateAutomationServiceTestSchema(t, db, &automationstore.AutomationRunner{}, &automationstore.AutomationRunnerSession{})
	store := automationstore.New(db)
	now := time.Date(2026, 6, 5, 11, 44, 0, 0, time.UTC)

	service := NewAutomationsService(store, func() time.Time { return now }, staticAutomationSourceResolver{content: "name: Build\non:\n  push:\n    branches:\n      - main\njobs:\n  build:\n    runs-on:\n      - self-hosted\n      - linux\n    steps:\n      - run: echo hi\n"})
	run, err := service.StartRepositoryAutomationRun(ctx, "ws-1", "user-1", AutomationRepositoryRunMutation{
		RepositoryOwner: "pingcap",
		RepositoryName:  "agentteam",
		DefinitionPath:  ".agentteam/build.yml",
		GitRef:          "0123456789abcdef0123456789abcdef01234567",
		Branch:          "main",
		BeforeSHA:       "1111111111111111111111111111111111111111",
		TriggerKind:     "push",
		TriggerRef:      "evt_1",
	})
	require.NoError(t, err)
	require.Equal(t, automationstore.AutomationRunStatusFailed, run.Status)
	structured := ParseAutomationRunError(run.ErrorJSON)
	require.NotNil(t, structured)
	require.Equal(t, "runner_no_matching_runner", structured.Code)

	runs, err := store.ListRuns(ctx, "ws-1", "", "", "", 1, 10)
	require.NoError(t, err)
	require.Len(t, runs.Runs, 1)
	require.Equal(t, automationstore.AutomationRunStatusFailed, runs.Runs[0].Status)
}

func TestGetRepositoryAutomationRunnerReadinessReturnsNoMatchingRunner(t *testing.T) {
	ctx := context.Background()
	db, err := openServiceTestSQLiteDB(t)
	require.NoError(t, err)
	migrateAutomationServiceTestSchema(t, db, &automationstore.AutomationRunner{}, &automationstore.AutomationRunnerSession{})
	store := automationstore.New(db)
	now := time.Date(2026, 6, 5, 11, 45, 0, 0, time.UTC)

	service := NewAutomationsService(store, func() time.Time { return now }, staticAutomationSourceResolver{content: "name: Build\non:\n  manual: {}\njobs:\n  build:\n    runs-on:\n      - self-hosted\n      - linux\n    steps:\n      - run: echo hi\n"})
	readiness, err := service.GetRepositoryAutomationRunnerReadiness(ctx, "ws-1", "user-1", AutomationRepositoryAutomationQuery{
		RepositoryOwner: "pingcap",
		RepositoryName:  "agentteam",
		DefinitionPath:  ".agentteam/build.yml",
		GitRef:          "main",
	})
	require.NoError(t, err)
	require.False(t, readiness.Runnable)
	require.Equal(t, "runner_no_matching_runner", readiness.ReasonCode)
	require.EqualValues(t, 0, readiness.MatchingRunnerCount)
	require.Len(t, readiness.Pools, 1)
	require.Equal(t, "runner_no_matching_runner", readiness.Pools[0].ReasonCode)
}

func TestPreflightRepositoryAutomationReturnsRunnerAndRepoCredentialReadiness(t *testing.T) {
	ctx := context.Background()
	db, err := openServiceTestSQLiteDB(t)
	require.NoError(t, err)
	migrateAutomationServiceTestSchema(t, db, &automationstore.AutomationRunner{}, &automationstore.AutomationRunnerSession{})
	store := automationstore.New(db)
	now := time.Date(2026, 6, 5, 12, 15, 0, 0, time.UTC)

	require.NoError(t, db.Create(&automationstore.AutomationRunner{
		WorkspaceID:  "ws-1",
		RunnerID:     "runner-1",
		Name:         "Runner 1",
		LabelsJSON:   `["self-hosted","linux","x64"]`,
		Status:       automationstore.AutomationRunnerStatusActive,
		RegisteredAt: now,
		CreatedAt:    now,
		UpdatedAt:    now,
	}).Error)
	require.NoError(t, db.Create(&automationstore.AutomationRunnerSession{
		WorkspaceID: "ws-1",
		RunnerID:    "runner-1",
		SessionID:   "session-1",
		LeaseOwner:  "runner-1:session-1",
		LabelsJSON:  `["self-hosted","linux","x64"]`,
		Status:      automationstore.AutomationRunnerSessionStatusActive,
		HeartbeatAt: now,
		ExpiresAt:   now.Add(time.Hour),
		CreatedAt:   now,
		UpdatedAt:   now,
	}).Error)

	service := NewAutomationsService(store, func() time.Time { return now }, staticAutomationSourceResolver{content: "name: Build\non:\n  manual: {}\njobs:\n  build:\n    runs-on:\n      - self-hosted\n      - linux\n    steps:\n      - run: agentteam-checkout\n      - run: echo hi\n"})
	service.SetRepositoryCredentialChecker(staticAutomationRepositoryCredentialChecker{available: true, scope: automationRepositoryTokenScopeReadOnly})

	report, err := service.PreflightRepositoryAutomation(ctx, "ws-1", "user-1", AutomationRepositoryAutomationPreflightMutation{
		RepositoryOwner: "pingcap",
		RepositoryName:  "agentteam",
		DefinitionPath:  ".agentteam/build.yml",
		GitRef:          "main",
		Scenario:        automationPreflightScenarioManualStart,
	})
	require.NoError(t, err)
	require.True(t, report.Runnable)
	require.Equal(t, automationPreflightStatusOK, report.Sections.RunnerReadiness.Status)
	require.EqualValues(t, 1, report.Sections.RunnerReadiness.MatchingRunnerCount)
	require.EqualValues(t, 1, report.Sections.RunnerReadiness.OnlineRunnerCount)
	require.True(t, report.Sections.RunnerReadiness.Runnable)
	require.Equal(t, automationPreflightStatusOK, report.Sections.RepoCredentialReadiness.Status)
	require.True(t, report.Sections.RepoCredentialReadiness.CheckoutSupported)
	require.True(t, report.Sections.RepoCredentialReadiness.RepoTokenAvailable)
	require.Equal(t, automationRepositoryTokenScopeReadOnly, report.Sections.RepoCredentialReadiness.RepoTokenScope)
}

func TestStartRepositoryAutomationRunRejectsMissingRepoCredential(t *testing.T) {
	ctx := context.Background()
	db, err := openServiceTestSQLiteDB(t)
	require.NoError(t, err)
	migrateAutomationServiceTestSchema(t, db, &automationstore.AutomationRunner{}, &automationstore.AutomationRunnerSession{})
	store := automationstore.New(db)
	now := time.Date(2026, 6, 5, 12, 20, 0, 0, time.UTC)

	require.NoError(t, db.Create(&automationstore.AutomationRunner{
		WorkspaceID:  "ws-1",
		RunnerID:     "runner-1",
		Name:         "Runner 1",
		LabelsJSON:   `["self-hosted","linux","x64"]`,
		Status:       automationstore.AutomationRunnerStatusActive,
		RegisteredAt: now,
		CreatedAt:    now,
		UpdatedAt:    now,
	}).Error)
	require.NoError(t, db.Create(&automationstore.AutomationRunnerSession{
		WorkspaceID: "ws-1",
		RunnerID:    "runner-1",
		SessionID:   "session-1",
		LeaseOwner:  "runner-1:session-1",
		LabelsJSON:  `["self-hosted","linux","x64"]`,
		Status:      automationstore.AutomationRunnerSessionStatusActive,
		HeartbeatAt: now,
		ExpiresAt:   now.Add(time.Hour),
		CreatedAt:   now,
		UpdatedAt:   now,
	}).Error)

	service := NewAutomationsService(store, func() time.Time { return now }, staticAutomationSourceResolver{content: "name: Build\non:\n  manual: {}\njobs:\n  build:\n    runs-on:\n      - self-hosted\n      - linux\n    steps:\n      - run: agentteam-checkout\n      - run: echo hi\n"})
	service.SetRepositoryCredentialChecker(staticAutomationRepositoryCredentialChecker{available: false, scope: automationRepositoryTokenScopeReadOnly})

	_, err = service.StartRepositoryAutomationRun(ctx, "ws-1", "user-1", AutomationRepositoryRunMutation{
		RepositoryOwner: "pingcap",
		RepositoryName:  "agentteam",
		DefinitionPath:  ".agentteam/build.yml",
		GitRef:          "main",
	})
	require.ErrorIs(t, err, ErrInvalidArgument)
	require.Equal(t, automationCodeCredentialSecretMissing, AutomationCode(err))

	runs, err := store.ListRuns(ctx, "ws-1", "", "", "", 1, 10)
	require.NoError(t, err)
	require.Empty(t, runs.Runs)
}

func TestPreflightRepositoryAutomationReturnsRunnerAndRepoCredentialIssues(t *testing.T) {
	ctx := context.Background()
	db, err := openServiceTestSQLiteDB(t)
	require.NoError(t, err)
	migrateAutomationServiceTestSchema(t, db, &automationstore.AutomationRunner{}, &automationstore.AutomationRunnerSession{})
	store := automationstore.New(db)
	now := time.Date(2026, 6, 5, 12, 30, 0, 0, time.UTC)

	service := NewAutomationsService(store, func() time.Time { return now }, staticAutomationSourceResolver{content: "name: Build\non:\n  manual: {}\njobs:\n  build:\n    runs-on:\n      - self-hosted\n      - linux\n    steps:\n      - run: agentteam-checkout\n      - run: echo hi\n"})
	service.SetRepositoryCredentialChecker(staticAutomationRepositoryCredentialChecker{available: false, scope: automationRepositoryTokenScopeReadOnly})

	report, err := service.PreflightRepositoryAutomation(ctx, "ws-1", "user-1", AutomationRepositoryAutomationPreflightMutation{
		RepositoryOwner: "pingcap",
		RepositoryName:  "agentteam",
		DefinitionPath:  ".agentteam/build.yml",
		GitRef:          "main",
		Scenario:        automationPreflightScenarioManualStart,
	})
	require.NoError(t, err)
	require.False(t, report.Runnable)
	require.Equal(t, automationPreflightStatusError, report.Sections.RunnerReadiness.Status)
	require.Equal(t, "runner_no_matching_runner", report.Sections.RunnerReadiness.ReasonCode)
	require.Equal(t, automationPreflightStatusError, report.Sections.RepoCredentialReadiness.Status)
	require.True(t, report.Sections.RepoCredentialReadiness.CheckoutSupported)
	require.False(t, report.Sections.RepoCredentialReadiness.RepoTokenAvailable)
	require.Equal(t, "runner_no_matching_runner", report.Overall.PrimaryIssueCode)
	require.Len(t, report.Issues, 2)
	require.Equal(t, "runner_no_matching_runner", report.Issues[0].Code)
	require.Equal(t, automationCodeCredentialSecretMissing, report.Issues[1].Code)
}

func TestAutomationRunnerLimitsRejectLabelsLogsAndArtifacts(t *testing.T) {
	ctx := context.Background()
	db, err := openServiceTestSQLiteDB(t)
	require.NoError(t, err)
	migrateAutomationServiceTestSchema(t, db)
	store := automationstore.New(db)
	now := time.Date(2026, 6, 5, 11, 0, 0, 0, time.UTC)
	service := NewAutomationsService(store, func() time.Time { return now }, nil)

	service.SetLimits(AutomationLimits{MaxRunnerLabels: 32})
	_, err = service.RegisterAutomationRunner(ctx, "ws-1", "user-1", AutomationRunnerRegisterMutation{RunnerID: "runner-many-labels", Labels: []string{"a", "b", "c", "d", "e", "f", "g", "h", "i"}})
	require.ErrorIs(t, err, ErrInvalidArgument)
	require.Contains(t, err.Error(), "labels exceed")

	service.SetLimits(AutomationLimits{MaxRunnerLabels: 1, MaxLogChunkBytes: 3, MaxArtifactBytes: 10})

	_, err = service.RegisterAutomationRunner(ctx, "ws-1", "user-1", AutomationRunnerRegisterMutation{RunnerID: "runner-1", Labels: []string{"linux", "x64"}})
	require.ErrorIs(t, err, ErrInvalidArgument)
	require.Contains(t, err.Error(), "labels exceed")

	_, err = service.RegisterAutomationRunner(ctx, "ws-1", "user-1", AutomationRunnerRegisterMutation{RunnerID: "runner-long-group", RunnerGroup: strings.Repeat("g", dsl.RunnerGroupMaxLength+1), Labels: []string{"linux"}})
	require.ErrorIs(t, err, ErrInvalidArgument)
	require.Contains(t, err.Error(), "group exceeds")

	_, err = service.RegisterAutomationRunner(ctx, "ws-1", "user-1", AutomationRunnerRegisterMutation{RunnerID: "runner-reserved-group", RunnerGroup: runtime.ServerManagedRunnerGroup, Labels: []string{"linux"}})
	require.ErrorIs(t, err, ErrInvalidArgument)
	require.Contains(t, err.Error(), "group is reserved")

	_, err = service.RegisterAutomationRunner(ctx, "ws-1", "user-1", AutomationRunnerRegisterMutation{RunnerID: "runner-long-label", Labels: []string{strings.Repeat("l", dsl.RunnerLabelKeyMaxLength+1)}})
	require.ErrorIs(t, err, ErrInvalidArgument)
	require.Contains(t, err.Error(), "labels exceed")

	leaseUntil := now.Add(time.Hour)
	_, err = store.CreateRun(ctx, automationstore.AutomationRun{ID: "run-1", WorkspaceID: "ws-1", Name: "Limits", Status: automationstore.AutomationRunStatusRunning, CreatedAt: now, UpdatedAt: now})
	require.NoError(t, err)
	require.NoError(t, store.CreateJobRuns(ctx, []automationstore.AutomationJobRun{{ID: "job-1", WorkspaceID: "ws-1", RunID: "run-1", JobID: "build", JobType: "github_actions_job", Status: automationstore.AutomationJobRunStatusRunning, ScheduledAt: now, CreatedAt: now, UpdatedAt: now}}))
	job, err := store.EnqueueJob(ctx, automationstore.AutomationJob{WorkspaceID: "ws-1", RunID: "run-1", JobRunID: "job-1", QueueName: "default", Status: automationstore.AutomationJobStatusLeased, LeaseOwner: automationRunnerLeaseOwner("runner-1", "session-1"), LeaseUntil: &leaseUntil, ScheduledAt: now, MaxAttempts: 3, IdempotencyKey: "run-1:build", ShardBucket: 0, CreatedAt: now, UpdatedAt: now})
	require.NoError(t, err)

	_, err = service.AppendAutomationRunnerJobLog(ctx, "ws-1", "user-1", AutomationRunnerJobMutation{RunnerID: "runner-1", SessionID: "session-1", QueueJobID: job.ID, LogContent: "1234"})
	require.ErrorIs(t, err, ErrInvalidArgument)
	require.Contains(t, err.Error(), "log chunk exceeds")

	_, err = service.AppendAutomationRunnerJobArtifact(ctx, "ws-1", "user-1", AutomationRunnerJobMutation{RunnerID: "runner-1", SessionID: "session-1", QueueJobID: job.ID, ArtifactName: "build", ArtifactSize: 11})
	require.ErrorIs(t, err, ErrInvalidArgument)
	require.Contains(t, err.Error(), "artifact exceeds")

	_, err = service.AppendAutomationRunnerJobArtifact(ctx, "ws-1", "user-1", AutomationRunnerJobMutation{RunnerID: "runner-1", SessionID: "session-1", QueueJobID: job.ID, ArtifactName: strings.Repeat("a", automationArtifactNameMaxLength+1), ArtifactSize: 1, ArtifactContentBase64: "AA==", ArtifactSHA256: strings.Repeat("0", 64)})
	require.ErrorIs(t, err, ErrInvalidArgument)
	require.Contains(t, err.Error(), "name exceeds")
}

func TestListAutomationRunsRepositoryFilterAndPagination(t *testing.T) {
	ctx := context.Background()
	db, err := openServiceTestSQLiteDB(t)
	require.NoError(t, err)
	migrateAutomationServiceTestSchema(t, db)
	store := automationstore.New(db)
	now := time.Date(2026, 6, 5, 12, 0, 0, 0, time.UTC)

	_, err = store.CreateRun(ctx, automationstore.AutomationRun{ID: "r1", WorkspaceID: "ws-1", Name: "A", Status: automationstore.AutomationRunStatusSucceeded, RepositoryFullName: "org/repo-a", CreatedAt: now, UpdatedAt: now})
	require.NoError(t, err)
	_, err = store.CreateRun(ctx, automationstore.AutomationRun{ID: "r2", WorkspaceID: "ws-1", Name: "B", Status: automationstore.AutomationRunStatusFailed, RepositoryFullName: "org/repo-b", CreatedAt: now.Add(time.Second), UpdatedAt: now.Add(time.Second)})
	require.NoError(t, err)
	_, err = store.CreateRun(ctx, automationstore.AutomationRun{ID: "r3", WorkspaceID: "ws-1", Name: "C", Status: automationstore.AutomationRunStatusRunning, RepositoryFullName: "org/repo-a", CreatedAt: now.Add(2 * time.Second), UpdatedAt: now.Add(2 * time.Second)})
	require.NoError(t, err)

	service := NewAutomationsService(store, func() time.Time { return now }, nil)

	t.Run("filters by repository full name", func(t *testing.T) {
		result, err := service.ListAutomationRuns(ctx, "ws-1", "user-1", "", "org/repo-a", "", 1, 10)
		require.NoError(t, err)
		require.Len(t, result.Runs, 2)
		require.Equal(t, "r3", result.Runs[0].ID)
		require.Equal(t, "r1", result.Runs[1].ID)
	})

	t.Run("filters by status", func(t *testing.T) {
		result, err := service.ListAutomationRuns(ctx, "ws-1", "user-1", "failed", "", "", 1, 10)
		require.NoError(t, err)
		require.Len(t, result.Runs, 1)
		require.Equal(t, "r2", result.Runs[0].ID)
	})

	t.Run("returns HasMore for pagination", func(t *testing.T) {
		result, err := service.ListAutomationRuns(ctx, "ws-1", "user-1", "", "", "", 1, 2)
		require.NoError(t, err)
		require.True(t, result.HasMore)
		require.Len(t, result.Runs, 2)
	})

	t.Run("empty repository returns no results", func(t *testing.T) {
		result, err := service.ListAutomationRuns(ctx, "ws-1", "user-1", "", "org/nonexistent", "", 1, 10)
		require.NoError(t, err)
		require.Empty(t, result.Runs)
		require.False(t, result.HasMore)
	})

	t.Run("filters by search query", func(t *testing.T) {
		result, err := service.ListAutomationRuns(ctx, "ws-1", "user-1", "", "", "repo-b", 1, 10)
		require.NoError(t, err)
		require.Len(t, result.Runs, 1)
		require.Equal(t, "r2", result.Runs[0].ID)
	})
}

type staticAutomationSourceResolver struct {
	content string
}

func (r staticAutomationSourceResolver) ResolveAutomationSource(context.Context, AutomationSourceRequest) (AutomationSourceSnapshot, error) {
	return AutomationSourceSnapshot{RepositoryOwner: "pingcap", RepositoryName: "agentteam", DefinitionPath: ".agentteam/build.yml", GitRef: "main", CommitSHA: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", Content: r.content}, nil
}

type countingAutomationSourceResolver struct {
	content string
	calls   int
}

func (r *countingAutomationSourceResolver) ResolveAutomationSource(context.Context, AutomationSourceRequest) (AutomationSourceSnapshot, error) {
	r.calls++
	return AutomationSourceSnapshot{RepositoryOwner: "pingcap", RepositoryName: "agentteam", DefinitionPath: ".agentteam/build.yml", GitRef: "main", CommitSHA: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", Content: r.content}, nil
}

type staticAutomationRepositoryCredentialChecker struct {
	available bool
	scope     string
}

func (c staticAutomationRepositoryCredentialChecker) CheckAutomationRepositoryCredential(context.Context, AutomationRepositoryCredentialCheckRequest) (AutomationRepositoryCredentialAvailability, error) {
	return AutomationRepositoryCredentialAvailability{
		RepoTokenAvailable: c.available,
		RepoTokenScope:     c.scope,
	}, nil
}

type recordingAutomationAuthorizer struct {
	AllowAutomationAuthorizer
	resumeWaitIDs []string
}

func (a *recordingAutomationAuthorizer) CanResumeWait(_ context.Context, request AutomationAuthorizationRequest) error {
	if waitID := strings.TrimSpace(request.Wait.ID); waitID != "" {
		a.resumeWaitIDs = append(a.resumeWaitIDs, waitID)
	}
	return nil
}

func allAutomationClaimShards() []int {
	shards := make([]int, automationstore.AutomationClaimShardCount)
	for i := range shards {
		shards[i] = i
	}
	return shards
}

func newAutomationRepositoryStoreForAutomationsTest(t *testing.T) *repos.Store {
	t.Helper()
	db, err := openServiceTestSQLiteDB(t, "repos")
	require.NoError(t, err)
	require.NoError(t, db.AutoMigrate(&repos.ResourceRepository{}, &repos.ResourceRepositoryLifecycleEvent{}))
	return repos.New(db)
}
