package agent

import (
	"context"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/core/secrets"
	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/store/storeerr"
	"github.com/tidbcloud/AgentTeam/internal/store/testfixture"
)

var testAgentCredentialStorageKey = []byte("0123456789abcdef0123456789abcdef")

func TestAgentPermissionModeHelpers(t *testing.T) {
	t.Parallel()

	require.Equal(t, AgentPermissionModeFullAccess, DefaultAgentPermissionMode(""))
	require.Equal(t, AgentPermissionModeFullAccess, DefaultAgentPermissionMode("  "))
	require.Equal(t, AgentPermissionModeFullAccess, DefaultAgentPermissionMode(AgentPermissionModeWorkspaceWrite))
	require.Equal(t, AgentPermissionModeFullAccess, DefaultAgentPermissionMode(AgentPermissionModeFullAccess))
	require.Equal(t, AgentPermissionModeFullAccess, DefaultAgentPermissionMode("restricted"))
	require.True(t, IsAgentPermissionMode(AgentPermissionModeWorkspaceWrite))
	require.True(t, IsAgentPermissionMode(AgentPermissionModeFullAccess))
	require.True(t, IsAgentPermissionMode("restricted"))
	normalized, ok := NormalizeAgentPermissionMode(AgentPermissionModeWorkspaceWrite)
	require.True(t, ok)
	require.Equal(t, AgentPermissionModeFullAccess, normalized)
}

func TestMarkMachinesDisconnectedOnStartupDoesNotPersistPresence(t *testing.T) {
	t.Parallel()

	ctx := context.Background()
	pool := startRuntimeTestPool(t, ctx)

	service := New(pool)

	now := time.Date(2026, 3, 13, 12, 0, 0, 0, time.UTC)
	workspaceID := "wsp_test"
	userID := "usr_test"
	humanMemberID := "mem_human"
	connectedAgentMemberID := "mem_connected_agent"
	offlineAgentMemberID := "mem_offline_agent"
	connectedMachineID := "mac_connected"
	offlineMachineID := "mac_offline"
	connectedAgentID := "agt_connected"
	offlineAgentID := "agt_offline"
	offlineDisconnectedAt := now.Add(-2 * time.Minute)

	mustExec(t, pool, `
INSERT INTO users (id, email, name, display_name, avatar_url, is_admin, password_hash, email_verified_at, created_at, updated_at)
VALUES ($1, 'owner@example.com', 'owner', 'Owner', '', TRUE, 'hash', $2, $2, $2)
`, userID, now)
	mustExec(t, pool, `
INSERT INTO workspaces (id, organization_id, slug, name, latest_message_seq, created_at, updated_at)
VALUES ($1, $2, 'test', 'test', 0, $3, $3)
`, workspaceID, testfixture.SeedWorkspaceOrganization(t, ctx, pool, workspaceID, now), now)
	mustExec(t, pool, `
INSERT INTO members (id, workspace_id, type, display_name, user_id, workspace_role, created_at, updated_at)
VALUES ($1, $2, 'human', 'Human', $3, 'owner', $4, $4)
`, humanMemberID, workspaceID, userID, now)
	mustExec(t, pool, `
INSERT INTO members (id, workspace_id, type, display_name, created_at, updated_at)
VALUES
($1, $3, 'agent', 'Connected Agent', $4, $4),
($2, $3, 'agent', 'Offline Agent', $4, $4)
`, connectedAgentMemberID, offlineAgentMemberID, workspaceID, now)
	mustExec(t, pool, `
INSERT INTO machines (
	id, workspace_id, created_by_user_id, name, api_key,
	connected_at, disconnected_at,
	created_at, updated_at
) VALUES (
	$1, $2, $3, 'Connected machine', 'api_key_connected',
	$4, NULL,
	$5, $5
)
`, connectedMachineID, workspaceID, userID, now.Add(-time.Minute), now)
	mustExec(t, pool, `
INSERT INTO machines (
	id, workspace_id, created_by_user_id, name, api_key,
	connected_at, disconnected_at,
	created_at, updated_at
) VALUES (
	$1, $2, $3, 'Offline machine', 'api_key_offline',
	$4, $5,
	$6, $6
)
`, offlineMachineID, workspaceID, userID, now.Add(-10*time.Minute), offlineDisconnectedAt, now)
	mustExec(t, pool, `
INSERT INTO agents (
	id, workspace_id, machine_id, member_id, name, runtime, model, description,
	status, activity, activity_detail, created_at, updated_at
) VALUES
($1, $3, $4, $6, 'connected-agent', 'codex', 'gpt-5.4', '', 'active', 'working', 'processing', $8, $8),
($2, $3, $5, $7, 'offline-agent', 'codex', 'gpt-5.4', '', 'sleeping', 'sleeping', 'already offline', $8, $8)
`, connectedAgentID, offlineAgentID, workspaceID, connectedMachineID, offlineMachineID, connectedAgentMemberID, offlineAgentMemberID, now)

	updated, err := service.MarkMachinesDisconnectedOnStartup(ctx)
	require.NoError(t, err)
	require.EqualValues(t, 0, updated)

	var disconnectedAt *time.Time
	require.NoError(t, pool.QueryRow(ctx, `SELECT disconnected_at FROM machines WHERE id = $1`, connectedMachineID).Scan(&disconnectedAt))
	require.Nil(t, disconnectedAt)

	var preservedDisconnectedAt time.Time
	require.NoError(t, pool.QueryRow(ctx, `SELECT disconnected_at FROM machines WHERE id = $1`, offlineMachineID).Scan(&preservedDisconnectedAt))
	require.True(t, preservedDisconnectedAt.Equal(offlineDisconnectedAt))

	var connectedStatus, connectedActivity, connectedDetail string
	require.NoError(t, pool.QueryRow(ctx, `SELECT status, activity, activity_detail FROM agents WHERE id = $1`, connectedAgentID).Scan(&connectedStatus, &connectedActivity, &connectedDetail))
	require.Equal(t, "active", connectedStatus)
	require.Equal(t, "working", connectedActivity)
	require.Equal(t, "processing", connectedDetail)

	var offlineStatus, offlineActivity, offlineDetail string
	require.NoError(t, pool.QueryRow(ctx, `SELECT status, activity, activity_detail FROM agents WHERE id = $1`, offlineAgentID).Scan(&offlineStatus, &offlineActivity, &offlineDetail))
	require.Equal(t, "sleeping", offlineStatus)
	require.Equal(t, "sleeping", offlineActivity)
	require.Equal(t, "already offline", offlineDetail)

	updated, err = service.MarkMachinesDisconnectedOnStartup(ctx)
	require.NoError(t, err)
	require.EqualValues(t, 0, updated)
}

func TestMarkMachinesDisconnectedOnStartupForWorkspacesDoesNotPersistPresence(t *testing.T) {
	t.Parallel()

	ctx := context.Background()
	pool := startRuntimeTestPool(t, ctx)
	service := New(pool)
	now := time.Date(2026, 6, 5, 8, 0, 0, 0, time.UTC)

	first := seedRuntimeAgentFixture(t, pool, now, "startup_scope_a")
	second := seedRuntimeAgentFixture(t, pool, now, "startup_scope_b")

	updated, err := service.MarkMachinesDisconnectedOnStartupForWorkspaces(ctx, []string{first.workspaceID})
	require.NoError(t, err)
	require.EqualValues(t, 0, updated)

	var firstDisconnectedAt *time.Time
	require.NoError(t, pool.QueryRow(ctx, `SELECT disconnected_at FROM machines WHERE id = $1`, first.machineID).Scan(&firstDisconnectedAt))
	require.Nil(t, firstDisconnectedAt)

	var secondDisconnectedAt *time.Time
	require.NoError(t, pool.QueryRow(ctx, `SELECT disconnected_at FROM machines WHERE id = $1`, second.machineID).Scan(&secondDisconnectedAt))
	require.Nil(t, secondDisconnectedAt)

	updated, err = service.MarkMachinesDisconnectedOnStartupForWorkspaces(ctx, nil)
	require.NoError(t, err)
	require.EqualValues(t, 0, updated)
}

func TestCreateModelProfileForUserCreatesOneProviderWithMultipleModels(t *testing.T) {
	t.Parallel()

	ctx := context.Background()
	pool := startRuntimeTestPool(t, ctx)
	service := NewWithOptions(pool, Options{CredentialStorageKey: testAgentCredentialStorageKey})
	now := time.Date(2026, 5, 22, 9, 0, 0, 0, time.UTC)
	userID := "usr_model_provider_multi"

	mustExec(t, pool, `
INSERT INTO users (id, email, name, display_name, avatar_url, is_admin, password_hash, email_verified_at, created_at, updated_at)
VALUES ($1, 'model-provider@example.com', 'model-provider', 'Model Provider', '', FALSE, 'hash', $2, $2, $2)
`, userID, now)

	created, err := service.CreateModelProfileForUser(ctx, userID, CreateModelProfileParams{
		DisplayName: "OpenAI compatible",
		Models:      []string{"gpt-5.4", "gpt-5.4", "gpt-5.4-mini"},
		BaseURL:     "https://api.example.com/v1",
		APIKey:      "sk-test",
		WireAPI:     "openai",
	})
	require.NoError(t, err)
	require.Len(t, created, 2)
	require.Equal(t, "OpenAI compatible", created[0].DisplayName)
	require.NotEmpty(t, created[0].ProviderID)
	require.Equal(t, created[0].ProviderID, created[1].ProviderID)
	require.ElementsMatch(t, []string{"gpt-5.4", "gpt-5.4-mini"}, []string{created[0].Model, created[1].Model})

	profiles, err := service.ListModelProfilesForUser(ctx, userID)
	require.NoError(t, err)
	require.Len(t, profiles, 2)
	require.Equal(t, created[0].ProviderID, profiles[0].ProviderID)
	require.True(t, profiles[0].APIKeyConfigured)

	loaded, err := service.GetModelProfileForUser(ctx, userID, created[0].ID)
	require.NoError(t, err)
	require.Equal(t, "https://api.example.com/v1", loaded.BaseURL)
	require.Equal(t, "openai", loaded.WireAPI)
	require.Equal(t, "sk-test", loaded.APIKey)

	updated, err := service.UpdateModelProfileForUser(ctx, userID, created[0].ID, UpdateModelProfileParams{
		DisplayName: "OpenAI compatible updated",
		Models:      []string{"gpt-5.5", "gpt-5.6"},
		BaseURL:     "https://api2.example.com/v1",
		WireAPI:     "openai",
	})
	require.NoError(t, err)
	require.Len(t, updated, 2)
	require.Equal(t, "OpenAI compatible updated", updated[0].DisplayName)
	require.Equal(t, created[0].ProviderID, updated[0].ProviderID)
	require.ElementsMatch(t, []string{"gpt-5.5", "gpt-5.6"}, []string{updated[0].Model, updated[1].Model})

	require.NoError(t, service.DeleteModelProfileForUser(ctx, userID, updated[0].ID))
	profiles, err = service.ListModelProfilesForUser(ctx, userID)
	require.NoError(t, err)
	require.Empty(t, profiles)
	_, err = service.GetModelProfileForUser(ctx, userID, created[1].ID)
	require.ErrorIs(t, err, ErrNotFound)
}

func TestMarkMachineDisconnectedDoesNotPersistPresence(t *testing.T) {
	t.Parallel()

	ctx := context.Background()
	pool := startRuntimeTestPool(t, ctx)
	service := New(pool)
	fixture := seedRuntimeAgentFixture(t, pool, time.Date(2026, 3, 24, 10, 0, 0, 0, time.UTC), "disconnect_presence")
	connectedAt := time.Date(2026, 3, 24, 10, 5, 0, 0, time.UTC)

	mustExec(t, pool, `UPDATE machines SET connected_at = $2, disconnected_at = NULL WHERE id = $1`, fixture.machineID, connectedAt)
	mustExec(t, pool, `UPDATE agents SET status = 'active', activity = 'thinking', activity_detail = 'investigating' WHERE id = $1`, fixture.agentID)

	require.NoError(t, service.MarkMachineDisconnected(ctx, fixture.machineID))

	var storedConnectedAt *time.Time
	var disconnectedAt *time.Time
	require.NoError(t, pool.QueryRow(ctx, `SELECT connected_at, disconnected_at FROM machines WHERE id = $1`, fixture.machineID).Scan(&storedConnectedAt, &disconnectedAt))
	require.NotNil(t, storedConnectedAt)
	require.True(t, storedConnectedAt.Equal(connectedAt))
	require.Nil(t, disconnectedAt)

	var status, activity, detail string
	require.NoError(t, pool.QueryRow(ctx, `SELECT status, activity, activity_detail FROM agents WHERE id = $1`, fixture.agentID).Scan(&status, &activity, &detail))
	require.Equal(t, "active", status)
	require.Equal(t, "thinking", activity)
	require.Equal(t, "investigating", detail)
}

func TestAgentTelemetryValidatesOwnershipWithoutPersistence(t *testing.T) {
	t.Parallel()

	ctx := context.Background()
	pool := startRuntimeTestPool(t, ctx)
	service := New(pool)
	fixture := seedRuntimeAgentFixture(t, pool, time.Date(2026, 4, 1, 9, 0, 0, 0, time.UTC), "telemetry_no_persist")

	updated, err := service.UpdateAgentSessionFromMachine(ctx, fixture.machineID, fixture.agentID, "sess-1", false)
	require.NoError(t, err)
	require.True(t, updated)
	updated, err = service.UpdateAgentStatusFromMachine(ctx, fixture.machineID, fixture.agentID, "active")
	require.NoError(t, err)
	require.True(t, updated)
	updated, err = service.UpdateAgentActivityFromMachine(ctx, fixture.machineID, fixture.agentID, "thinking", "Planning")
	require.NoError(t, err)
	require.True(t, updated)
	updated, err = service.UpdateAgentSessionFromMachine(ctx, "mac_other", fixture.agentID, "sess-other", false)
	require.NoError(t, err)
	require.False(t, updated)

	var status, activity, activityDetail, sessionID, currentInstanceID string
	require.NoError(t, pool.QueryRow(ctx, `
SELECT status, activity, activity_detail, session_id, COALESCE(current_instance_id, '')
FROM agents
WHERE id = $1
`, fixture.agentID).Scan(&status, &activity, &activityDetail, &sessionID, &currentInstanceID))
	require.Equal(t, "inactive", status)
	require.Equal(t, "offline", activity)
	require.Empty(t, activityDetail)
	require.Empty(t, sessionID)
	require.Empty(t, currentInstanceID)

	var instanceCount int
	require.NoError(t, pool.QueryRow(ctx, `SELECT COUNT(*) FROM agent_instances WHERE agent_id = $1`, fixture.agentID).Scan(&instanceCount))
	require.Zero(t, instanceCount)
}

func TestMarkMachineReadyPersistsOnlyChangedMachineMetadata(t *testing.T) {
	t.Parallel()

	ctx := context.Background()
	pool := startRuntimeTestPool(t, ctx)
	service := New(pool)
	fixture := seedRuntimeAgentFixture(t, pool, time.Date(2026, 4, 1, 10, 0, 0, 0, time.UTC), "ready_metadata")
	connectedAt := time.Date(2026, 4, 1, 10, 5, 0, 0, time.UTC)

	mustExec(t, pool, `UPDATE machines SET connected_at = $2, disconnected_at = NULL WHERE id = $1`, fixture.machineID, connectedAt)

	require.NoError(t, service.MarkMachineReady(ctx, MachineIdentity{
		MachineID:   fixture.machineID,
		WorkspaceID: fixture.workspaceID,
	}, ReadyPayload{
		Hostname:      "runner",
		OS:            "linux amd64",
		DaemonVersion: "agentteam/1.0.0",
		Runtimes:      []string{"codex"},
		RuntimeDefaults: map[string]RuntimeDefaults{
			"codex": {Model: "gpt-5.4", ReasoningEffort: "high"},
		},
		RunningAgents: []string{fixture.agentID},
	}))

	var hostname, osValue, daemonVersion string
	var storedConnectedAt *time.Time
	var disconnectedAt *time.Time
	require.NoError(t, pool.QueryRow(ctx, `
SELECT hostname, os, daemon_version, connected_at, disconnected_at
FROM machines
WHERE id = $1
`, fixture.machineID).Scan(&hostname, &osValue, &daemonVersion, &storedConnectedAt, &disconnectedAt))
	require.Equal(t, "runner", hostname)
	require.Equal(t, "linux amd64", osValue)
	require.Equal(t, "agentteam/1.0.0", daemonVersion)
	require.NotNil(t, storedConnectedAt)
	require.True(t, storedConnectedAt.Equal(connectedAt))
	require.Nil(t, disconnectedAt)

	var runtimeCapabilityCount int
	require.NoError(t, pool.QueryRow(ctx, `SELECT COUNT(*) FROM machine_runtime_capabilities WHERE machine_id = $1`, fixture.machineID).Scan(&runtimeCapabilityCount))
	require.Zero(t, runtimeCapabilityCount)

	var status, activity, currentInstanceID string
	require.NoError(t, pool.QueryRow(ctx, `
SELECT status, activity, COALESCE(current_instance_id, '')
FROM agents
WHERE id = $1
`, fixture.agentID).Scan(&status, &activity, &currentInstanceID))
	require.Equal(t, "inactive", status)
	require.Equal(t, "offline", activity)
	require.Empty(t, currentInstanceID)
}

func TestListAgentsForMachineReturnsMachineAgentsSortedByName(t *testing.T) {
	t.Parallel()

	ctx := context.Background()
	pool := startRuntimeTestPool(t, ctx)

	service := NewWithOptions(pool, Options{CredentialStorageKey: testAgentCredentialStorageKey})
	fixture := seedRuntimeAgentFixture(t, pool, time.Date(2026, 5, 3, 13, 0, 0, 0, time.UTC), "list_agents")
	llmColumns, err := EncodeStoredAgentLLMColumns(testAgentCredentialStorageKey, &AgentLLM{
		Mode:   AgentLLMModeAgentTeamProxy,
		APIKey: "lpi_test",
	})
	require.NoError(t, err)

	mustExec(t, pool, `
INSERT INTO user_preferences (user_id, runtime_debug_enabled, created_at, updated_at)
VALUES ($1, TRUE, NOW(), NOW())
ON DUPLICATE KEY UPDATE
  runtime_debug_enabled = VALUES(runtime_debug_enabled),
  updated_at = VALUES(updated_at)
`, fixture.userID)
	mustExec(t, pool, `
UPDATE members
SET display_name = 'Zeta Agent'
WHERE id = $1
`, "mem_list_agents")
	mustExec(t, pool, `
UPDATE agents
SET name = 'zeta-agent',
    description = 'Zeta description',
    runtime = 'codex',
    model = 'gpt-5.4',
    reasoning_effort = 'medium',
    llm_mode = $2,
    llm_base_url = $3,
    llm_api_key_ciphertext = $4,
    llm_headers_json = CAST(NULLIF($5, '') AS JSON),
    llm_wire_api = $6,
    status = 'active',
    activity = 'working'
WHERE id = $1
`, fixture.agentID, llmColumns.Mode, llmColumns.BaseURL, llmColumns.APIKeyCiphertext, llmColumns.HeadersJSON, llmColumns.WireAPI)
	mustExec(t, pool, `
INSERT INTO members (id, workspace_id, type, display_name, created_at, updated_at)
VALUES ($1, $2, 'agent', 'Alpha Agent', $3, $3)
`, "mem_list_agents_alpha", fixture.workspaceID, time.Date(2026, 5, 3, 13, 1, 0, 0, time.UTC))
	mustExec(t, pool, `
INSERT INTO agents (
	id, workspace_id, machine_id, member_id, name, runtime, model, reasoning_effort, description,
	status, activity, activity_detail, created_at, updated_at
) VALUES (
	$1, $2, $3, $4, 'alpha-agent', 'claude', 'claude-sonnet-4.5', 'high', 'Alpha description',
	'inactive', 'offline', '', $5, $5
)
`, "agt_list_agents_alpha", fixture.workspaceID, fixture.machineID, "mem_list_agents_alpha", time.Date(2026, 5, 3, 13, 1, 0, 0, time.UTC))
	mustExec(t, pool, `
INSERT INTO members (id, workspace_id, type, display_name, created_at, updated_at)
VALUES ($1, $2, 'agent', 'Archived Agent', $3, $3)
`, "mem_list_agents_archived", fixture.workspaceID, time.Date(2026, 5, 3, 13, 2, 0, 0, time.UTC))
	mustExec(t, pool, `
INSERT INTO agents (
	id, workspace_id, machine_id, member_id, name, runtime, model, reasoning_effort, description,
	status, activity, activity_detail, created_at, updated_at
) VALUES (
	$1, $2, $3, $4, 'archived-agent', 'codex', 'gpt-5.4', '', '',
	'archived', 'offline', '', $5, $5
)
`, "agt_list_agents_archived", fixture.workspaceID, fixture.machineID, "mem_list_agents_archived", time.Date(2026, 5, 3, 13, 2, 0, 0, time.UTC))

	agents, err := service.ListAgentsForMachine(ctx, fixture.machineID)
	require.NoError(t, err)
	require.Len(t, agents, 2)

	require.Equal(t, "agt_list_agents_alpha", agents[0].ID)
	require.Equal(t, "alpha-agent", agents[0].Name)
	require.Equal(t, "Alpha Agent", agents[0].DisplayName)
	require.Equal(t, "Alpha description", agents[0].Description)
	require.Equal(t, "claude", agents[0].Runtime)
	require.Equal(t, "claude", agents[0].RuntimePolicy.Runtime)
	require.Equal(t, "claude", agents[0].RuntimePolicy.RuntimeType)
	require.Equal(t, AgentPermissionModeFullAccess, agents[0].RuntimePolicy.PermissionMode)
	require.Equal(t, int64(1), agents[0].RuntimePolicy.Generation)
	require.NotEmpty(t, agents[0].RuntimePolicy.Fingerprint)
	require.Equal(t, "claude-sonnet-4.5", agents[0].Model)
	require.Equal(t, "high", agents[0].ReasoningEffort)
	require.Equal(t, AgentLLMModeLocal, agents[0].LLMMode)
	require.Empty(t, agents[0].APIKey)
	require.True(t, agents[0].RuntimeDebugEnabled)
	require.Equal(t, "inactive", agents[0].Status)
	require.Equal(t, "offline", agents[0].Activity)

	require.Equal(t, fixture.agentID, agents[1].ID)
	require.Equal(t, "zeta-agent", agents[1].Name)
	require.Equal(t, "Zeta Agent", agents[1].DisplayName)
	require.Equal(t, "codex", agents[1].RuntimePolicy.Runtime)
	require.Equal(t, AgentPermissionModeFullAccess, agents[1].RuntimePolicy.PermissionMode)
	require.Equal(t, AgentLLMModeAgentTeamProxy, agents[1].LLMMode)
	require.Equal(t, "lpi_test", agents[1].APIKey)
	require.True(t, agents[1].RuntimeDebugEnabled)
}

func TestAgentNamesForMachineReturnsPublicNamesForOwnedAgent(t *testing.T) {
	t.Parallel()

	ctx := context.Background()
	pool := startRuntimeTestPool(t, ctx)

	service := New(pool)
	fixture := seedRuntimeAgentFixture(t, pool, time.Date(2026, 5, 3, 13, 30, 0, 0, time.UTC), "machine_agent_identity")
	mustExec(t, pool, `
UPDATE agents
SET name = 'technical-name'
WHERE id = $1
`, fixture.agentID)
	mustExec(t, pool, `
UPDATE members
SET display_name = 'Public Display Name'
WHERE id = $1
`, "mem_machine_agent_identity")

	name, displayName, found, err := service.AgentNamesForMachine(ctx, fixture.machineID, fixture.agentID)
	require.NoError(t, err)
	require.True(t, found)
	require.Equal(t, "technical-name", name)
	require.Equal(t, "Public Display Name", displayName)

	_, _, found, err = service.AgentNamesForMachine(ctx, "mac_other", fixture.agentID)
	require.NoError(t, err)
	require.False(t, found)
}

func TestAgentRuntimeForStartReturnsTrimmedRuntimeAndNotFound(t *testing.T) {
	t.Parallel()

	ctx := context.Background()
	pool := startRuntimeTestPool(t, ctx)

	service := New(pool)
	fixture := seedRuntimeAgentFixture(t, pool, time.Date(2026, 5, 3, 14, 0, 0, 0, time.UTC), "runtime_start")
	mustExec(t, pool, `
UPDATE agents
SET runtime = '  codex-custom  '
WHERE id = $1
`, fixture.agentID)

	runtimeValue, err := service.AgentRuntimeForStart(ctx, "  "+fixture.agentID+"  ")
	require.NoError(t, err)
	require.Equal(t, "codex-custom", runtimeValue)

	_, err = service.AgentRuntimeForStart(ctx, "agt_runtime_start_missing")
	require.ErrorIs(t, err, ErrNotFound)
}

func TestAgentSessionTelemetryPersistsLegacyMirrorOnlyWhenRequested(t *testing.T) {
	t.Parallel()

	ctx := context.Background()
	pool := startRuntimeTestPool(t, ctx)
	service := New(pool)
	fixture := seedRuntimeAgentFixture(t, pool, time.Date(2026, 4, 1, 9, 0, 0, 0, time.UTC), "legacy_session_mirror")

	updated, err := service.UpdateAgentSessionFromMachine(ctx, fixture.machineID, fixture.agentID, "sess-legacy", true)
	require.NoError(t, err)
	require.True(t, updated)

	var sessionID string
	require.NoError(t, pool.QueryRow(ctx, `SELECT session_id FROM agents WHERE id = $1`, fixture.agentID).Scan(&sessionID))
	require.Equal(t, "sess-legacy", sessionID)

	updated, err = service.UpdateAgentSessionFromMachine(ctx, "mac_other", fixture.agentID, "sess-other", true)
	require.NoError(t, err)
	require.False(t, updated)

	require.NoError(t, pool.QueryRow(ctx, `SELECT session_id FROM agents WHERE id = $1`, fixture.agentID).Scan(&sessionID))
	require.Equal(t, "sess-legacy", sessionID)

	var instanceCount int
	require.NoError(t, pool.QueryRow(ctx, `SELECT COUNT(*) FROM agent_instances WHERE agent_id = $1`, fixture.agentID).Scan(&instanceCount))
	require.Zero(t, instanceCount)
}

func TestRuntimeAuthAndTelemetryRejectRevokedOrArchivedResources(t *testing.T) {
	t.Parallel()

	ctx := context.Background()
	pool := startRuntimeTestPool(t, ctx)
	service := New(pool)
	fixture := seedRuntimeAgentFixture(t, pool, time.Date(2026, 5, 10, 9, 0, 0, 0, time.UTC), "revoked_runtime")

	machineIdentity, err := service.AuthenticateMachine(ctx, "revoked_runtime-key")
	require.NoError(t, err)
	require.Equal(t, fixture.machineID, machineIdentity.MachineID)

	mustExec(t, pool, `UPDATE machines SET status = 'revoked' WHERE id = $1`, fixture.machineID)
	_, err = service.AuthenticateMachine(ctx, "revoked_runtime-key")
	require.ErrorIs(t, err, ErrUnauthorized)
	err = service.MarkMachineReady(ctx, machineIdentity, ReadyPayload{Hostname: "host"})
	require.ErrorIs(t, err, ErrUnauthorized)

	updated, err := service.UpdateAgentStatusFromMachine(ctx, fixture.machineID, fixture.agentID, "active")
	require.NoError(t, err)
	require.False(t, updated)
	updated, err = service.UpdateAgentActivityFromMachine(ctx, fixture.machineID, fixture.agentID, "thinking", "stale")
	require.NoError(t, err)
	require.False(t, updated)
	updated, err = service.UpdateAgentSessionFromMachine(ctx, fixture.machineID, fixture.agentID, "stale-session", false)
	require.NoError(t, err)
	require.False(t, updated)

	var status string
	var activity string
	var activityDetail string
	var sessionID string
	require.NoError(t, pool.QueryRow(ctx, `
SELECT status, activity, activity_detail, session_id
FROM agents
WHERE id = $1
`, fixture.agentID).Scan(&status, &activity, &activityDetail, &sessionID))
	require.Equal(t, "inactive", status)
	require.Equal(t, "offline", activity)
	require.Empty(t, activityDetail)
	require.Empty(t, sessionID)

	mustExec(t, pool, `UPDATE machines SET status = 'active' WHERE id = $1`, fixture.machineID)
	mustExec(t, pool, `UPDATE agents SET status = 'archived' WHERE id = $1`, fixture.agentID)
	updated, err = service.UpdateAgentStatusFromMachine(ctx, fixture.machineID, fixture.agentID, "active")
	require.NoError(t, err)
	require.False(t, updated)

	require.NoError(t, pool.QueryRow(ctx, `SELECT status FROM agents WHERE id = $1`, fixture.agentID).Scan(&status))
	require.Equal(t, "archived", status)
	_, err = service.PrepareAgentStart(ctx, fixture.agentID, true, true)
	require.ErrorIs(t, err, ErrNotFound)
}

func TestAuthenticateAgentAndPrepareAgentStartRotateToken(t *testing.T) {
	t.Parallel()

	ctx := context.Background()
	pool := startRuntimeTestPool(t, ctx)

	service := New(pool)

	now := time.Date(2026, 3, 19, 8, 0, 0, 0, time.UTC)
	workspaceID := "wsp_test"
	userID := "usr_test"
	agentMemberID := "mem_agent"
	machineID := "mac_test"
	agentID := "agt_test"
	agentToken := "agent-token"
	machineAPIKey := "machine-key"

	mustExec(t, pool, `
INSERT INTO users (id, email, name, display_name, avatar_url, is_admin, password_hash, email_verified_at, created_at, updated_at)
VALUES ($1, 'owner@example.com', 'owner', 'Owner', '', TRUE, 'hash', $2, $2, $2)
`, userID, now)
	mustExec(t, pool, `
INSERT INTO workspaces (id, organization_id, slug, name, latest_message_seq, created_at, updated_at)
VALUES ($1, $2, 'test', 'test', 0, $3, $3)
`, workspaceID, testfixture.SeedWorkspaceOrganization(t, ctx, pool, workspaceID, now), now)
	mustExec(t, pool, `
INSERT INTO members (id, workspace_id, type, display_name, created_at, updated_at)
VALUES ($1, $2, 'agent', 'Review App', $3, $3)
`, agentMemberID, workspaceID, now)
	mustExec(t, pool, `
INSERT INTO machines (id, workspace_id, created_by_user_id, name, api_key, created_at, updated_at)
VALUES ($1, $2, $3, 'Test machine', $4, $5, $5)
`, machineID, workspaceID, userID, machineAPIKey, now)
	mustExec(t, pool, `
INSERT INTO agents (
	id, workspace_id, machine_id, member_id, name, runtime, model, reasoning_effort, description,
	auth_token_hash, auto_wake_enabled, status, activity, activity_detail, created_at, updated_at
) VALUES (
	$1, $2, $3, $4, 'review-app', 'codex', 'gpt-5-codex', 'high', 'Agent description',
	$5, TRUE, 'inactive', 'offline', '', $6, $6
)
`, agentID, workspaceID, machineID, agentMemberID, secrets.HashToken(agentToken), now)

	machineIdentity, err := service.AuthenticateMachine(ctx, machineAPIKey)
	require.NoError(t, err)
	require.Equal(t, machineID, machineIdentity.MachineID)
	require.Equal(t, workspaceID, machineIdentity.WorkspaceID)
	require.Equal(t, userID, machineIdentity.CreatedByUserID)

	agent, err := service.AuthenticateAgent(ctx, agentID, agentToken)
	require.NoError(t, err)
	require.Equal(t, agentID, agent.AgentID)
	require.Equal(t, workspaceID, agent.WorkspaceID)
	require.Equal(t, agentMemberID, agent.MemberID)
	require.Equal(t, machineID, agent.MachineID)
	require.Empty(t, agent.DaemonVersion)

	_, err = service.AuthenticateAgent(ctx, agentID, "wrong-token")
	require.ErrorIs(t, err, ErrUnauthorized)

	mustExec(t, pool, `UPDATE agents SET permission_mode = $2 WHERE id = $1`, agentID, AgentPermissionModeWorkspaceWrite)

	prepared, err := service.PrepareAgentStart(ctx, agentID, false, true)
	require.NoError(t, err)
	require.Equal(t, agentID, prepared.AgentID)
	require.Equal(t, workspaceID, prepared.WorkspaceID)
	require.Equal(t, agentMemberID, prepared.MemberID)
	require.Equal(t, "Review App", prepared.DisplayName)
	require.Equal(t, "Agent description", prepared.Description)
	require.Equal(t, "codex", prepared.Runtime)
	require.Equal(t, int64(1), prepared.RuntimePolicy.Generation)
	require.Equal(t, "codex", prepared.RuntimePolicy.Runtime)
	require.Equal(t, "codex", prepared.RuntimePolicy.RuntimeType)
	require.Equal(t, AgentPermissionModeFullAccess, prepared.PermissionMode)
	require.Equal(t, AgentPermissionModeFullAccess, prepared.RuntimePolicy.PermissionMode)
	require.NotEmpty(t, prepared.RuntimePolicy.Fingerprint)
	require.Equal(t, "gpt-5-codex", prepared.Model)
	require.Equal(t, "high", prepared.ReasoningEffort)
	require.Equal(t, AgentLLMModeLocal, prepared.LLMMode)
	require.Equal(t, machineID, prepared.MachineID)
	require.NotEmpty(t, prepared.AuthToken)

	var storedHash string
	require.NoError(t, pool.QueryRow(ctx, `SELECT auth_token_hash FROM agents WHERE id = $1`, agentID).Scan(&storedHash))
	require.True(t, secrets.TokenMatchesHash(prepared.AuthToken, storedHash))
	require.False(t, secrets.TokenMatchesHash(agentToken, storedHash))

	_, err = service.AuthenticateAgent(ctx, agentID, agentToken)
	require.ErrorIs(t, err, ErrUnauthorized)

	rotatedAgent, err := service.AuthenticateAgent(ctx, agentID, prepared.AuthToken)
	require.NoError(t, err)
	require.Equal(t, agentMemberID, rotatedAgent.MemberID)
}

func TestPrepareAgentStartOmitsLegacySessionWhenDisabled(t *testing.T) {
	t.Parallel()

	ctx := context.Background()
	pool := startRuntimeTestPool(t, ctx)
	service := New(pool)
	fixture := seedRuntimeAgentFixture(t, pool, time.Date(2026, 6, 10, 8, 0, 0, 0, time.UTC), "prepare_legacy_session")

	mustExec(t, pool, `UPDATE agents SET session_id = $2 WHERE id = $1`, fixture.agentID, "legacy-session")

	prepared, err := service.PrepareAgentStart(ctx, fixture.agentID, true, false)
	require.NoError(t, err)
	require.Empty(t, prepared.SessionID)

	prepared, err = service.PrepareAgentStart(ctx, fixture.agentID, true, true)
	require.NoError(t, err)
	require.Equal(t, "legacy-session", prepared.SessionID)
}

func TestPrepareAgentStartDoesNotUseMachineRuntimeDefaultsForLocalLLM(t *testing.T) {
	t.Parallel()

	ctx := context.Background()
	pool := startRuntimeTestPool(t, ctx)
	service := New(pool)
	fixture := seedRuntimeAgentFixture(t, pool, time.Date(2026, 6, 8, 9, 0, 0, 0, time.UTC), "local_runtime_defaults")

	mustExec(t, pool, `
UPDATE agents
SET runtime = 'opencode',
    model = '',
    reasoning_effort = '',
    llm_mode = $2
WHERE id = $1
`, fixture.agentID, AgentLLMModeLocal)
	mustExec(t, pool, `
INSERT INTO machine_runtime_capabilities (machine_id, runtime, default_model, default_reasoning_effort)
VALUES ($1, 'opencode', 'openai/gpt-5.2-codex', 'high')
`, fixture.machineID)

	prepared, err := service.PrepareAgentStart(ctx, fixture.agentID, true, true)
	require.NoError(t, err)
	require.Equal(t, "opencode", prepared.Runtime)
	require.Equal(t, AgentLLMModeLocal, prepared.LLMMode)
	require.Empty(t, prepared.Model)
	require.Empty(t, prepared.ReasoningEffort)

	mustExec(t, pool, `
UPDATE agents
SET model = 'gpt-5.2-codex',
    reasoning_effort = 'medium'
WHERE id = $1
`, fixture.agentID)

	prepared, err = service.PrepareAgentStart(ctx, fixture.agentID, true, true)
	require.NoError(t, err)
	require.Equal(t, "gpt-5.2-codex", prepared.Model)
	require.Equal(t, "medium", prepared.ReasoningEffort)
}

func TestIssueAgentAuthTokenForMachineRotatesOwnedAgentToken(t *testing.T) {
	t.Parallel()

	ctx := context.Background()
	pool := startRuntimeTestPool(t, ctx)
	service := New(pool)
	fixture := seedRuntimeAgentFixture(t, pool, time.Date(2026, 5, 25, 9, 0, 0, 0, time.UTC), "issue_token")

	oldToken := "old-agent-token"
	mustExec(t, pool, `
UPDATE agents
SET auth_token_hash = $2
WHERE id = $1
`, fixture.agentID, secrets.HashToken(oldToken))

	issuedToken, err := service.IssueAgentAuthTokenForMachine(ctx, fixture.machineID, fixture.agentID, false)
	require.NoError(t, err)
	require.NotEmpty(t, issuedToken)
	require.NotEqual(t, oldToken, issuedToken)

	_, err = service.AuthenticateAgent(ctx, fixture.agentID, oldToken)
	require.ErrorIs(t, err, ErrUnauthorized)

	authenticated, err := service.AuthenticateAgent(ctx, fixture.agentID, issuedToken)
	require.NoError(t, err)
	require.Equal(t, fixture.agentID, authenticated.AgentID)
	require.Equal(t, fixture.machineID, authenticated.MachineID)
}

func TestIssueAgentAuthTokenForMachineDefaultsLegacyWorkspaceWriteToFullAccess(t *testing.T) {
	t.Parallel()

	ctx := context.Background()
	pool := startRuntimeTestPool(t, ctx)
	service := New(pool)
	fixture := seedRuntimeAgentFixture(t, pool, time.Date(2026, 5, 25, 9, 2, 0, 0, time.UTC), "issue_workspace_token")

	oldToken := "old-workspace-agent-token"
	oldTokenHash := secrets.HashToken(oldToken)
	mustExec(t, pool, `
UPDATE agents
SET permission_mode = $2,
    auth_token_hash = $3
WHERE id = $1
`, fixture.agentID, AgentPermissionModeWorkspaceWrite, oldTokenHash)

	token, err := service.IssueAgentAuthTokenForMachine(ctx, fixture.machineID, fixture.agentID, false)
	require.NoError(t, err)
	require.NotEmpty(t, token)

	var refreshedHash string
	require.NoError(t, pool.QueryRow(ctx, `SELECT auth_token_hash FROM agents WHERE id = $1`, fixture.agentID).Scan(&refreshedHash))
	require.NotEqual(t, oldTokenHash, refreshedHash)
	require.True(t, secrets.TokenMatchesHash(token, refreshedHash))
}

func TestIssueAgentAuthTokenForMachineRejectsWrongMachine(t *testing.T) {
	t.Parallel()

	ctx := context.Background()
	pool := startRuntimeTestPool(t, ctx)
	service := New(pool)
	fixture := seedRuntimeAgentFixture(t, pool, time.Date(2026, 5, 25, 9, 5, 0, 0, time.UTC), "issue_wrong_machine")

	_, err := service.IssueAgentAuthTokenForMachine(ctx, "mac_other_machine", fixture.agentID, false)
	require.ErrorIs(t, err, ErrUnauthorized)

	_, err = service.IssueAgentAuthTokenForMachine(ctx, fixture.machineID, "agt_missing_issue_token", false)
	require.ErrorIs(t, err, ErrUnauthorized)
}

func TestLoadMaaSLLMProxyScopeProjectsDurableAuthority(t *testing.T) {
	t.Parallel()

	ctx := context.Background()
	pool := startRuntimeTestPool(t, ctx)
	service := New(pool)
	fixture := seedRuntimeAgentFixture(t, pool, time.Date(2026, 6, 1, 10, 1, 0, 0, time.UTC), "maas_llm_scope")

	scope, err := service.LoadMaaSLLMProxyScope(ctx, AuthenticatedAgent{
		AgentID:     fixture.agentID,
		WorkspaceID: fixture.workspaceID,
		MemberID:    fixture.memberID,
		MachineID:   fixture.machineID,
	})
	require.NoError(t, err)
	require.NotEmpty(t, scope.OrganizationID)
	require.Equal(t, fixture.workspaceID, scope.WorkspaceID)
	require.Equal(t, fixture.agentID, scope.AgentID)
	require.Equal(t, fixture.memberID, scope.MemberID)
	require.Equal(t, fixture.machineID, scope.MachineID)
	require.Empty(t, scope.RuntimeSessionID)
	require.Equal(t, int64(1), scope.AuthorityGeneration)
	require.Equal(t, "active", scope.OrganizationStatus)
	require.Equal(t, "active", scope.WorkspaceStatus)
	require.Equal(t, "active", scope.AgentStatus)
	require.Equal(t, "active", scope.RuntimeStatus)
}

func TestMarkMachineReadyDoesNotPersistRuntimeCapabilities(t *testing.T) {
	t.Parallel()

	ctx := context.Background()
	pool := startRuntimeTestPool(t, ctx)

	service := New(pool)

	now := time.Date(2026, 4, 5, 14, 0, 0, 0, time.UTC)
	const (
		userID      = "usr_runtime_capabilities"
		workspaceID = "wsp_runtime_capabilities"
		machineID   = "mac_runtime_capabilities"
	)

	mustExec(t, pool, `
INSERT INTO users (id, email, name, display_name, avatar_url, is_admin, password_hash, email_verified_at, created_at, updated_at)
VALUES ($1, 'owner@example.com', 'owner', 'Owner', '', TRUE, 'hash', $2, $2, $2)
`, userID, now)
	mustExec(t, pool, `
INSERT INTO workspaces (id, organization_id, slug, name, latest_message_seq, created_at, updated_at)
VALUES ($1, $2, 'runtime-capabilities', 'runtime-capabilities', 0, $3, $3)
`, workspaceID, testfixture.SeedWorkspaceOrganization(t, ctx, pool, workspaceID, now), now)
	mustExec(t, pool, `
INSERT INTO machines (id, workspace_id, created_by_user_id, name, api_key, created_at, updated_at)
VALUES ($1, $2, $3, 'Runtime machine', 'machine-key', $4, $4)
`, machineID, workspaceID, userID, now)

	err := service.MarkMachineReady(ctx, MachineIdentity{MachineID: machineID, WorkspaceID: workspaceID}, ReadyPayload{
		Runtimes:      []string{"claude", "codex"},
		RunningAgents: []string{},
		Hostname:      "host-1",
		OS:            "linux",
		DaemonVersion: "1.2.3",
	})
	require.NoError(t, err)

	var count int
	require.NoError(t, pool.QueryRow(ctx, `
SELECT COUNT(*)
FROM machine_runtime_capabilities
WHERE machine_id = $1
`, machineID).Scan(&count))
	require.Zero(t, count)
}

func TestMarkMachineReadyStoresDaemonVersionForAgentAuth(t *testing.T) {
	t.Parallel()

	ctx := context.Background()
	pool := startRuntimeTestPool(t, ctx)

	service := New(pool)

	now := time.Date(2026, 5, 15, 1, 0, 0, 0, time.UTC)
	workspaceID := "wsp_daemon_version"
	userID := "usr_daemon_version"
	agentMemberID := "mem_daemon_version_agent"
	machineID := "mac_daemon_version"
	agentID := "agt_daemon_version"
	agentToken := "agent-token-daemon-version"

	mustExec(t, pool, `
INSERT INTO users (id, email, name, display_name, avatar_url, is_admin, password_hash, email_verified_at, created_at, updated_at)
VALUES ($1, 'daemon-version@example.com', 'daemon-version', 'Daemon Version', '', TRUE, 'hash', $2, $2, $2)
`, userID, now)
	mustExec(t, pool, `
INSERT INTO workspaces (id, organization_id, slug, name, latest_message_seq, created_at, updated_at)
VALUES ($1, $2, 'daemon-version', 'daemon-version', 0, $3, $3)
`, workspaceID, testfixture.SeedWorkspaceOrganization(t, ctx, pool, workspaceID, now), now)
	mustExec(t, pool, `
INSERT INTO members (id, workspace_id, type, display_name, created_at, updated_at)
VALUES ($1, $2, 'agent', 'Version App', $3, $3)
`, agentMemberID, workspaceID, now)
	mustExec(t, pool, `
INSERT INTO machines (id, workspace_id, created_by_user_id, name, api_key, created_at, updated_at)
VALUES ($1, $2, $3, 'Version machine', 'machine-key-version', $4, $4)
`, machineID, workspaceID, userID, now)
	mustExec(t, pool, `
INSERT INTO agents (
	id, workspace_id, machine_id, member_id, name, runtime, model, description,
	auth_token_hash, auto_wake_enabled, status, activity, activity_detail, created_at, updated_at
) VALUES (
	$1, $2, $3, $4, 'version-app', 'codex', 'gpt-5-codex', '',
	$5, TRUE, 'inactive', 'offline', '', $6, $6
)
`, agentID, workspaceID, machineID, agentMemberID, secrets.HashToken(agentToken), now)

	require.NoError(t, service.MarkMachineReady(ctx, MachineIdentity{MachineID: machineID, WorkspaceID: workspaceID}, ReadyPayload{
		Runtimes:      []string{"codex"},
		RunningAgents: []string{},
		Hostname:      "host-protocol",
		OS:            "linux",
		DaemonVersion: "agentteam/v1.0.2-rc.11",
	}))

	authenticated, err := service.AuthenticateAgent(ctx, agentID, agentToken)
	require.NoError(t, err)
	require.Equal(t, machineID, authenticated.MachineID)
	require.Equal(t, "agentteam/v1.0.2-rc.11", authenticated.DaemonVersion)
}

func TestCreateMachineConnectTokenAllowsMemberOwnedMachine(t *testing.T) {
	t.Parallel()

	ctx := context.Background()
	pool := startRuntimeTestPool(t, ctx)
	service := New(pool)

	now := time.Date(2026, 5, 3, 12, 0, 0, 0, time.UTC)
	const (
		workspaceID   = "wsp_connect_member"
		ownerUserID   = "usr_connect_owner"
		peerUserID    = "usr_connect_peer"
		ownerMemberID = "mem_connect_owner"
		peerMemberID  = "mem_connect_peer"
		machineID     = "mac_connect_member"
		serverURL     = "https://runtime.example.test"
	)

	mustExec(t, pool, `
INSERT INTO users (id, email, name, display_name, avatar_url, is_admin, password_hash, email_verified_at, created_at, updated_at)
VALUES
($1, 'connect-owner@example.com', 'owner', 'Owner', '', FALSE, 'hash', $3, $3, $3),
($2, 'connect-peer@example.com', 'peer', 'Peer', '', FALSE, 'hash', $3, $3, $3)
`, ownerUserID, peerUserID, now)
	mustExec(t, pool, `
INSERT INTO workspaces (id, organization_id, slug, name, latest_message_seq, created_at, updated_at)
VALUES ($1, $2, 'connect-member', 'connect-member', 0, $3, $3)
`, workspaceID, testfixture.SeedWorkspaceOrganization(t, ctx, pool, workspaceID, now), now)
	mustExec(t, pool, `
INSERT INTO members (id, workspace_id, type, display_name, user_id, workspace_role, created_at, updated_at)
VALUES
($1, $3, 'human', 'Owner', $4, 'member', $6, $6),
($2, $3, 'human', 'Peer', $5, 'member', $6, $6)
`, ownerMemberID, peerMemberID, workspaceID, ownerUserID, peerUserID, now)
	mustExec(t, pool, `
INSERT INTO machines (id, workspace_id, created_by_user_id, name, api_key, created_at, updated_at)
VALUES ($1, $2, $3, 'Member machine', 'member-machine-key', $4, $4)
`, machineID, workspaceID, ownerUserID, now)

	token, err := service.CreateMachineConnectToken(ctx, ownerUserID, workspaceID, machineID, serverURL)
	require.NoError(t, err)
	require.NotEmpty(t, token)

	var storedServerURL string
	require.NoError(t, pool.QueryRow(ctx, `
SELECT server_url
FROM machine_connect_tokens
WHERE machine_id = $1
`, machineID).Scan(&storedServerURL))
	require.Equal(t, serverURL, storedServerURL)

	_, err = service.CreateMachineConnectToken(ctx, peerUserID, workspaceID, machineID, serverURL)
	require.ErrorIs(t, err, storeerr.ErrForbidden)
}

func mustExec(t *testing.T, queryer coredb.QueryExecer, query string, args ...any) {
	t.Helper()

	_, err := queryer.Exec(context.Background(), query, args...)
	require.NoError(t, err)
}

type runtimeAgentFixture struct {
	userID      string
	workspaceID string
	machineID   string
	agentID     string
	memberID    string
}

func seedRuntimeAgentFixture(t *testing.T, pool coredb.DB, now time.Time, prefix string) runtimeAgentFixture {
	t.Helper()

	userID := "usr_" + prefix
	workspaceID := "wsp_" + prefix
	agentMemberID := "mem_" + prefix
	machineID := "mac_" + prefix
	agentID := "agt_" + prefix

	mustExec(t, pool, `
INSERT INTO users (id, email, name, display_name, avatar_url, is_admin, password_hash, email_verified_at, created_at, updated_at)
VALUES ($1, $2, $3, 'Owner', '', TRUE, 'hash', $4, $4, $4)
`, userID, prefix+"@example.com", "owner_"+prefix, now)
	mustExec(t, pool, `
INSERT INTO workspaces (id, organization_id, slug, name, latest_message_seq, created_at, updated_at)
VALUES ($1, $2, $3, $3, 0, $4, $4)
`, workspaceID, testfixture.SeedWorkspaceOrganization(t, context.Background(), pool, workspaceID, now), prefix, now)
	mustExec(t, pool, `
INSERT INTO members (id, workspace_id, type, display_name, created_at, updated_at)
VALUES ($1, $2, 'agent', 'Runtime Agent', $3, $3)
`, agentMemberID, workspaceID, now)
	mustExec(t, pool, `
INSERT INTO machines (
	id, workspace_id, created_by_user_id, name, api_key,
	connected_at, disconnected_at, created_at, updated_at
) VALUES (
	$1, $2, $3, 'Runtime machine', $4,
	$5, NULL, $5, $5
)
`, machineID, workspaceID, userID, prefix+"-key", now)
	mustExec(t, pool, `
INSERT INTO agents (
	id, workspace_id, machine_id, member_id, name, runtime, model, description,
	status, activity, activity_detail, created_at, updated_at
) VALUES (
	$1, $2, $3, $4, 'runtime-agent', 'codex', 'gpt-5.4', '',
	'inactive', 'offline', '', $5, $5
)
`, agentID, workspaceID, machineID, agentMemberID, now)

	return runtimeAgentFixture{
		userID:      userID,
		workspaceID: workspaceID,
		machineID:   machineID,
		agentID:     agentID,
		memberID:    agentMemberID,
	}
}
