package agent

import (
	"bytes"
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/api/dto"
	"github.com/tidbcloud/AgentTeam/internal/store/agent"
	"github.com/tidbcloud/AgentTeam/internal/store/message"
	"github.com/tidbcloud/AgentTeam/internal/store/rewind"
)

func deprecatedScopedCursorQueryAliasForNegativeTest() string {
	return "space" + "_id"
}

type fakeMessageStore struct {
	sentParams        message.SendMessageParams
	sendFreshResult   message.SendMessageResult
	sendFreshErr      error
	workspaceSnapshot agent.WorkspaceSnapshot
	resolvedChannelID string
	resolvedTarget    string
	readableTarget    string
	resolveErr        error
}

type fakeWorkspaceRuntime struct {
	connectedMachines map[string]bool
	runningAgents     map[workspaceRuntimeCall]bool
	sessionIDs        map[workspaceRuntimeCall]string
	statuses          map[workspaceRuntimeCall]workspaceRuntimeStatus
}

type workspaceRuntimeCall struct{ MachineID, AgentID string }
type workspaceRuntimeStatus struct {
	Status   string
	Activity string
	Detail   string
}

func (fake *fakeWorkspaceRuntime) MachineConnected(machineID string) bool {
	return fake.connectedMachines[machineID]
}

func (fake *fakeWorkspaceRuntime) AgentRuntimeRunning(machineID string, agentID string) bool {
	return fake.runningAgents[workspaceRuntimeCall{MachineID: machineID, AgentID: agentID}]
}

func (fake *fakeWorkspaceRuntime) AgentRuntimeSessionID(machineID string, agentID string) (string, bool) {
	sessionID := fake.sessionIDs[workspaceRuntimeCall{MachineID: machineID, AgentID: agentID}]
	return sessionID, sessionID != ""
}

func (fake *fakeWorkspaceRuntime) AgentRuntimeStatus(machineID string, agentID string) (string, string, string, bool) {
	status, ok := fake.statuses[workspaceRuntimeCall{MachineID: machineID, AgentID: agentID}]
	if !ok {
		return "", "", "", false
	}
	return status.Status, status.Activity, status.Detail, true
}

func (fake *fakeMessageStore) ResolveWritableAgentTargetScopeID(_ context.Context, _ agent.AuthenticatedAgent, target string) (string, error) {
	fake.resolvedTarget = target
	if fake.resolveErr != nil {
		return "", fake.resolveErr
	}
	return fake.resolvedChannelID, nil
}

func (fake *fakeMessageStore) ResolveReadableAgentTargetScopeID(_ context.Context, _ agent.AuthenticatedAgent, target string) (string, error) {
	fake.readableTarget = target
	if fake.resolveErr != nil {
		return "", fake.resolveErr
	}
	return fake.resolvedChannelID, nil
}

func (fake *fakeMessageStore) ResolveAgentReviewSubjectScopeID(context.Context, agent.AuthenticatedAgent, string) (string, error) {
	panic("not used")
}

func (fake *fakeMessageStore) SendAgentMessage(_ context.Context, _ agent.AuthenticatedAgent, params message.SendMessageParams) (message.MessageDelivery, error) {
	fake.sentParams = params
	return message.MessageDelivery{
		ThreadID:  "thr_1",
		Seq:       42,
		MessageID: "msg_1",
		Message:   message.ReceiveMessage{MessageID: "msg_1", Content: params.Content},
		AppMessage: &message.AppMessage{
			ID:        "msg_1",
			ChannelID: "chn_1",
			Content:   params.Content,
		},
		WakeRecipients: []message.AgentRecipient{{AgentID: "agt_0000000000wake", MachineID: "mac_1"}},
	}, nil
}

func (fake *fakeMessageStore) SendAgentMessageWithFreshness(_ context.Context, _ agent.AuthenticatedAgent, params message.SendMessageParams) (message.SendMessageResult, error) {
	fake.sentParams = params
	if fake.sendFreshErr != nil {
		return message.SendMessageResult{}, fake.sendFreshErr
	}
	if fake.sendFreshResult.State != "" {
		return fake.sendFreshResult, nil
	}
	delivery := message.MessageDelivery{
		ThreadID:        "thr_1",
		Seq:             42,
		MessageID:       "msg_1",
		ClientMessageID: params.ClientMessageID,
		Message:         message.ReceiveMessage{MessageID: "msg_1", TargetKey: params.Freshness.TargetKey, TargetSeq: 42, Content: params.Content},
		AppMessage: &message.AppMessage{
			ID:        "msg_1",
			ChannelID: "chn_1",
			Content:   params.Content,
		},
		WakeRecipients: []message.AgentRecipient{{AgentID: "agt_0000000000wake", MachineID: "mac_1"}},
	}
	return message.SendMessageResult{
		State:           message.SendStateSent,
		MessageID:       delivery.MessageID,
		TargetKey:       params.Freshness.TargetKey,
		ClientMessageID: params.ClientMessageID,
		SeenUpToSeq:     params.Freshness.SeenUpToSeq,
		AcceptedSeq:     delivery.Seq,
		LatestSeq:       delivery.Seq,
		Delivery:        delivery,
	}, nil
}

func (fake *fakeMessageStore) GetAgentWorkspaceSnapshot(context.Context, agent.AuthenticatedAgent) (agent.WorkspaceSnapshot, error) {
	return fake.workspaceSnapshot, nil
}

type fakeScopeReceiveMessageStore struct {
	fakeMessageStore
}

type fakeScopeAPIMessageStore struct {
	fakeScopeReceiveMessageStore
	historyAgent   agent.AuthenticatedAgent
	historyRequest message.ScopeAPIHistoryRequest
	historyPage    message.ScopeAPIHistoryPage
	searchAgent    agent.AuthenticatedAgent
	searchRequest  message.ScopeAPISearchRequest
	searchPage     message.ScopeAPISearchPage
	searchErr      error
	readAgent      agent.AuthenticatedAgent
	readRequest    message.ScopeAPIReadCheckRequest
	readResult     message.ScopeAPIReadCheckResult
	memberAgent    agent.AuthenticatedAgent
	memberScopeID  string
	memberRequest  message.ScopeMemberListRequest
	memberRows     []message.ScopeMemberDisplayRow
}

func (fake *fakeScopeAPIMessageStore) ListScopeHistoryForAgent(_ context.Context, authenticatedAgent agent.AuthenticatedAgent, request message.ScopeAPIHistoryRequest) (message.ScopeAPIHistoryPage, error) {
	fake.historyAgent = authenticatedAgent
	fake.historyRequest = request
	return fake.historyPage, nil
}

func (fake *fakeScopeAPIMessageStore) SearchScopeMessagesForAgent(_ context.Context, authenticatedAgent agent.AuthenticatedAgent, request message.ScopeAPISearchRequest) (message.ScopeAPISearchPage, error) {
	fake.searchAgent = authenticatedAgent
	fake.searchRequest = request
	if fake.searchErr != nil {
		return message.ScopeAPISearchPage{}, fake.searchErr
	}
	return fake.searchPage, nil
}

func (fake *fakeScopeAPIMessageStore) CanReadScopeMessageForAgent(_ context.Context, authenticatedAgent agent.AuthenticatedAgent, request message.ScopeAPIReadCheckRequest) (message.ScopeAPIReadCheckResult, error) {
	fake.readAgent = authenticatedAgent
	fake.readRequest = request
	return fake.readResult, nil
}

func (fake *fakeScopeAPIMessageStore) ListScopeMemberDisplayRowsForAgent(_ context.Context, authenticatedAgent agent.AuthenticatedAgent, request message.ScopeMemberListRequest) (message.ScopeMemberListPage, error) {
	fake.memberAgent = authenticatedAgent
	fake.memberScopeID = request.ScopeID
	fake.memberRequest = request
	return message.ScopeMemberListPage{Members: fake.memberRows, Limit: request.Limit}, nil
}

type fakeRewindStore struct {
	pruned bool
}

func (fake *fakeRewindStore) DeleteAgentRecoveryPointsOutsideThread(context.Context, string, string, string) ([]rewind.MessageRecoveryPoint, error) {
	fake.pruned = true
	return nil, nil
}

type fakeMessageFanout struct {
	calls       int
	workspaceID string
	messageID   string
	deliveryID  string
}

func (fanout *fakeMessageFanout) EmitCommittedMessage(_ context.Context, workspaceID string, appMessage message.AppMessage, delivery message.MessageDelivery) {
	fanout.calls++
	fanout.workspaceID = workspaceID
	fanout.messageID = appMessage.ID
	fanout.deliveryID = delivery.MessageID
}

func setAgentAuthHeader(request *http.Request) {
	request.Header.Set("Authorization", "Bearer test-token")
}

func legacyAgentIdentity() agent.AuthenticatedAgent {
	return agent.AuthenticatedAgent{AgentID: "agt_0000000000test", WorkspaceID: "wsp_test", MemberID: "0000000agent"}
}

func scopeFrontierAgentIdentity() agent.AuthenticatedAgent {
	identity := legacyAgentIdentity()
	identity.MachineID = "mac_test"
	identity.DaemonVersion = "agentteam/v1.0.2-rc.11"
	return identity
}

func oldDaemonAgentIdentity() agent.AuthenticatedAgent {
	identity := legacyAgentIdentity()
	identity.MachineID = "mac_test"
	identity.DaemonVersion = "v1.0.2-rc.10"
	return identity
}

const testMinDaemonVersion = "v1.0.2-rc.11"

func TestMessageHandlerGetWorkspaceReturnsSnapshotLimits(t *testing.T) {
	gin.SetMode(gin.TestMode)

	agentIdentity := scopeFrontierAgentIdentity()
	messageStore := &fakeMessageStore{workspaceSnapshot: agent.WorkspaceSnapshot{
		Channels:   []agent.WorkspaceChannel{{Name: "all", Joined: true}},
		Agents:     []agent.WorkspaceActor{{Name: "runtime", DisplayName: "Helpful App", Status: "online"}},
		Humans:     []agent.WorkspaceActor{{Name: "0000000alice", DisplayName: "Alice Zhang"}},
		Kind:       "channels",
		Limit:      1,
		HasMore:    true,
		NextCursor: "page-2",
		Limits: agent.WorkspaceSnapshotLimits{
			Channels: agent.WorkspaceSnapshotCollectionLimit{Limit: 1, Truncated: true},
			Agents:   agent.WorkspaceSnapshotCollectionLimit{Limit: 1, Truncated: true},
			Humans:   agent.WorkspaceSnapshotCollectionLimit{Limit: 1, Truncated: true},
		},
	}}
	handler := &MessageHandler{auth: fakeAgentAuthenticator{agent: agentIdentity}, store: messageStore}

	router := gin.New()
	router.GET("/api/v1/workspaces/:workspaceId/agents/:agentId/runtime/workspace", handler.GetWorkspace)
	request := httptest.NewRequest(http.MethodGet, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/workspace", nil)
	setAgentAuthHeader(request)
	response := httptest.NewRecorder()

	router.ServeHTTP(response, request)

	require.Equal(t, http.StatusOK, response.Code)
	require.JSONEq(t, `{
		"channels":[{"name":"all","joined":true}],
		"agents":[{"name":"runtime","displayName":"Helpful App","status":"online"}],
		"humans":[{"name":"0000000alice","displayName":"Alice Zhang"}],
		"kind":"channels",
		"limit":1,
		"hasMore":true,
		"nextCursor":"page-2",
		"has_more":true,
		"next_cursor":"page-2",
		"limits":{
			"channels":{"limit":1,"truncated":true},
			"agents":{"limit":1,"truncated":true},
			"humans":{"limit":1,"truncated":true}
		}
	}`, response.Body.String())
}

func TestMessageHandlerGetWorkspaceOverlaysLiveAgentRuntime(t *testing.T) {
	gin.SetMode(gin.TestMode)

	agentIdentity := scopeFrontierAgentIdentity()
	messageStore := &fakeMessageStore{workspaceSnapshot: agent.WorkspaceSnapshot{
		Agents: []agent.WorkspaceActor{
			{
				ID:             "agt_0000000000live",
				Name:           "runtime",
				DisplayName:    "Helpful App",
				Status:         "offline",
				Activity:       "offline",
				ActivityDetail: "",
				MachineID:      "mac_live",
			},
		},
	}}
	runtime := &fakeWorkspaceRuntime{
		connectedMachines: map[string]bool{"mac_live": true},
		runningAgents:     map[workspaceRuntimeCall]bool{{MachineID: "mac_live", AgentID: "agt_0000000000live"}: true},
		statuses: map[workspaceRuntimeCall]workspaceRuntimeStatus{
			{MachineID: "mac_live", AgentID: "agt_0000000000live"}: {Activity: "thinking", Detail: "Investigating"},
		},
	}
	handler := &MessageHandler{auth: fakeAgentAuthenticator{agent: agentIdentity}, store: messageStore, runtime: runtime}

	router := gin.New()
	router.GET("/api/v1/workspaces/:workspaceId/agents/:agentId/runtime/workspace", handler.GetWorkspace)
	request := httptest.NewRequest(http.MethodGet, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/workspace?kind=agents", nil)
	setAgentAuthHeader(request)
	response := httptest.NewRecorder()

	router.ServeHTTP(response, request)

	require.Equal(t, http.StatusOK, response.Code)
	require.JSONEq(t, `{
		"channels":[],
		"agents":[{"name":"runtime","displayName":"Helpful App","status":"busy","activity":"thinking","activityDetail":"Investigating","machineId":"mac_live"}],
		"humans":[]
	}`, response.Body.String())
}

func TestMessageHandlerResolveChannelReturnsResolvedTargetKey(t *testing.T) {
	gin.SetMode(gin.TestMode)

	agentIdentity := scopeFrontierAgentIdentity()
	messageStore := &fakeMessageStore{resolvedChannelID: "scp_general"}
	handler := &MessageHandler{auth: fakeAgentAuthenticator{agent: agentIdentity}, store: messageStore}

	router := gin.New()
	router.POST("/api/v1/workspaces/:workspaceId/agents/:agentId/runtime/resolve-channel", handler.ResolveChannel)
	body, err := json.Marshal(dto.AgentResolveChannelRequest{Target: "#general"})
	require.NoError(t, err)
	request := httptest.NewRequest(http.MethodPost, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/resolve-channel", bytes.NewReader(body))
	setAgentAuthHeader(request)
	request.Header.Set("Content-Type", "application/json")
	response := httptest.NewRecorder()

	router.ServeHTTP(response, request)

	require.Equal(t, http.StatusOK, response.Code)
	require.Equal(t, "#general", messageStore.resolvedTarget)
	require.JSONEq(t, `{"scopeId":"scp_general","targetKey":"scp_general"}`, response.Body.String())
}

func TestMessageHandlerCreateUsesNarrowInterfaces(t *testing.T) {
	gin.SetMode(gin.TestMode)

	agentIdentity := scopeFrontierAgentIdentity()
	messageStore := &fakeMessageStore{}
	rewindStore := &fakeRewindStore{}
	fanout := &fakeMessageFanout{}
	handler := &MessageHandler{
		auth:        fakeAgentAuthenticator{agent: agentIdentity},
		store:       messageStore,
		rewindStore: rewindStore,
		fanout:      fanout,
	}

	router := gin.New()
	router.POST("/api/v1/workspaces/:workspaceId/agents/:agentId/runtime/send", handler.Create)

	body, err := json.Marshal(map[string]any{
		"target":        "#general",
		"content":       "hello from test",
		"attachmentIds": []string{"att_1"},
	})
	require.NoError(t, err)
	request := httptest.NewRequest(http.MethodPost, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/send", bytes.NewReader(body))
	setAgentAuthHeader(request)
	request.Header.Set("Content-Type", "application/json")
	response := httptest.NewRecorder()

	router.ServeHTTP(response, request)

	require.Equal(t, http.StatusOK, response.Code)
	require.Equal(t, "#general", messageStore.sentParams.TargetRef)
	require.Equal(t, "hello from test", messageStore.sentParams.Content)
	require.Equal(t, []string{"att_1"}, messageStore.sentParams.AttachmentIDs)
	require.True(t, rewindStore.pruned)
	require.Equal(t, 1, fanout.calls)
	require.Equal(t, "msg_1", fanout.messageID)
	require.Equal(t, "msg_1", fanout.deliveryID)
	require.JSONEq(t, `{"messageId":"msg_1"}`, response.Body.String())
}

func TestMessageHandlerCreateFreshnessCapableReturnsStatefulResult(t *testing.T) {
	gin.SetMode(gin.TestMode)

	agentIdentity := scopeFrontierAgentIdentity()
	messageStore := &fakeMessageStore{}
	rewindStore := &fakeRewindStore{}
	fanout := &fakeMessageFanout{}
	handler := &MessageHandler{
		auth:        fakeAgentAuthenticator{agent: agentIdentity},
		store:       messageStore,
		rewindStore: rewindStore,
		fanout:      fanout,
	}

	router := gin.New()
	router.POST("/api/v1/workspaces/:workspaceId/agents/:agentId/runtime/send", handler.Create)

	body, err := json.Marshal(map[string]any{
		"target":           "#general",
		"content":          "fresh hello",
		"clientMessageId":  "client-fresh-1",
		"teamInvokeTeamId": "team_core",
		"freshness": map[string]any{
			"targetKey":        "scp_general",
			"seenUpToSeq":      41,
			"sourceMessageIds": []string{"msg_seen"},
		},
	})
	require.NoError(t, err)
	request := httptest.NewRequest(http.MethodPost, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/send", bytes.NewReader(body))
	setAgentAuthHeader(request)
	request.Header.Set("Content-Type", "application/json")
	response := httptest.NewRecorder()

	router.ServeHTTP(response, request)

	require.Equal(t, http.StatusOK, response.Code, response.Body.String())
	require.Equal(t, "#general", messageStore.sentParams.TargetRef)
	require.Equal(t, "client-fresh-1", messageStore.sentParams.ClientMessageID)
	require.Equal(t, "team_core", messageStore.sentParams.TeamInvokeTeamID)
	require.Equal(t, message.SendMessageFreshness{TargetKey: "scp_general", SeenUpToSeq: 41, SourceMessageIDs: []string{"msg_seen"}}, messageStore.sentParams.Freshness)
	require.True(t, rewindStore.pruned)
	require.Equal(t, 1, fanout.calls)
	require.JSONEq(t, `{
		"state":"sent",
		"messageId":"msg_1",
		"targetKey":"scp_general",
		"clientMessageId":"client-fresh-1",
		"seenUpToSeq":41,
		"acceptedSeq":42,
		"latestSeq":42
	}`, response.Body.String())
}

func TestMessageHandlerCreateFreshnessHeldSkipsFanout(t *testing.T) {
	gin.SetMode(gin.TestMode)

	agentIdentity := scopeFrontierAgentIdentity()
	messageStore := &fakeMessageStore{sendFreshResult: message.SendMessageResult{
		State:                message.SendStateHeld,
		TargetKey:            "scp_general",
		ClientMessageID:      "client-held-1",
		SeenUpToSeq:          41,
		LatestSeq:            45,
		NeedsRefreshUntilSeq: 45,
	}}
	rewindStore := &fakeRewindStore{}
	fanout := &fakeMessageFanout{}
	handler := &MessageHandler{
		auth:        fakeAgentAuthenticator{agent: agentIdentity},
		store:       messageStore,
		rewindStore: rewindStore,
		fanout:      fanout,
	}

	router := gin.New()
	router.POST("/api/v1/workspaces/:workspaceId/agents/:agentId/runtime/send", handler.Create)

	body, err := json.Marshal(map[string]any{
		"target":          "#general",
		"content":         "held hello",
		"clientMessageId": "client-held-1",
		"freshness": map[string]any{
			"targetKey":   "scp_general",
			"seenUpToSeq": 41,
		},
	})
	require.NoError(t, err)
	request := httptest.NewRequest(http.MethodPost, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/send", bytes.NewReader(body))
	setAgentAuthHeader(request)
	request.Header.Set("Content-Type", "application/json")
	response := httptest.NewRecorder()

	router.ServeHTTP(response, request)

	require.Equal(t, http.StatusOK, response.Code, response.Body.String())
	require.False(t, rewindStore.pruned)
	require.Equal(t, 0, fanout.calls)
	require.JSONEq(t, `{
		"state":"held",
		"targetKey":"scp_general",
		"clientMessageId":"client-held-1",
		"seenUpToSeq":41,
		"latestSeq":45,
		"needsRefreshUntilSeq":45
	}`, response.Body.String())
}

func TestMessageHandlerCreateFreshnessCapableRequiresCompleteContract(t *testing.T) {
	gin.SetMode(gin.TestMode)

	messageStore := &fakeMessageStore{}
	handler := &MessageHandler{
		auth:  fakeAgentAuthenticator{agent: scopeFrontierAgentIdentity()},
		store: messageStore,
	}

	router := gin.New()
	router.POST("/api/v1/workspaces/:workspaceId/agents/:agentId/runtime/send", handler.Create)

	body, err := json.Marshal(map[string]any{
		"target":          "#general",
		"content":         "missing freshness",
		"clientMessageId": "client-missing-freshness",
	})
	require.NoError(t, err)
	request := httptest.NewRequest(http.MethodPost, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/send", bytes.NewReader(body))
	setAgentAuthHeader(request)
	request.Header.Set("Content-Type", "application/json")
	response := httptest.NewRecorder()

	router.ServeHTTP(response, request)

	require.Equal(t, http.StatusBadRequest, response.Code)
	require.Contains(t, response.Body.String(), "freshness_required")
	require.Empty(t, messageStore.sentParams)
}

func TestMessageHandlerCreatePassesRawTaskScopeTarget(t *testing.T) {
	gin.SetMode(gin.TestMode)

	agentIdentity := scopeFrontierAgentIdentity()
	messageStore := &fakeMessageStore{}
	handler := &MessageHandler{
		auth:  fakeAgentAuthenticator{agent: agentIdentity},
		store: messageStore,
	}

	router := gin.New()
	router.POST("/api/v1/workspaces/:workspaceId/agents/:agentId/runtime/send", handler.Create)

	body, err := json.Marshal(map[string]any{
		"target":  "#tsk_abc123",
		"content": "task discussion via send_message",
	})
	require.NoError(t, err)
	request := httptest.NewRequest(http.MethodPost, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/send", bytes.NewReader(body))
	setAgentAuthHeader(request)
	request.Header.Set("Content-Type", "application/json")
	response := httptest.NewRecorder()

	router.ServeHTTP(response, request)

	require.Equal(t, http.StatusOK, response.Code, response.Body.String())
	require.Equal(t, "tsk_abc123", messageStore.sentParams.TaskScopeID)
	require.Equal(t, "task discussion via send_message", messageStore.sentParams.Content)
	require.Empty(t, messageStore.sentParams.TargetRef)
	require.Empty(t, messageStore.sentParams.DMTo)
}

func TestMessageHandlerCreateUsesResolvedFanoutWhenConfigured(t *testing.T) {
	gin.SetMode(gin.TestMode)

	agentIdentity := scopeFrontierAgentIdentity()
	messageStore := &fakeMessageStore{}
	rewindStore := &fakeRewindStore{}
	fanout := &fakeMessageFanout{}
	handler := &MessageHandler{
		auth:        fakeAgentAuthenticator{agent: agentIdentity},
		store:       messageStore,
		rewindStore: rewindStore,
		fanout:      fanout,
	}

	router := gin.New()
	router.POST("/api/v1/workspaces/:workspaceId/agents/:agentId/runtime/send", handler.Create)

	body, err := json.Marshal(map[string]any{
		"target":  "#general",
		"content": "hello from resolved fanout",
	})
	require.NoError(t, err)
	request := httptest.NewRequest(http.MethodPost, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/send", bytes.NewReader(body))
	setAgentAuthHeader(request)
	request.Header.Set("Content-Type", "application/json")
	response := httptest.NewRecorder()

	router.ServeHTTP(response, request)

	require.Equal(t, http.StatusOK, response.Code)
	require.True(t, rewindStore.pruned)
	require.Equal(t, 1, fanout.calls)
	require.Equal(t, "wsp_test", fanout.workspaceID)
	require.Equal(t, "msg_1", fanout.messageID)
	require.Equal(t, "msg_1", fanout.deliveryID)
}

func TestMessageHandlerCreateRejectsInvalidTarget(t *testing.T) {
	gin.SetMode(gin.TestMode)

	agentIdentity := scopeFrontierAgentIdentity()
	messageStore := &fakeMessageStore{}
	rewindStore := &fakeRewindStore{}
	handler := &MessageHandler{
		auth:        fakeAgentAuthenticator{agent: agentIdentity},
		store:       messageStore,
		rewindStore: rewindStore,
	}

	router := gin.New()
	router.POST("/api/v1/workspaces/:workspaceId/agents/:agentId/runtime/send", handler.Create)

	body, err := json.Marshal(map[string]any{
		"target":  "not-a-target",
		"content": "hello from invalid target test",
	})
	require.NoError(t, err)
	request := httptest.NewRequest(http.MethodPost, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/send", bytes.NewReader(body))
	setAgentAuthHeader(request)
	request.Header.Set("Content-Type", "application/json")
	response := httptest.NewRecorder()

	router.ServeHTTP(response, request)

	require.Equal(t, http.StatusBadRequest, response.Code)
	require.Empty(t, messageStore.sentParams)
}

func TestMessageHandlerSearchReturnsUnsupportedWhenProjectionUnavailable(t *testing.T) {
	gin.SetMode(gin.TestMode)

	agentIdentity := scopeFrontierAgentIdentity()
	handler := &MessageHandler{
		auth:  fakeAgentAuthenticator{agent: agentIdentity},
		store: &fakeScopeAPIMessageStore{searchErr: message.ErrMessageSearchUnavailable},
	}

	router := gin.New()
	router.GET("/api/v1/workspaces/:workspaceId/agents/:agentId/runtime/search", handler.Search)
	request := httptest.NewRequest(http.MethodGet, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/search?q=jett", nil)
	setAgentAuthHeader(request)
	response := httptest.NewRecorder()

	router.ServeHTTP(response, request)

	require.Equal(t, http.StatusNotImplemented, response.Code)
	require.Contains(t, response.Body.String(), "message search is unavailable")
}

func TestMessageHandlerCreateDoesNotRequirePerRequestProtocolHeaderWithMinDaemonVersion(t *testing.T) {
	gin.SetMode(gin.TestMode)

	agentIdentity := scopeFrontierAgentIdentity()
	messageStore := &fakeMessageStore{}
	handler := &MessageHandler{
		auth:             fakeAgentAuthenticator{agent: agentIdentity},
		store:            messageStore,
		minDaemonVersion: testMinDaemonVersion,
	}

	router := gin.New()
	router.POST("/api/v1/workspaces/:workspaceId/agents/:agentId/runtime/send", handler.Create)

	body, err := json.Marshal(map[string]any{
		"target":  "#general",
		"content": "old daemon request",
	})
	require.NoError(t, err)
	request := httptest.NewRequest(http.MethodPost, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/send", bytes.NewReader(body))
	request.Header.Set("Authorization", "Bearer test-token")
	request.Header.Set("Content-Type", "application/json")
	response := httptest.NewRecorder()

	router.ServeHTTP(response, request)

	require.Equal(t, http.StatusOK, response.Code)
	require.Equal(t, "#general", messageStore.sentParams.TargetRef)
	require.Equal(t, "old daemon request", messageStore.sentParams.Content)
	require.JSONEq(t, `{"messageId":"msg_1"}`, response.Body.String())
}

func TestMessageHandlerSendAllowsEmptyDaemonVersionWhenMinUnset(t *testing.T) {
	gin.SetMode(gin.TestMode)

	agentIdentity := legacyAgentIdentity()
	messageStore := &fakeMessageStore{}
	handler := &MessageHandler{
		auth:  fakeAgentAuthenticator{agent: agentIdentity},
		store: messageStore,
	}

	router := gin.New()
	router.POST("/api/v1/workspaces/:workspaceId/agents/:agentId/runtime/send", handler.Create)

	body, err := json.Marshal(map[string]any{
		"target":  "#general",
		"content": "min gate disabled",
	})
	require.NoError(t, err)
	request := httptest.NewRequest(http.MethodPost, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/send", bytes.NewReader(body))
	setAgentAuthHeader(request)
	request.Header.Set("Content-Type", "application/json")
	response := httptest.NewRecorder()

	router.ServeHTTP(response, request)

	require.Equal(t, http.StatusOK, response.Code)
	require.Equal(t, "#general", messageStore.sentParams.TargetRef)
	require.Equal(t, "min gate disabled", messageStore.sentParams.Content)
}

func TestMessageHandlerSendRejectsInvalidDaemonVersionBeforeStore(t *testing.T) {
	gin.SetMode(gin.TestMode)

	agentIdentity := legacyAgentIdentity()
	agentIdentity.MachineID = "mac_test"
	agentIdentity.DaemonVersion = "agentteam/not-semver"
	messageStore := &fakeMessageStore{}
	handler := &MessageHandler{
		auth:             fakeAgentAuthenticator{agent: agentIdentity},
		store:            messageStore,
		minDaemonVersion: testMinDaemonVersion,
	}

	router := gin.New()
	router.POST("/api/v1/workspaces/:workspaceId/agents/:agentId/runtime/send", handler.Create)

	body, err := json.Marshal(map[string]any{
		"target":  "#general",
		"content": "invalid daemon",
	})
	require.NoError(t, err)
	request := httptest.NewRequest(http.MethodPost, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/send", bytes.NewReader(body))
	setAgentAuthHeader(request)
	request.Header.Set("Content-Type", "application/json")
	response := httptest.NewRecorder()

	router.ServeHTTP(response, request)

	require.Equal(t, http.StatusConflict, response.Code)
	require.Empty(t, messageStore.sentParams)
	require.JSONEq(t, `{"code":"upgrade_required","error":"This machine's agentteam daemon is too old for AgentTeam's message APIs. Upgrade agentteam daemon and try again."}`, response.Body.String())
}

func TestMessageHandlerSendRequiresMinDaemonVersionBeforeStore(t *testing.T) {
	gin.SetMode(gin.TestMode)

	agentIdentity := oldDaemonAgentIdentity()
	messageStore := &fakeMessageStore{}
	handler := &MessageHandler{
		auth:             fakeAgentAuthenticator{agent: agentIdentity},
		store:            messageStore,
		minDaemonVersion: testMinDaemonVersion,
	}

	router := gin.New()
	router.POST("/api/v1/workspaces/:workspaceId/agents/:agentId/runtime/send", handler.Create)

	body, err := json.Marshal(map[string]any{
		"target":  "#general",
		"content": "old daemon request",
	})
	require.NoError(t, err)
	request := httptest.NewRequest(http.MethodPost, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/send", bytes.NewReader(body))
	setAgentAuthHeader(request)
	request.Header.Set("Content-Type", "application/json")
	response := httptest.NewRecorder()

	router.ServeHTTP(response, request)

	require.Equal(t, http.StatusConflict, response.Code)
	require.Empty(t, messageStore.sentParams)
	require.JSONEq(t, `{"code":"upgrade_required","error":"This machine's agentteam daemon is too old for AgentTeam's message APIs. Upgrade agentteam daemon and try again."}`, response.Body.String())
}

func TestMessageHandlerRawTaskScopeSendRequiresMinDaemonVersionBeforeStore(t *testing.T) {
	gin.SetMode(gin.TestMode)

	agentIdentity := oldDaemonAgentIdentity()
	messageStore := &fakeMessageStore{}
	handler := &MessageHandler{
		auth:             fakeAgentAuthenticator{agent: agentIdentity},
		store:            messageStore,
		minDaemonVersion: testMinDaemonVersion,
	}

	router := gin.New()
	router.POST("/api/v1/workspaces/:workspaceId/agents/:agentId/runtime/send", handler.Create)

	body, err := json.Marshal(map[string]any{
		"target":  "#tsk_abc123",
		"content": "old daemon raw task-scope request",
	})
	require.NoError(t, err)
	request := httptest.NewRequest(http.MethodPost, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/send", bytes.NewReader(body))
	setAgentAuthHeader(request)
	request.Header.Set("Content-Type", "application/json")
	response := httptest.NewRecorder()

	router.ServeHTTP(response, request)

	require.Equal(t, http.StatusConflict, response.Code)
	require.Empty(t, messageStore.sentParams)
	require.JSONEq(t, `{"code":"upgrade_required","error":"This machine's agentteam daemon is too old for AgentTeam's message APIs. Upgrade agentteam daemon and try again."}`, response.Body.String())
}

func TestMessageHandlerHistoryRequiresMinDaemonVersionBeforeStore(t *testing.T) {
	gin.SetMode(gin.TestMode)

	agentIdentity := legacyAgentIdentity()
	messageStore := &fakeScopeAPIMessageStore{}
	handler := &MessageHandler{
		auth:             fakeAgentAuthenticator{agent: agentIdentity},
		store:            messageStore,
		minDaemonVersion: testMinDaemonVersion,
	}

	router := gin.New()
	router.GET("/api/v1/workspaces/:workspaceId/agents/:agentId/runtime/history", handler.ListHistory)
	request := httptest.NewRequest(http.MethodGet, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/history?scope_id=scp_1&before_seq=11", nil)
	setAgentAuthHeader(request)
	response := httptest.NewRecorder()

	router.ServeHTTP(response, request)

	require.Equal(t, http.StatusConflict, response.Code)
	require.Empty(t, messageStore.historyAgent)
	require.JSONEq(t, `{"code":"upgrade_required","error":"This machine's agentteam daemon is too old for AgentTeam's message APIs. Upgrade agentteam daemon and try again."}`, response.Body.String())
}

func TestMessageHandlerSearchRequiresMinDaemonVersionBeforeStore(t *testing.T) {
	gin.SetMode(gin.TestMode)

	agentIdentity := legacyAgentIdentity()
	messageStore := &fakeScopeAPIMessageStore{}
	handler := &MessageHandler{
		auth:             fakeAgentAuthenticator{agent: agentIdentity},
		store:            messageStore,
		minDaemonVersion: testMinDaemonVersion,
	}

	router := gin.New()
	router.GET("/api/v1/workspaces/:workspaceId/agents/:agentId/runtime/search", handler.Search)
	request := httptest.NewRequest(http.MethodGet, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/search?scope_id=scp_1&q=needle&before_seq=11", nil)
	setAgentAuthHeader(request)
	response := httptest.NewRecorder()

	router.ServeHTTP(response, request)

	require.Equal(t, http.StatusConflict, response.Code)
	require.Empty(t, messageStore.searchAgent)
	require.JSONEq(t, `{"code":"upgrade_required","error":"This machine's agentteam daemon is too old for AgentTeam's message APIs. Upgrade agentteam daemon and try again."}`, response.Body.String())
}

func TestMessageHandlerHistoryDoesNotRequirePerRequestProtocolHeaderAndSearchRejectsLegacyGlobalCursor(t *testing.T) {
	gin.SetMode(gin.TestMode)

	agentIdentity := scopeFrontierAgentIdentity()
	messageStore := &fakeScopeAPIMessageStore{}
	handler := &MessageHandler{
		auth:  fakeAgentAuthenticator{agent: agentIdentity},
		store: messageStore,
	}

	router := gin.New()
	router.GET("/api/v1/workspaces/:workspaceId/agents/:agentId/runtime/history", handler.ListHistory)
	router.GET("/api/v1/workspaces/:workspaceId/agents/:agentId/runtime/search", handler.Search)

	historyRequest := httptest.NewRequest(http.MethodGet, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/history?scope_id=scp_1&before_seq=11", nil)
	historyRequest.Header.Set("Authorization", "Bearer test-token")
	historyResponse := httptest.NewRecorder()
	router.ServeHTTP(historyResponse, historyRequest)
	require.Equal(t, http.StatusOK, historyResponse.Code)
	require.Equal(t, agentIdentity, messageStore.historyAgent)
	require.Equal(t, "scp_1", messageStore.historyRequest.ScopeID)
	require.Equal(t, int64(11), messageStore.historyRequest.BeforeSeq)

	searchRequest := httptest.NewRequest(http.MethodGet, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/search?scope_id=scp_1&q=needle&before_global_seq=99", nil)
	searchRequest.Header.Set("Authorization", "Bearer test-token")
	searchResponse := httptest.NewRecorder()
	router.ServeHTTP(searchResponse, searchRequest)
	require.Equal(t, http.StatusConflict, searchResponse.Code)
	require.Empty(t, messageStore.searchAgent)
	require.JSONEq(t, `{"code":"upgrade_required","error":"This machine's agentteam daemon is too old for AgentTeam's message APIs. Upgrade agentteam daemon and try again."}`, searchResponse.Body.String())
}

func TestMessageHandlerHistoryRejectsLegacyBeforeCursorUnderScopeAPI(t *testing.T) {
	gin.SetMode(gin.TestMode)

	agentIdentity := scopeFrontierAgentIdentity()
	messageStore := &fakeScopeAPIMessageStore{}
	handler := &MessageHandler{
		auth:  fakeAgentAuthenticator{agent: agentIdentity},
		store: messageStore,
	}

	router := gin.New()
	router.GET("/api/v1/workspaces/:workspaceId/agents/:agentId/runtime/history", handler.ListHistory)
	request := httptest.NewRequest(http.MethodGet, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/history?scope_id=scp_1&before=11", nil)
	setAgentAuthHeader(request)
	response := httptest.NewRecorder()

	router.ServeHTTP(response, request)

	require.Equal(t, http.StatusConflict, response.Code)
	require.Empty(t, messageStore.historyAgent)
	require.JSONEq(t, `{"code":"upgrade_required","error":"This machine's agentteam daemon is too old for AgentTeam's message APIs. Upgrade agentteam daemon and try again."}`, response.Body.String())
}

func TestMessageHandlerSearchRejectsLegacyBeforeCursorUnderScopeAPI(t *testing.T) {
	gin.SetMode(gin.TestMode)

	agentIdentity := scopeFrontierAgentIdentity()
	messageStore := &fakeScopeAPIMessageStore{}
	handler := &MessageHandler{
		auth:  fakeAgentAuthenticator{agent: agentIdentity},
		store: messageStore,
	}

	router := gin.New()
	router.GET("/api/v1/workspaces/:workspaceId/agents/:agentId/runtime/search", handler.Search)
	request := httptest.NewRequest(http.MethodGet, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/search?scope_id=scp_1&q=needle&before=11", nil)
	setAgentAuthHeader(request)
	response := httptest.NewRecorder()

	router.ServeHTTP(response, request)

	require.Equal(t, http.StatusConflict, response.Code)
	require.Empty(t, messageStore.searchAgent)
	require.JSONEq(t, `{"code":"upgrade_required","error":"This machine's agentteam daemon is too old for AgentTeam's message APIs. Upgrade agentteam daemon and try again."}`, response.Body.String())
}

func TestMessageHandlerSearchRejectsLegacyGlobalCursorBeforeStore(t *testing.T) {
	gin.SetMode(gin.TestMode)

	agentIdentity := scopeFrontierAgentIdentity()
	messageStore := &fakeScopeAPIMessageStore{}
	handler := &MessageHandler{
		auth:  fakeAgentAuthenticator{agent: agentIdentity},
		store: messageStore,
	}

	router := gin.New()
	router.GET("/api/v1/workspaces/:workspaceId/agents/:agentId/runtime/search", handler.Search)
	request := httptest.NewRequest(http.MethodGet, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/search?scope_id=scp_1&q=needle&before_global_seq=99", nil)
	setAgentAuthHeader(request)
	response := httptest.NewRecorder()

	router.ServeHTTP(response, request)

	require.Equal(t, http.StatusConflict, response.Code)
	require.Empty(t, messageStore.searchAgent)
	require.JSONEq(t, `{"code":"upgrade_required","error":"This machine's agentteam daemon is too old for AgentTeam's message APIs. Upgrade agentteam daemon and try again."}`, response.Body.String())
}

func TestMessageHandlerSearchResolvesChannelForScopeCursor(t *testing.T) {
	gin.SetMode(gin.TestMode)

	agentIdentity := scopeFrontierAgentIdentity()
	messageStore := &fakeScopeAPIMessageStore{
		fakeScopeReceiveMessageStore: fakeScopeReceiveMessageStore{
			fakeMessageStore: fakeMessageStore{resolvedChannelID: "scp_general"},
		},
	}
	handler := &MessageHandler{
		auth:  fakeAgentAuthenticator{agent: agentIdentity},
		store: messageStore,
	}

	router := gin.New()
	router.GET("/api/v1/workspaces/:workspaceId/agents/:agentId/runtime/search", handler.Search)
	request := httptest.NewRequest(http.MethodGet, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/search?channel=%23general&q=needle&before_seq=99", nil)
	setAgentAuthHeader(request)
	response := httptest.NewRecorder()

	router.ServeHTTP(response, request)

	require.Equal(t, http.StatusOK, response.Code)
	require.Empty(t, messageStore.resolvedTarget)
	require.Equal(t, "#general", messageStore.readableTarget)
	require.Equal(t, "scp_general", messageStore.searchRequest.ScopeID)
	require.Equal(t, int64(99), messageStore.searchRequest.BeforeSeq)
}

func TestMessageHandlerSearchResolvesRawTaskScopeTarget(t *testing.T) {
	gin.SetMode(gin.TestMode)

	agentIdentity := scopeFrontierAgentIdentity()
	messageStore := &fakeScopeAPIMessageStore{
		fakeScopeReceiveMessageStore: fakeScopeReceiveMessageStore{
			fakeMessageStore: fakeMessageStore{resolvedChannelID: "tsk_task_scope"},
		},
	}
	handler := &MessageHandler{
		auth:  fakeAgentAuthenticator{agent: agentIdentity},
		store: messageStore,
	}

	router := gin.New()
	router.GET("/api/v1/workspaces/:workspaceId/agents/:agentId/runtime/search", handler.Search)
	request := httptest.NewRequest(http.MethodGet, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/search?channel=%23tsk_task_scope&q=needle", nil)
	setAgentAuthHeader(request)
	response := httptest.NewRecorder()

	router.ServeHTTP(response, request)

	require.Equal(t, http.StatusOK, response.Code)
	require.Empty(t, messageStore.resolvedTarget)
	require.Equal(t, "#tsk_task_scope", messageStore.readableTarget)
	require.Equal(t, "tsk_task_scope", messageStore.searchRequest.ScopeID)
}

func TestMessageHandlerHistoryResolvesChannelThroughReadOnlyTargetResolver(t *testing.T) {
	gin.SetMode(gin.TestMode)

	agentIdentity := scopeFrontierAgentIdentity()
	messageStore := &fakeScopeAPIMessageStore{
		fakeScopeReceiveMessageStore: fakeScopeReceiveMessageStore{
			fakeMessageStore: fakeMessageStore{resolvedChannelID: "scp_thread"},
		},
	}
	handler := &MessageHandler{
		auth:  fakeAgentAuthenticator{agent: agentIdentity},
		store: messageStore,
	}

	router := gin.New()
	router.GET("/api/v1/workspaces/:workspaceId/agents/:agentId/runtime/history", handler.ListHistory)
	request := httptest.NewRequest(http.MethodGet, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/history?channel=%23general%3Aabc12345&before_seq=77", nil)
	setAgentAuthHeader(request)
	response := httptest.NewRecorder()

	router.ServeHTTP(response, request)

	require.Equal(t, http.StatusOK, response.Code)
	require.Empty(t, messageStore.resolvedTarget)
	require.Equal(t, "#general:abc12345", messageStore.readableTarget)
	require.Equal(t, "scp_thread", messageStore.historyRequest.ScopeID)
	require.Equal(t, int64(77), messageStore.historyRequest.BeforeSeq)
}

func TestMessageHandlerScopeHistorySearchReadCheckAndMembersUseScopeAPIStore(t *testing.T) {
	gin.SetMode(gin.TestMode)

	agentIdentity := scopeFrontierAgentIdentity()
	messageStore := &fakeScopeAPIMessageStore{
		historyPage: message.ScopeAPIHistoryPage{
			TargetKey:        "scp_1",
			CoversThroughSeq: 7,
			Messages: []message.HistoryMessage{{
				ID:      "msg_history",
				Seq:     7,
				Content: "scope history",
				Source: message.ContributionSource{
					Kind:    message.ContributionSourceKindScheduleRun,
					RefType: message.ContributionSourceRefTypeAutomationRun,
					RefID:   "arun_history",
					RunID:   "arun_history",
					Label:   "daily history",
				},
			}},
			NextCursor:               6,
			HasMore:                  true,
			CandidateBudgetExhausted: true,
			LastReadSeq:              5,
		},
		searchPage: message.ScopeAPISearchPage{
			Messages: []message.HistoryMessage{{
				ID:      "msg_search",
				Seq:     8,
				Content: "scope search",
				Source: message.ContributionSource{
					Kind:      message.ContributionSourceKindProviderEvent,
					RefType:   message.ContributionSourceRefTypeServiceEventDelivery,
					RefID:     "svced_search",
					RunID:     "svced_search",
					EventID:   "evt_search",
					EventType: "gitea_pull_request.synchronized",
					Label:     "Gitea PR watch",
				},
			}},
			NextCursor:               70,
			HasMore:                  true,
			CandidateBudgetExhausted: true,
		},
		readResult: message.ScopeAPIReadCheckResult{
			Allowed: true,
			Reason:  message.ScopeVisibilityReasonMembership,
			Message: &message.HistoryMessage{
				ID:      "msg_read",
				Seq:     9,
				Content: "readable",
				Source: message.ContributionSource{
					Kind:    message.ContributionSourceKindScheduleRun,
					RefType: message.ContributionSourceRefTypeAutomationRun,
					RefID:   "arun_read",
					RunID:   "arun_read",
					Label:   "read schedule",
				},
			},
		},
		memberRows: []message.ScopeMemberDisplayRow{{
			MemberID:      "000000public",
			MemberType:    "human",
			PublicID:      "usr_00000000public",
			DisplayName:   "Public Member",
			Visibility:    "public",
			OwnerMemberID: "",
		}, {
			MemberID:      "00000private",
			MemberType:    "agent",
			PublicID:      "agt_0000000private",
			DisplayName:   "Private Agent",
			Visibility:    "private",
			OwnerMemberID: "0000000agent",
		}},
	}
	handler := &MessageHandler{
		auth:  fakeAgentAuthenticator{agent: agentIdentity},
		store: messageStore,
	}

	router := gin.New()
	router.GET("/api/v1/workspaces/:workspaceId/agents/:agentId/runtime/history", handler.ListHistory)
	router.GET("/api/v1/workspaces/:workspaceId/agents/:agentId/runtime/search", handler.Search)
	router.GET("/api/v1/workspaces/:workspaceId/agents/:agentId/runtime/messages/:messageId/read-check", handler.ReadCheck)
	router.GET("/api/v1/workspaces/:workspaceId/agents/:agentId/runtime/scopes/:scopeId/members", handler.ListScopeMembers)

	historyRequest := httptest.NewRequest(http.MethodGet, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/history?scope_id=scp_1&before_seq=11&limit=2", nil)
	setAgentAuthHeader(historyRequest)
	historyResponse := httptest.NewRecorder()
	router.ServeHTTP(historyResponse, historyRequest)
	require.Equal(t, http.StatusOK, historyResponse.Code)
	require.Equal(t, agentIdentity, messageStore.historyAgent)
	require.Equal(t, "scp_1", messageStore.historyRequest.ScopeID)
	require.Equal(t, int64(11), messageStore.historyRequest.BeforeSeq)
	require.Equal(t, 2, messageStore.historyRequest.Limit)
	var historyBody dto.AgentHistoryResponse
	require.NoError(t, json.Unmarshal(historyResponse.Body.Bytes(), &historyBody))
	require.Len(t, historyBody.Messages, 1)
	require.Equal(t, "scp_1", historyBody.TargetKey)
	require.Equal(t, int64(7), historyBody.CoversThroughSeq)
	require.Equal(t, "msg_history", historyBody.Messages[0].ID)
	require.Equal(t, int64(7), historyBody.Messages[0].Seq)
	require.NotNil(t, historyBody.Messages[0].Source)
	require.Equal(t, message.ContributionSourceRefTypeAutomationRun, historyBody.Messages[0].Source.RefType)
	require.Equal(t, "arun_history", historyBody.Messages[0].Source.RefID)
	require.Equal(t, int64(6), historyBody.NextCursor)
	require.Equal(t, int64(5), historyBody.LastReadSeq)
	require.True(t, historyBody.HasMore)
	require.True(t, historyBody.CandidateBudgetExhausted)

	searchRequest := httptest.NewRequest(http.MethodGet, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/search?scope_id=scp_1&q=needle&before_seq=99&limit=3", nil)
	setAgentAuthHeader(searchRequest)
	searchResponse := httptest.NewRecorder()
	router.ServeHTTP(searchResponse, searchRequest)
	require.Equal(t, http.StatusOK, searchResponse.Code)
	require.Equal(t, agentIdentity, messageStore.searchAgent)
	require.Equal(t, "scp_1", messageStore.searchRequest.ScopeID)
	require.Equal(t, "needle", messageStore.searchRequest.Query)
	require.Equal(t, int64(99), messageStore.searchRequest.BeforeSeq)
	require.Equal(t, 3, messageStore.searchRequest.Limit)
	var searchBody dto.AgentSearchMessagesResponse
	require.NoError(t, json.Unmarshal(searchResponse.Body.Bytes(), &searchBody))
	require.Len(t, searchBody.Messages, 1)
	require.Equal(t, "msg_search", searchBody.Messages[0].ID)
	require.Equal(t, int64(8), searchBody.Messages[0].Seq)
	require.NotNil(t, searchBody.Messages[0].Source)
	require.Equal(t, message.ContributionSourceRefTypeServiceEventDelivery, searchBody.Messages[0].Source.RefType)
	require.Equal(t, "svced_search", searchBody.Messages[0].Source.RefID)
	require.Equal(t, "evt_search", searchBody.Messages[0].Source.EventID)
	require.Equal(t, int64(70), searchBody.NextCursor)
	require.True(t, searchBody.HasMore)
	require.True(t, searchBody.CandidateBudgetExhausted)

	readRequest := httptest.NewRequest(http.MethodGet, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/messages/msg_read/read-check?kind=attachment", nil)
	setAgentAuthHeader(readRequest)
	readResponse := httptest.NewRecorder()
	router.ServeHTTP(readResponse, readRequest)
	require.Equal(t, http.StatusOK, readResponse.Code)
	require.Equal(t, agentIdentity, messageStore.readAgent)
	require.Equal(t, "msg_read", messageStore.readRequest.MessageID)
	require.Equal(t, "attachment", messageStore.readRequest.RequestKind)
	var readBody dto.AgentReadCheckResponse
	require.NoError(t, json.Unmarshal(readResponse.Body.Bytes(), &readBody))
	require.True(t, readBody.Allowed)
	require.Equal(t, message.ScopeVisibilityReasonMembership, readBody.Reason)
	require.NotNil(t, readBody.Message)
	require.Equal(t, "msg_read", readBody.Message.ID)
	require.NotNil(t, readBody.Message.Source)
	require.Equal(t, message.ContributionSourceRefTypeAutomationRun, readBody.Message.Source.RefType)
	require.Equal(t, "arun_read", readBody.Message.Source.RefID)

	membersRequest := httptest.NewRequest(http.MethodGet, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/scopes/scp_1/members", nil)
	setAgentAuthHeader(membersRequest)
	membersResponse := httptest.NewRecorder()
	router.ServeHTTP(membersResponse, membersRequest)
	require.Equal(t, http.StatusOK, membersResponse.Code)
	require.Equal(t, agentIdentity, messageStore.memberAgent)
	require.Equal(t, "scp_1", messageStore.memberScopeID)
	var membersBody dto.AgentScopeMembersResponse
	require.NoError(t, json.Unmarshal(membersResponse.Body.Bytes(), &membersBody))
	require.Len(t, membersBody.Members, 2)
	require.Equal(t, "00000private", membersBody.Members[1].MemberID)
	require.Equal(t, "private", membersBody.Members[1].Visibility)
}

func TestMessageHandlerHistorySearchIgnoreDeprecatedScopedCursorAlias(t *testing.T) {
	gin.SetMode(gin.TestMode)

	agentIdentity := scopeFrontierAgentIdentity()
	messageStore := &fakeScopeAPIMessageStore{}
	handler := &MessageHandler{
		auth:  fakeAgentAuthenticator{agent: agentIdentity},
		store: messageStore,
	}

	router := gin.New()
	router.GET("/api/v1/workspaces/:workspaceId/agents/:agentId/runtime/history", handler.ListHistory)
	router.GET("/api/v1/workspaces/:workspaceId/agents/:agentId/runtime/search", handler.Search)

	historyRequest := httptest.NewRequest(http.MethodGet, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/history?"+deprecatedScopedCursorQueryAliasForNegativeTest()+"=scp_legacy&before_seq=11", nil)
	setAgentAuthHeader(historyRequest)
	historyResponse := httptest.NewRecorder()
	router.ServeHTTP(historyResponse, historyRequest)

	require.Equal(t, http.StatusBadRequest, historyResponse.Code)
	require.Empty(t, messageStore.historyRequest.ScopeID)
	require.Contains(t, historyResponse.Body.String(), "channel is required")

	searchRequest := httptest.NewRequest(http.MethodGet, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/search?"+deprecatedScopedCursorQueryAliasForNegativeTest()+"=scp_legacy&q=needle&before_seq=99", nil)
	setAgentAuthHeader(searchRequest)
	searchResponse := httptest.NewRecorder()
	router.ServeHTTP(searchResponse, searchRequest)

	require.Equal(t, http.StatusBadRequest, searchResponse.Code)
	require.Empty(t, messageStore.searchRequest.ScopeID)
	require.Contains(t, searchResponse.Body.String(), "scope_id is required with before_seq")

	historyWithScopeRequest := httptest.NewRequest(http.MethodGet, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/history?scope_id=scp_current&"+deprecatedScopedCursorQueryAliasForNegativeTest()+"=scp_legacy&before_seq=11", nil)
	setAgentAuthHeader(historyWithScopeRequest)
	historyWithScopeResponse := httptest.NewRecorder()
	router.ServeHTTP(historyWithScopeResponse, historyWithScopeRequest)

	require.Equal(t, http.StatusOK, historyWithScopeResponse.Code)
	require.Equal(t, "scp_current", messageStore.historyRequest.ScopeID)

	searchWithScopeRequest := httptest.NewRequest(http.MethodGet, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/search?scope_id=scp_current&"+deprecatedScopedCursorQueryAliasForNegativeTest()+"=scp_legacy&q=needle&before_seq=99", nil)
	setAgentAuthHeader(searchWithScopeRequest)
	searchWithScopeResponse := httptest.NewRecorder()
	router.ServeHTTP(searchWithScopeResponse, searchWithScopeRequest)

	require.Equal(t, http.StatusOK, searchWithScopeResponse.Code)
	require.Equal(t, "scp_current", messageStore.searchRequest.ScopeID)
}
