package dispatcher

import (
	"context"
	"strings"
	"sync"
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/desktop/internal/agent/drivercontract"
	"github.com/tidbcloud/AgentTeam/desktop/internal/consts"
	"github.com/tidbcloud/AgentTeam/desktop/internal/types"
	"github.com/tidbcloud/AgentTeam/desktop/internal/wire/manager"
)

type reportingStubChannel struct {
	mu     sync.Mutex
	id     string
	events []types.AgentEvent
}

func (c *reportingStubChannel) ID() string { return c.id }

func (c *reportingStubChannel) Enabled() bool { return true }

func (c *reportingStubChannel) Start(context.Context) error { return nil }

func (c *reportingStubChannel) Stop(context.Context) error { return nil }

func (c *reportingStubChannel) Send(context.Context, types.Metadata, types.OutboundMessage) error {
	return nil
}

func (c *reportingStubChannel) Emit(event types.AgentEvent) {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.events = append(c.events, event)
}

func (c *reportingStubChannel) eventCount() int {
	c.mu.Lock()
	defer c.mu.Unlock()
	return len(c.events)
}

func (c *reportingStubChannel) eventSnapshot() []types.AgentEvent {
	c.mu.Lock()
	defer c.mu.Unlock()
	return append([]types.AgentEvent(nil), c.events...)
}

func TestBuildEventCallbackEmitsCompactionActivity(t *testing.T) {
	d := mustNewDispatcher("")
	mgr := manager.New(d, nil)
	ch := &reportingStubChannel{id: "agentteam"}
	mgr.Register(ch)
	d.serverConnections = mgr

	callback := d.buildEventCallback("agentteam", "agent-a", "agent-a", "")
	callback(drivercontract.ParsedEvent{
		Kind:      consts.ParsedEventKindRuntimeCompacting,
		SessionID: "sess-1",
	})

	require.Len(t, ch.events, 2)

	activity, ok := ch.events[0].(types.ActivityEvent)
	require.True(t, ok)
	require.Equal(t, "agent-a", activity.AgentID)
	require.Equal(t, "working", activity.Activity)
	require.Equal(t, "Compacting context...", activity.Detail)

	trajectory, ok := ch.events[1].(types.TrajectoryEvent)
	require.True(t, ok)
	require.Equal(t, "agent-a", trajectory.AgentID)
	require.Equal(t, "sess-1", trajectory.SessionID)
	require.Len(t, trajectory.Entries, 1)
	require.Equal(t, consts.TrajectoryKindStatus, trajectory.Entries[0].Kind)
	require.Equal(t, "working", trajectory.Entries[0].Activity)
	require.Equal(t, "Compacting context...", trajectory.Entries[0].Detail)
}

func TestBuildEventCallbackRecordsCompactionCompletionBeforeIdle(t *testing.T) {
	d := mustNewDispatcher("")
	mgr := manager.New(d, nil)
	ch := &reportingStubChannel{id: "agentteam"}
	mgr.Register(ch)
	d.serverConnections = mgr

	callback := d.buildEventCallback("agentteam", "agent-a", "agent-a", "")
	callback(drivercontract.ParsedEvent{
		Kind:      consts.ParsedEventKindRuntimeCompacting,
		SessionID: "sess-1",
	})
	callback(drivercontract.ParsedEvent{
		Kind:      consts.ParsedEventKindRuntimeCompacted,
		SessionID: "sess-1",
		Message:   "Context compacted (152355 → 4234 tokens).",
	})
	callback(drivercontract.ParsedEvent{
		Kind:      consts.ParsedEventKindRuntimeIdle,
		SessionID: "sess-1",
	})

	events := ch.eventSnapshot()
	require.Len(t, events, 6)
	details := make([]string, 0, 3)
	for _, event := range events {
		trajectory, ok := event.(types.TrajectoryEvent)
		if !ok {
			continue
		}
		require.Len(t, trajectory.Entries, 1)
		details = append(details, trajectory.Entries[0].Detail)
	}
	require.Equal(t, []string{
		"Compacting context...",
		"Context compacted (152355 → 4234 tokens).",
		"Idle",
	}, details)
}

func TestRecordUrgentDeliveryAcceptedUpdatesActivity(t *testing.T) {
	d := mustNewDispatcher("")
	mgr := manager.New(d, nil)
	ch := &reportingStubChannel{id: "agentteam"}
	mgr.Register(ch)
	d.serverConnections = mgr

	binding := conversationBinding{
		channelID:     "agentteam",
		serverAgentID: "agent-a",
	}
	d.sessionManager.Set("agent-a", &interactiveSession{
		sessionKey:    "agent-a",
		agentID:       "agent-a",
		binding:       binding,
		serverAgentID: "agent-a",
	})

	d.recordUrgentDeliveryAccepted("agent-a", "agent-a", binding, types.InboxMessage{
		MessageID:   "msg-urgent",
		Target:      "#ops",
		SenderName:  "alice",
		ChannelName: "ops",
		Content:     "please switch now",
	})

	snap, ok := d.Session("agent-a")
	require.True(t, ok)
	require.Equal(t, "working", snap.Activity)
	require.Contains(t, snap.ActivityDetail, "Urgent message from alice in #ops")
	require.NotContains(t, snap.ActivityDetail, "please switch now")

	events := ch.eventSnapshot()
	require.Len(t, events, 2)
	activity, ok := events[0].(types.ActivityEvent)
	require.True(t, ok)
	require.Equal(t, "agent-a", activity.AgentID)
	require.Equal(t, "working", activity.Activity)
	require.Contains(t, activity.Detail, "Urgent message from alice in #ops")
	require.NotContains(t, activity.Detail, "please switch now")

	trajectory, ok := events[1].(types.TrajectoryEvent)
	require.True(t, ok)
	require.Equal(t, "agent-a", trajectory.AgentID)
	require.Len(t, trajectory.Entries, 1)
	require.Equal(t, consts.TrajectoryKindStatus, trajectory.Entries[0].Kind)
	require.Contains(t, trajectory.Entries[0].Detail, "Urgent message from alice in #ops")
	require.NotContains(t, trajectory.Entries[0].Detail, "please switch now")
}

func TestRecordUrgentDeliveryAcceptedUsesWorkflowPresentationAuthor(t *testing.T) {
	d := mustNewDispatcher("")
	mgr := manager.New(d, nil)
	ch := &reportingStubChannel{id: "agentteam"}
	mgr.Register(ch)
	d.serverConnections = mgr

	binding := conversationBinding{
		channelID:     "agentteam",
		serverAgentID: "agent-a",
	}
	d.sessionManager.Set("agent-a", &interactiveSession{
		sessionKey:    "agent-a",
		agentID:       "agent-a",
		binding:       binding,
		serverAgentID: "agent-a",
	})

	d.recordUrgentDeliveryAccepted("agent-a", "agent-a", binding, types.InboxMessage{
		MessageID: "msg-workflow-callback",
		Target:    "#ops",
		AuthorProfile: types.AuthorProfile{
			ActorType:   "human",
			PrincipalID: "usr_owner",
			DisplayName: "Arithmetic E2E Owner",
		},
		Source: types.ContributionSource{
			Kind:    "workflow_run",
			RefType: "workflow_run",
			RefID:   "wfr_calculator",
			RunID:   "wfr_calculator",
			EventID: "wrf_calculator_effect",
			Label:   "Parallel calculator",
		},
		Content: "secret workflow payload",
	})

	snap, ok := d.Session("agent-a")
	require.True(t, ok)
	require.Contains(t, snap.ActivityDetail, "Urgent message from Parallel calculator in #ops")
	require.NotContains(t, snap.ActivityDetail, "Arithmetic E2E Owner")
	require.NotContains(t, snap.ActivityDetail, "secret workflow payload")

	events := ch.eventSnapshot()
	require.Len(t, events, 2)
	activity, ok := events[0].(types.ActivityEvent)
	require.True(t, ok)
	require.Contains(t, activity.Detail, "Urgent message from Parallel calculator in #ops")
	require.NotContains(t, activity.Detail, "Arithmetic E2E Owner")

	trajectory, ok := events[1].(types.TrajectoryEvent)
	require.True(t, ok)
	require.Len(t, trajectory.Entries, 1)
	require.Contains(t, trajectory.Entries[0].Detail, "Urgent message from Parallel calculator in #ops")
	require.NotContains(t, trajectory.Entries[0].Detail, "Arithmetic E2E Owner")
}

func TestBuildEventCallbackEmitsIdleAfterRuntimeIdle(t *testing.T) {
	d := mustNewDispatcher("")
	mgr := manager.New(d, nil)
	ch := &reportingStubChannel{id: "agentteam"}
	mgr.Register(ch)
	d.serverConnections = mgr

	callback := d.buildEventCallback("agentteam", "agent-a", "agent-a", "")
	callback(drivercontract.ParsedEvent{
		Kind:      consts.ParsedEventKindRuntimeIdle,
		SessionID: "sess-1",
	})

	require.Len(t, ch.events, 2)

	activity, ok := ch.events[0].(types.ActivityEvent)
	require.True(t, ok)
	require.Equal(t, "agent-a", activity.AgentID)
	require.Equal(t, "online", activity.Activity)
	require.Equal(t, "Idle", activity.Detail)

	trajectory, ok := ch.events[1].(types.TrajectoryEvent)
	require.True(t, ok)
	require.Equal(t, "agent-a", trajectory.AgentID)
	require.Equal(t, "sess-1", trajectory.SessionID)
	require.Len(t, trajectory.Entries, 1)
	require.Equal(t, consts.TrajectoryKindStatus, trajectory.Entries[0].Kind)
	require.Equal(t, "online", trajectory.Entries[0].Activity)
	require.Equal(t, "Idle", trajectory.Entries[0].Detail)
}

func TestBuildEventCallbackEmitsThinkingActivityAndTrajectory(t *testing.T) {
	d := mustNewDispatcher("")
	mgr := manager.New(d, nil)
	ch := &reportingStubChannel{id: "agentteam"}
	mgr.Register(ch)
	d.serverConnections = mgr

	callback := d.buildEventCallback("agentteam", "agent-a", "agent-a", "")
	callback(drivercontract.ParsedEvent{
		Kind:      consts.ParsedEventKindThinking,
		SessionID: "sess-thinking",
		Text:      "Planning the next step",
	})

	require.Len(t, ch.events, 2)

	activity, ok := ch.events[0].(types.ActivityEvent)
	require.True(t, ok)
	require.Equal(t, "agent-a", activity.AgentID)
	require.Equal(t, "thinking", activity.Activity)
	require.Empty(t, activity.Detail)

	trajectory, ok := ch.events[1].(types.TrajectoryEvent)
	require.True(t, ok)
	require.Equal(t, "agent-a", trajectory.AgentID)
	require.Equal(t, "sess-thinking", trajectory.SessionID)
	require.Len(t, trajectory.Entries, 1)
	require.Equal(t, consts.ParsedEventKindThinking, trajectory.Entries[0].Kind)
	require.Equal(t, "Planning the next step", trajectory.Entries[0].Text)
}

func TestBuildEventCallbackEmitsThinkingTitleInActivityAndTrajectory(t *testing.T) {
	d := mustNewDispatcher("")
	mgr := manager.New(d, nil)
	ch := &reportingStubChannel{id: "agentteam"}
	mgr.Register(ch)
	d.serverConnections = mgr

	callback := d.buildEventCallback("agentteam", "agent-a", "agent-a", "")
	callback(drivercontract.ParsedEvent{
		Kind:      consts.ParsedEventKindThinking,
		SessionID: "sess-thinking",
		Text:      "**Checking events**\nTracing trajectory",
		Title:     "Checking events",
		SectionID: "reasoning-1:summary:0",
	})

	require.Len(t, ch.events, 2)

	activity, ok := ch.events[0].(types.ActivityEvent)
	require.True(t, ok)
	require.Equal(t, "thinking", activity.Activity)
	require.Equal(t, "Checking events", activity.Detail)

	trajectory, ok := ch.events[1].(types.TrajectoryEvent)
	require.True(t, ok)
	require.Len(t, trajectory.Entries, 1)
	require.Equal(t, consts.ParsedEventKindThinking, trajectory.Entries[0].Kind)
	require.Equal(t, "Checking events", trajectory.Entries[0].Title)
	require.Equal(t, "reasoning-1:summary:0", trajectory.Entries[0].SectionID)
}

func TestBuildEventCallbackSuppressesDuplicateThinkingActivity(t *testing.T) {
	d := mustNewDispatcher("")
	mgr := manager.New(d, nil)
	ch := &reportingStubChannel{id: "agentteam"}
	mgr.Register(ch)
	d.serverConnections = mgr

	callback := d.buildEventCallback("agentteam", "agent-a", "agent-a", "")
	callback(drivercontract.ParsedEvent{
		Kind:      consts.ParsedEventKindThinking,
		SessionID: "sess-thinking",
		Text:      "**Checking events**\nTracing",
		Title:     "Checking events",
		SectionID: "reasoning-1:summary:0",
	})
	callback(drivercontract.ParsedEvent{
		Kind:      consts.ParsedEventKindThinking,
		SessionID: "sess-thinking",
		Text:      " more",
		Title:     "Checking events",
		SectionID: "reasoning-1:summary:0",
	})

	require.Len(t, ch.events, 3)
	_, ok := ch.events[0].(types.ActivityEvent)
	require.True(t, ok)
	_, ok = ch.events[1].(types.TrajectoryEvent)
	require.True(t, ok)
	trajectory, ok := ch.events[2].(types.TrajectoryEvent)
	require.True(t, ok)
	require.Len(t, trajectory.Entries, 1)
	require.Equal(t, " more", trajectory.Entries[0].Text)
}

func TestBuildEventCallbackDescribesWriteStdinAsWaitingForCommandOutput(t *testing.T) {
	d := mustNewDispatcher("")
	mgr := manager.New(d, nil)
	ch := &reportingStubChannel{id: "agentteam"}
	mgr.Register(ch)
	d.serverConnections = mgr

	callback := d.buildEventCallback("agentteam", "agent-a", "agent-a", "")
	callback(drivercontract.ParsedEvent{
		Kind:      consts.ParsedEventKindToolCall,
		SessionID: "sess-2",
		ToolName:  "write_stdin",
		ToolInput: `{"session_id":14107}`,
	})

	require.Len(t, ch.events, 2)

	activity, ok := ch.events[0].(types.ActivityEvent)
	require.True(t, ok)
	require.Equal(t, "agent-a", activity.AgentID)
	require.Equal(t, "working", activity.Activity)
	require.Equal(t, "Waiting for command output…", activity.Detail)

	trajectory, ok := ch.events[1].(types.TrajectoryEvent)
	require.True(t, ok)
	require.Equal(t, "agent-a", trajectory.AgentID)
	require.Equal(t, "sess-2", trajectory.SessionID)
	require.Len(t, trajectory.Entries, 2)
	require.Equal(t, consts.TrajectoryKindToolStart, trajectory.Entries[0].Kind)
	require.Equal(t, "write_stdin", trajectory.Entries[0].ToolName)
	require.Equal(t, consts.TrajectoryKindStatus, trajectory.Entries[1].Kind)
	require.Equal(t, "working", trajectory.Entries[1].Activity)
	require.Equal(t, "Waiting for command output…", trajectory.Entries[1].Detail)
}

func TestBuildEventCallbackDescribesExecCommandAsRunningCommand(t *testing.T) {
	d := mustNewDispatcher("")
	mgr := manager.New(d, nil)
	ch := &reportingStubChannel{id: "agentteam"}
	mgr.Register(ch)
	d.serverConnections = mgr

	callback := d.buildEventCallback("agentteam", "agent-a", "agent-a", "")
	callback(drivercontract.ParsedEvent{
		Kind:      consts.ParsedEventKindToolCall,
		SessionID: "sess-3",
		ToolName:  "exec_command",
		ToolInput: `{"cmd":"make e2e-web"}`,
	})

	require.Len(t, ch.events, 2)

	activity, ok := ch.events[0].(types.ActivityEvent)
	require.True(t, ok)
	require.Equal(t, "agent-a", activity.AgentID)
	require.Equal(t, "working", activity.Activity)
	require.Equal(t, "Running command…", activity.Detail)

	trajectory, ok := ch.events[1].(types.TrajectoryEvent)
	require.True(t, ok)
	require.Equal(t, "agent-a", trajectory.AgentID)
	require.Equal(t, "sess-3", trajectory.SessionID)
	require.Len(t, trajectory.Entries, 2)
	require.Equal(t, consts.TrajectoryKindToolStart, trajectory.Entries[0].Kind)
	require.Equal(t, "exec_command", trajectory.Entries[0].ToolName)
	require.Equal(t, consts.TrajectoryKindStatus, trajectory.Entries[1].Kind)
	require.Equal(t, "working", trajectory.Entries[1].Activity)
	require.Equal(t, "Running command…", trajectory.Entries[1].Detail)
}

func TestBuildEventCallbackMapsBuiltinToolNamesToActivityDetails(t *testing.T) {
	tests := []struct {
		name       string
		toolName   string
		wantDetail string
	}{
		{name: "view file", toolName: "view", wantDetail: "Viewing file…"},
		{name: "list files", toolName: "ls", wantDetail: "Listing files…"},
		{name: "list files alias", toolName: "list_files", wantDetail: "Listing files…"},
		{name: "update todos", toolName: "todos", wantDetail: "Updating TODOs…"},
		{name: "edit file", toolName: "edit", wantDetail: "Editing file…"},
		{name: "multi edit file", toolName: "multiedit", wantDetail: "Editing file…"},
		{name: "write file", toolName: "write", wantDetail: "Writing file…"},
		{name: "spawn agent", toolName: "collaboration.spawn_agent", wantDetail: "Spawning an agent…"},
		{name: "wait for agent", toolName: "collaboration.wait", wantDetail: "Waiting for an agent…"},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			d := mustNewDispatcher("")
			mgr := manager.New(d, nil)
			ch := &reportingStubChannel{id: "agentteam"}
			mgr.Register(ch)
			d.serverConnections = mgr

			callback := d.buildEventCallback("agentteam", "agent-a", "agent-a", "")
			callback(drivercontract.ParsedEvent{
				Kind:      consts.ParsedEventKindToolCall,
				SessionID: "sess-builtin",
				ToolName:  tt.toolName,
				ToolInput: `{}`,
			})

			require.Len(t, ch.events, 2)

			activity, ok := ch.events[0].(types.ActivityEvent)
			require.True(t, ok)
			require.Equal(t, "working", activity.Activity)
			require.Equal(t, tt.wantDetail, activity.Detail)

			trajectory, ok := ch.events[1].(types.TrajectoryEvent)
			require.True(t, ok)
			require.Len(t, trajectory.Entries, 2)
			require.Equal(t, consts.TrajectoryKindToolStart, trajectory.Entries[0].Kind)
			require.Equal(t, tt.toolName, trajectory.Entries[0].ToolName, "raw provider tool name stays available for debug")
			require.Equal(t, consts.TrajectoryKindStatus, trajectory.Entries[1].Kind)
			require.Equal(t, tt.wantDetail, trajectory.Entries[1].Detail)
		})
	}
}

func TestBuildEventCallbackPreservesLargeToolInputJSON(t *testing.T) {
	d := mustNewDispatcher("")
	mgr := manager.New(d, nil)
	ch := &reportingStubChannel{id: "agentteam"}
	mgr.Register(ch)
	d.serverConnections = mgr

	content := "prefix " + strings.Repeat("long ", 600) + "suffix"
	toolInput := `{"target":"dm:test","content":"` + content + `"}`

	callback := d.buildEventCallback("agentteam", "agent-a", "agent-a", "")
	callback(drivercontract.ParsedEvent{
		Kind:      consts.ParsedEventKindToolCall,
		SessionID: "sess-4",
		ToolName:  "mcp_agentteam_message_send",
		ToolInput: toolInput,
	})

	require.Len(t, ch.events, 2)
	trajectory, ok := ch.events[1].(types.TrajectoryEvent)
	require.True(t, ok)
	require.Len(t, trajectory.Entries, 2)
	require.Equal(t, consts.TrajectoryKindToolStart, trajectory.Entries[0].Kind)
	require.Equal(t, toolInput, trajectory.Entries[0].ToolInput)
	require.JSONEq(t, toolInput, trajectory.Entries[0].ToolInput)
}

func TestBuildEventCallbackPreservesToolOutput(t *testing.T) {
	d := mustNewDispatcher("")
	mgr := manager.New(d, nil)
	ch := &reportingStubChannel{id: "agentteam"}
	mgr.Register(ch)
	d.serverConnections = mgr

	callback := d.buildEventCallback("agentteam", "agent-a", "agent-a", "")
	callback(drivercontract.ParsedEvent{
		Kind:      consts.ParsedEventKindToolOutput,
		SessionID: "sess-output",
		ToolName:  "shell",
		Metadata: map[string]any{
			"customFlag":  true,
			"customCount": 2,
		},
		Text: "stdout line\nstderr line",
	})

	require.Len(t, ch.events, 1)

	trajectory, ok := ch.events[0].(types.TrajectoryEvent)
	require.True(t, ok)
	require.Equal(t, "agent-a", trajectory.AgentID)
	require.Equal(t, "sess-output", trajectory.SessionID)
	require.Len(t, trajectory.Entries, 1)
	require.Equal(t, consts.ParsedEventKindToolOutput, trajectory.Entries[0].Kind)
	require.Equal(t, "shell", trajectory.Entries[0].ToolName)
	require.Equal(t, "stdout line\nstderr line", trajectory.Entries[0].Text)
	require.Equal(t, map[string]any{
		"customFlag":  true,
		"customCount": 2,
	}, trajectory.Entries[0].Metadata)
}

func TestBuildEventCallbackFallsBackUnknownParsedEventToText(t *testing.T) {
	d := mustNewDispatcher("")
	mgr := manager.New(d, nil)
	ch := &reportingStubChannel{id: "agentteam"}
	mgr.Register(ch)
	d.serverConnections = mgr

	callback := d.buildEventCallback("agentteam", "agent-a", "agent-a", "")
	callback(drivercontract.ParsedEvent{
		Kind:      "runtime.custom_output",
		SessionID: "sess-custom",
		Text:      "custom runtime output",
	})

	require.Len(t, ch.events, 2)

	activity, ok := ch.events[0].(types.ActivityEvent)
	require.True(t, ok)
	require.Equal(t, "thinking", activity.Activity)

	trajectory, ok := ch.events[1].(types.TrajectoryEvent)
	require.True(t, ok)
	require.Equal(t, "sess-custom", trajectory.SessionID)
	require.Len(t, trajectory.Entries, 1)
	require.Equal(t, consts.ParsedEventKindText, trajectory.Entries[0].Kind)
	require.Equal(t, "custom runtime output", trajectory.Entries[0].Text)
}

func TestBuildEventCallbackPreservesFullPromptInputText(t *testing.T) {
	d := mustNewDispatcher("")
	mgr := manager.New(d, nil)
	ch := &reportingStubChannel{id: "agentteam"}
	mgr.Register(ch)
	d.serverConnections = mgr

	callback := d.buildEventCallback("agentteam", "agent-a", "agent-a", "")
	fullText := strings.Repeat("0123456789", 260)
	callback(drivercontract.ParsedEvent{
		Kind:       consts.ParsedEventKindPromptInput,
		SessionID:  "sess-4",
		Text:       fullText,
		PromptKind: consts.PromptKindTurnInput,
		Runtime:    "codex",
		EventType:  "wake",
	})

	require.Len(t, ch.events, 1)

	trajectory, ok := ch.events[0].(types.TrajectoryEvent)
	require.True(t, ok)
	require.Equal(t, "agent-a", trajectory.AgentID)
	require.Equal(t, "sess-4", trajectory.SessionID)
	require.Len(t, trajectory.Entries, 1)
	require.Equal(t, consts.ParsedEventKindPromptInput, trajectory.Entries[0].Kind)
	require.Equal(t, fullText, trajectory.Entries[0].Text)
	require.Equal(t, "turn_input", trajectory.Entries[0].PromptKind)
	require.Equal(t, "codex", trajectory.Entries[0].Runtime)
	require.Equal(t, "wake", trajectory.Entries[0].EventType)
}

func TestBuildEventCallbackAllowsEmptyPromptInputText(t *testing.T) {
	d := mustNewDispatcher("")
	mgr := manager.New(d, nil)
	ch := &reportingStubChannel{id: "agentteam"}
	mgr.Register(ch)
	d.serverConnections = mgr

	callback := d.buildEventCallback("agentteam", "agent-a", "agent-a", "")
	callback(drivercontract.ParsedEvent{
		Kind:       consts.ParsedEventKindPromptInput,
		SessionID:  "sess-5",
		Text:       "",
		PromptKind: consts.PromptKindDeveloperInstructions,
		Runtime:    "codex",
		EventType:  "thread_start",
	})

	require.Len(t, ch.events, 1)

	trajectory, ok := ch.events[0].(types.TrajectoryEvent)
	require.True(t, ok)
	require.Equal(t, "agent-a", trajectory.AgentID)
	require.Equal(t, "sess-5", trajectory.SessionID)
	require.Len(t, trajectory.Entries, 1)
	require.Equal(t, consts.ParsedEventKindPromptInput, trajectory.Entries[0].Kind)
	require.Empty(t, trajectory.Entries[0].Text)
	require.Equal(t, "developer_instructions", trajectory.Entries[0].PromptKind)
	require.Equal(t, "codex", trajectory.Entries[0].Runtime)
	require.Equal(t, "thread_start", trajectory.Entries[0].EventType)
}

func TestBuildEventCallbackRuntimeInputPendingAddsPromptInputActivity(t *testing.T) {
	d := mustNewDispatcher("")
	mgr := manager.New(d, nil)
	ch := &reportingStubChannel{id: "agentteam"}
	mgr.Register(ch)
	d.serverConnections = mgr

	callback := d.buildEventCallback("agentteam", "agent-a", "agent-a", "")
	callback(drivercontract.ParsedEvent{
		Kind:           consts.ParsedEventKindRuntimeInputPending,
		SessionID:      "sess-6",
		Runtime:        "claude",
		RuntimeInputID: "msg-pending",
		Text:           "New message received:\n\n[target=#agentteam msg=msg-pending] @owner: hello",
		PromptKind:     consts.PromptKindDeliveryText,
		EventType:      "routed_message_delivery",
		Message:        "New message delivered to agent.",
	})

	require.Len(t, ch.events, 1)
	trajectory, ok := ch.events[0].(types.TrajectoryEvent)
	require.True(t, ok)
	require.Equal(t, "agent-a", trajectory.AgentID)
	require.Equal(t, "sess-6", trajectory.SessionID)
	require.Len(t, trajectory.Entries, 1)
	require.Equal(t, consts.ParsedEventKindPromptInput, trajectory.Entries[0].Kind)
	require.Contains(t, trajectory.Entries[0].Text, "@owner: hello")
	require.Equal(t, consts.PromptKindDeliveryText, trajectory.Entries[0].PromptKind)
	require.Equal(t, "claude", trajectory.Entries[0].Runtime)
	require.Equal(t, "routed_message_delivery", trajectory.Entries[0].EventType)
	require.Equal(t, map[string]any{
		"backgroundNotice": true,
		"deliveryState":    "pending",
		"runtimeInputId":   "msg-pending",
		"runtimeSurface":   false,
	}, trajectory.Entries[0].Metadata)
}

func TestDeliveryCheckpointSummaryOmitsMessageContent(t *testing.T) {
	msg := types.InboxMessage{
		MessageID:   "msg-secret",
		Target:      "#ops",
		SenderName:  "alice",
		ChannelName: "ops",
		Content:     "secret payload should not be persisted",
	}

	summary := deliveryCheckpointSummary(msg, false)
	require.Contains(t, summary, "New message from alice in #ops")
	require.Contains(t, summary, "msg-secret")
	require.NotContains(t, summary, "secret payload")
}

func TestDeliveryCheckpointSummaryUsesWorkflowPresentationAuthor(t *testing.T) {
	msg := types.InboxMessage{
		MessageID: "msg-workflow-callback",
		Target:    "#ops",
		AuthorProfile: types.AuthorProfile{
			ActorType:   "human",
			PrincipalID: "usr_owner",
			DisplayName: "Arithmetic E2E Owner",
		},
		Source: types.ContributionSource{
			Kind:    "workflow_run",
			RefType: "workflow_run",
			RefID:   "wfr_calculator",
			RunID:   "wfr_calculator",
			EventID: "wrf_calculator_effect",
			Label:   "Parallel calculator",
		},
		Content: "secret workflow payload",
	}

	summary := deliveryCheckpointSummary(msg, false)
	require.Contains(t, summary, "New message from Parallel calculator in #ops")
	require.NotContains(t, summary, "Arithmetic E2E Owner")
	require.NotContains(t, summary, "secret workflow payload")
}
