package channel

import (
	"context"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

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

	"github.com/tidbcloud/AgentTeam/internal/api"
	"github.com/tidbcloud/AgentTeam/internal/store/agent"
	"github.com/tidbcloud/AgentTeam/internal/store/app"
	"github.com/tidbcloud/AgentTeam/internal/store/channel"
	"github.com/tidbcloud/AgentTeam/internal/store/identity"
	"github.com/tidbcloud/AgentTeam/internal/store/message"
	"github.com/tidbcloud/AgentTeam/internal/store/model"
	"github.com/tidbcloud/AgentTeam/internal/store/workspace"
	"github.com/tidbcloud/AgentTeam/internal/unread"
)

type channelHandlerTestStore struct {
	listChannelsUserID      string
	listChannelsWorkspaceID string
	created                 struct {
		userID      string
		workspaceID string
		name        string
		description string
		visibility  string
	}
	updated struct {
		userID      string
		workspaceID string
		channelID   string
		name        *string
		description *string
		visibility  *string
	}
	deleted struct {
		userID      string
		workspaceID string
		channelID   string
	}
	shareCreated struct {
		userID          string
		workspaceID     string
		sourceChannelID string
		targetChannelID string
	}
	shareShared struct {
		userID          string
		workspaceID     string
		messageID       string
		targetChannelID string
	}
	shareLoaded struct {
		userID      string
		workspaceID string
		messageID   string
	}
	shareJoined struct {
		userID      string
		workspaceID string
		messageID   string
	}
	shareRevoked struct {
		userID      string
		workspaceID string
		messageID   string
	}
	discoveredList struct {
		userID      string
		workspaceID string
	}
	discoveredGet struct {
		userID      string
		workspaceID string
		channelID   string
	}
	joined struct {
		userID      string
		workspaceID string
		channelID   string
	}
	left struct {
		userID      string
		workspaceID string
		channelID   string
	}
	archived struct {
		userID      string
		workspaceID string
		channelID   string
	}
	unarchived struct {
		userID      string
		workspaceID string
		channelID   string
	}
	transferred struct {
		userID      string
		workspaceID string
		channelID   string
		ownerID     string
	}
	dismissed struct {
		userID      string
		workspaceID string
		channelID   string
		seq         int64
	}
	membersForUser struct {
		userID      string
		workspaceID string
		channelID   string
	}
	agentTaskSummaries struct {
		userID      string
		workspaceID string
		channelID   string
	}
	agentTaskSummaryResult []message.ChannelAgentTaskSummary
	addableAgents          struct {
		userID      string
		workspaceID string
		channelID   string
		options     agent.AgentDirectoryOptions
	}
	addablePeople struct {
		userID      string
		workspaceID string
		channelID   string
		options     app.ChannelAddablePeopleOptions
	}
	addedAgent struct {
		userID      string
		workspaceID string
		channelID   string
		agentID     string
	}
	addedService struct {
		userID          string
		workspaceID     string
		channelID       string
		serviceMemberID string
	}
	addedHuman struct {
		userID      string
		workspaceID string
		channelID   string
		humanUserID string
	}
	removedAgent struct {
		userID      string
		workspaceID string
		channelID   string
		agentID     string
	}
	removedService struct {
		userID          string
		workspaceID     string
		channelID       string
		serviceMemberID string
	}
	removedHuman struct {
		userID      string
		workspaceID string
		channelID   string
		humanUserID string
	}
	privateAgent struct {
		userID      string
		workspaceID string
		channelID   string
		agentID     string
	}
	updatedAgentVisibility struct {
		userID      string
		workspaceID string
		channelID   string
		agentID     string
		visibility  string
	}
	batchAdded struct {
		userID      string
		workspaceID string
		channelID   string
		members     []message.ChannelMemberAddRequest
	}
	dmList struct {
		userID      string
		workspaceID string
		limit       int
	}
	dmGet struct {
		userID      string
		workspaceID string
		channelID   string
	}
	dmAgent struct {
		userID      string
		workspaceID string
		agentID     string
	}
	dmUser struct {
		userID      string
		workspaceID string
		peerUserID  string
	}
	dmHidden struct {
		userID      string
		workspaceID string
		channelID   string
	}
	listRoomsChannelID string
	listRooms          []string
	listRoomsErr       error
	memberEventResult  message.ChannelMemberEventResult
}

type fakeChannelUnreadReader struct {
	states                           map[string]unread.ScopeUnreadState
	workspaceHasUnread               bool
	workspaceUnreadConversationCount int64
	scopeStateCalls                  [][]string
	invalidated                      []unread.ScopeInvalidated
}

func (fake *fakeChannelUnreadReader) ScopeStates(workspaceID string, memberID string, scopeIDs []string) map[string]unread.ScopeUnreadState {
	if fake == nil {
		return map[string]unread.ScopeUnreadState{}
	}
	fake.scopeStateCalls = append(fake.scopeStateCalls, append([]string(nil), scopeIDs...))
	results := make(map[string]unread.ScopeUnreadState)
	if len(scopeIDs) == 0 {
		for scopeID, state := range fake.states {
			if state.WorkspaceID == "" {
				state.WorkspaceID = workspaceID
			}
			if state.MemberID == "" {
				state.MemberID = memberID
			}
			if state.ScopeID == "" {
				state.ScopeID = scopeID
			}
			results[scopeID] = state
		}
		return results
	}
	for _, scopeID := range scopeIDs {
		if state, ok := fake.states[scopeID]; ok {
			if state.WorkspaceID == "" {
				state.WorkspaceID = workspaceID
			}
			if state.MemberID == "" {
				state.MemberID = memberID
			}
			if state.ScopeID == "" {
				state.ScopeID = scopeID
			}
			results[scopeID] = state
		}
	}
	return results
}

func (fake *fakeChannelUnreadReader) WorkspaceHasUnread(string, string) bool {
	if fake == nil {
		return false
	}
	if fake.workspaceHasUnread {
		return true
	}
	for _, state := range fake.states {
		if state.HasUnread() {
			return true
		}
	}
	return false
}

func (fake *fakeChannelUnreadReader) WorkspaceUnreadConversationCount(string, string) int64 {
	if fake == nil {
		return 0
	}
	return fake.workspaceUnreadConversationCount
}

func (fake *fakeChannelUnreadReader) ApplyScopeInvalidated(event unread.ScopeInvalidated) error {
	if fake == nil {
		return nil
	}
	fake.invalidated = append(fake.invalidated, event)
	return nil
}

func (s *channelHandlerTestStore) ListChannelsForUser(_ context.Context, userID string, workspaceID string) ([]message.ChannelSummary, error) {
	s.listChannelsUserID = userID
	s.listChannelsWorkspaceID = workspaceID
	return []message.ChannelSummary{{ChannelID: "chn_1", Slug: "general", Description: "General", Visibility: "private", Joined: true, CanInvite: true, CanManage: true}}, nil
}

func (s *channelHandlerTestStore) CreateChannelForUser(_ context.Context, userID string, workspaceID string, name string) (message.ChannelSummary, error) {
	s.created.userID = userID
	s.created.workspaceID = workspaceID
	s.created.name = name
	return message.ChannelSummary{ChannelID: "chn_new", Slug: name, Description: "", Visibility: "private", Joined: true}, nil
}

func (s *channelHandlerTestStore) UpdateChannelForUser(_ context.Context, userID string, workspaceID string, channelID string, name *string, description *string, visibility *string) (message.ChannelSummary, error) {
	s.updated.userID = userID
	s.updated.workspaceID = workspaceID
	s.updated.channelID = channelID
	s.updated.name = name
	s.updated.description = description
	s.updated.visibility = visibility
	return message.ChannelSummary{ChannelID: channelID, Slug: derefString(name), Description: derefString(description), Visibility: derefString(visibility), Joined: true}, nil
}

func (s *channelHandlerTestStore) DeleteChannelOrDMForUser(_ context.Context, userID string, workspaceID string, channelID string) error {
	s.deleted.userID = userID
	s.deleted.workspaceID = workspaceID
	s.deleted.channelID = channelID
	return nil
}

func (s *channelHandlerTestStore) CreateChannelShareCardForUser(_ context.Context, user model.User, workspaceID string, sourceChannelID string, targetChannelID string) (message.AppMessage, message.MessageDelivery, error) {
	s.shareCreated.userID = user.ID
	s.shareCreated.workspaceID = workspaceID
	s.shareCreated.sourceChannelID = sourceChannelID
	s.shareCreated.targetChannelID = targetChannelID
	return message.AppMessage{ID: "msg_share", ChannelID: targetChannelID, MessageType: message.MessageTypeChannelShareCard}, message.MessageDelivery{MessageID: "msg_share"}, nil
}

func (s *channelHandlerTestStore) ShareChannelShareCardForUser(_ context.Context, user model.User, workspaceID string, messageID string, targetChannelID string) (message.AppMessage, message.MessageDelivery, error) {
	s.shareShared.userID = user.ID
	s.shareShared.workspaceID = workspaceID
	s.shareShared.messageID = messageID
	s.shareShared.targetChannelID = targetChannelID
	return message.AppMessage{ID: "msg_share_again", ChannelID: targetChannelID, MessageType: message.MessageTypeChannelShareCard}, message.MessageDelivery{MessageID: "msg_share_again"}, nil
}

func (s *channelHandlerTestStore) LoadChannelShareCardForUser(_ context.Context, userID string, workspaceID string, messageID string) (message.ChannelShareCardView, error) {
	s.shareLoaded.userID = userID
	s.shareLoaded.workspaceID = workspaceID
	s.shareLoaded.messageID = messageID
	return message.ChannelShareCardView{MessageID: messageID, CardID: "csc_1", ChannelID: "chn_private", Status: "joinable", CanJoin: true}, nil
}

func (s *channelHandlerTestStore) JoinChannelShareCardForUser(_ context.Context, userID string, workspaceID string, messageID string) (message.ChannelSummary, error) {
	s.shareJoined.userID = userID
	s.shareJoined.workspaceID = workspaceID
	s.shareJoined.messageID = messageID
	return message.ChannelSummary{ChannelID: "chn_private", Slug: "private", Joined: true, CanInvite: true}, nil
}

func (s *channelHandlerTestStore) RevokeChannelShareCardForUser(_ context.Context, userID string, workspaceID string, messageID string) (message.ChannelShareCardView, error) {
	s.shareRevoked.userID = userID
	s.shareRevoked.workspaceID = workspaceID
	s.shareRevoked.messageID = messageID
	return message.ChannelShareCardView{MessageID: messageID, CardID: "csc_1", ChannelID: "chn_private", Status: "revoked", Revoked: true}, nil
}

func (s *channelHandlerTestStore) CreateThreadShareCardForUser(_ context.Context, _ model.User, _ string, _ string, targetScopeID string) (message.AppMessage, message.MessageDelivery, error) {
	return message.AppMessage{ID: "msg_thread_share", ChannelID: targetScopeID, MessageType: message.MessageTypeThreadShareCard}, message.MessageDelivery{MessageID: "msg_thread_share"}, nil
}

func (s *channelHandlerTestStore) CreateReviewFeedbackForUser(_ context.Context, _ model.User, _ string, rc agent.ReviewContext, params message.ReviewFeedbackBuildRequest) (message.AppMessage, message.MessageDelivery, error) {
	return message.AppMessage{ID: "msg_review_feedback", ChannelID: rc.ThreadID, Metadata: map[string]any{message.ReviewFeedbackMetadataKey: map[string]any{"reviewContextId": rc.ID}}, Content: params.Summary}, message.MessageDelivery{MessageID: "msg_review_feedback"}, nil
}

func (s *channelHandlerTestStore) ListReviewFeedbackForUser(_ context.Context, _ string, _ string, rc agent.ReviewContext, _ int) ([]message.AppMessage, error) {
	return []message.AppMessage{{ID: "msg_review_feedback", ChannelID: rc.ThreadID}}, nil
}

func (s *channelHandlerTestStore) SetReviewFeedbackItemStatusForUser(_ context.Context, _ model.User, _ string, rc agent.ReviewContext, messageID string, _ string, _ string) (message.AppMessage, error) {
	return message.AppMessage{ID: messageID, ChannelID: rc.ThreadID}, nil
}

func (s *channelHandlerTestStore) LoadThreadShareCardForUser(_ context.Context, _ string, _ string, messageID string) (message.ThreadShareCardView, error) {
	return message.ThreadShareCardView{MessageID: messageID, Title: "Topic", MessageCount: 2}, nil
}

func (s *channelHandlerTestStore) ListDiscoverableChannelsForUser(_ context.Context, userID string, workspaceID string) ([]channel.DiscoverableChannel, error) {
	s.discoveredList.userID = userID
	s.discoveredList.workspaceID = workspaceID
	return []channel.DiscoverableChannel{{ChannelID: "chn_pub", Slug: "public", Description: "Public", Visibility: "public"}}, nil
}

func (s *channelHandlerTestStore) DiscoverableChannelForUser(_ context.Context, userID string, workspaceID string, channelID string) (channel.DiscoverableChannel, error) {
	s.discoveredGet.userID = userID
	s.discoveredGet.workspaceID = workspaceID
	s.discoveredGet.channelID = channelID
	return channel.DiscoverableChannel{ChannelID: channelID, Slug: "public", Description: "Public", Visibility: "public"}, nil
}

func (s *channelHandlerTestStore) JoinChannelForUser(_ context.Context, userID string, workspaceID string, channelID string) error {
	s.joined.userID = userID
	s.joined.workspaceID = workspaceID
	s.joined.channelID = channelID
	return nil
}

func (s *channelHandlerTestStore) LeaveChannelForUser(_ context.Context, userID string, workspaceID string, channelID string) error {
	s.left.userID = userID
	s.left.workspaceID = workspaceID
	s.left.channelID = channelID
	return nil
}

func (s *channelHandlerTestStore) ArchiveChannelForUser(_ context.Context, userID string, workspaceID string, channelID string) (message.ChannelSummary, error) {
	s.archived.userID = userID
	s.archived.workspaceID = workspaceID
	s.archived.channelID = channelID
	return message.ChannelSummary{ChannelID: channelID, Slug: "general", Archived: true}, nil
}

func (s *channelHandlerTestStore) UnarchiveChannelForUser(_ context.Context, userID string, workspaceID string, channelID string) (message.ChannelSummary, error) {
	s.unarchived.userID = userID
	s.unarchived.workspaceID = workspaceID
	s.unarchived.channelID = channelID
	return message.ChannelSummary{ChannelID: channelID, Slug: "general", Archived: false}, nil
}

func (s *channelHandlerTestStore) TransferChannelOwnershipForUser(_ context.Context, userID string, workspaceID string, channelID string, humanUserID string) error {
	s.transferred.userID = userID
	s.transferred.workspaceID = workspaceID
	s.transferred.channelID = channelID
	s.transferred.ownerID = humanUserID
	return nil
}

func (s *channelHandlerTestStore) DismissPendingOwnerContextForUser(_ context.Context, userID string, workspaceID string, channelID string, dismissedThroughGlobalSeq int64) error {
	s.dismissed.userID = userID
	s.dismissed.workspaceID = workspaceID
	s.dismissed.channelID = channelID
	s.dismissed.seq = dismissedThroughGlobalSeq
	return nil
}

func (s *channelHandlerTestStore) ChannelMembersForUser(_ context.Context, userID string, workspaceID string, channelID string) (message.ChannelMemberList, error) {
	s.membersForUser.userID = userID
	s.membersForUser.workspaceID = workspaceID
	s.membersForUser.channelID = channelID
	return message.ChannelMemberList{Humans: []message.ChannelMemberSummary{{ID: "usr_2", Name: "alice"}}}, nil
}

func (s *channelHandlerTestStore) ChannelAgentTaskSummariesForUser(_ context.Context, userID string, workspaceID string, channelID string) ([]message.ChannelAgentTaskSummary, error) {
	s.agentTaskSummaries.userID = userID
	s.agentTaskSummaries.workspaceID = workspaceID
	s.agentTaskSummaries.channelID = channelID
	if s.agentTaskSummaryResult != nil {
		return s.agentTaskSummaryResult, nil
	}
	return []message.ChannelAgentTaskSummary{}, nil
}

func (s *channelHandlerTestStore) ListChannelAddableAgentsForUser(_ context.Context, userID string, workspaceID string, channelID string, options agent.AgentDirectoryOptions) (agent.AgentDirectoryPage, error) {
	s.addableAgents.userID = userID
	s.addableAgents.workspaceID = workspaceID
	s.addableAgents.channelID = channelID
	s.addableAgents.options = options
	return agent.AgentDirectoryPage{
		Agents: []agent.AgentDirectoryItem{{
			ID:          "agt_1",
			Name:        "agent-one",
			MemberID:    "mem_agt_1",
			DisplayName: "Agent One",
			AvatarURL:   "https://example.com/agent.png",
			OwnerUserID: "usr_1",
			MachineID:   "mac_1",
			CanManage:   true,
		}},
		Limit:   options.Limit,
		HasMore: true,
	}, nil
}

func (s *channelHandlerTestStore) ListChannelAddablePeopleForUser(_ context.Context, userID string, workspaceID string, channelID string, options app.ChannelAddablePeopleOptions) (app.ChannelAddablePeoplePage, error) {
	s.addablePeople.userID = userID
	s.addablePeople.workspaceID = workspaceID
	s.addablePeople.channelID = channelID
	s.addablePeople.options = options
	return app.ChannelAddablePeoplePage{
		Humans: []workspace.WorkspaceMember{{
			UserID:      "usr_2",
			Email:       "alice@example.com",
			Name:        "alice",
			DisplayName: "Alice",
			MemberID:    "mem_usr_2",
			Role:        "member",
		}},
		Services: []app.AppInstallationView{{
			ID:       "ainst_1",
			AppID:    "app_1",
			AppSlug:  "demo",
			AppName:  "Demo",
			MemberID: "mem_app_1",
			Handle:   "demo",
			Enabled:  true,
		}},
		HasMore:    true,
		NextCursor: "next_people",
	}, nil
}

func (s *channelHandlerTestStore) AddChannelAgentForUserWithEvent(_ context.Context, userID string, workspaceID string, channelID string, agentID string) (message.ChannelMemberEventResult, error) {
	s.addedAgent.userID = userID
	s.addedAgent.workspaceID = workspaceID
	s.addedAgent.channelID = channelID
	s.addedAgent.agentID = agentID
	return s.memberEventResult, nil
}

func (s *channelHandlerTestStore) AddChannelPrivateAgentForUserWithEvent(_ context.Context, userID string, workspaceID string, channelID string, agentID string) (message.ChannelMemberEventResult, error) {
	s.privateAgent.userID = userID
	s.privateAgent.workspaceID = workspaceID
	s.privateAgent.channelID = channelID
	s.privateAgent.agentID = agentID
	return s.memberEventResult, nil
}

func (s *channelHandlerTestStore) UpdateChannelAgentVisibilityForUserWithEvent(_ context.Context, userID string, workspaceID string, channelID string, agentID string, visibility string) (message.ChannelMemberEventResult, error) {
	s.updatedAgentVisibility.userID = userID
	s.updatedAgentVisibility.workspaceID = workspaceID
	s.updatedAgentVisibility.channelID = channelID
	s.updatedAgentVisibility.agentID = agentID
	s.updatedAgentVisibility.visibility = visibility
	return s.memberEventResult, nil
}

func (s *channelHandlerTestStore) AddChannelServiceForUserWithEvent(_ context.Context, userID string, workspaceID string, channelID string, serviceMemberID string) (message.ChannelMemberEventResult, error) {
	s.addedService.userID = userID
	s.addedService.workspaceID = workspaceID
	s.addedService.channelID = channelID
	s.addedService.serviceMemberID = serviceMemberID
	return s.memberEventResult, nil
}

func (s *channelHandlerTestStore) AddChannelHumanForUserWithEvent(_ context.Context, userID string, workspaceID string, channelID string, humanUserID string) (message.ChannelMemberEventResult, error) {
	s.addedHuman.userID = userID
	s.addedHuman.workspaceID = workspaceID
	s.addedHuman.channelID = channelID
	s.addedHuman.humanUserID = humanUserID
	return s.memberEventResult, nil
}

func (s *channelHandlerTestStore) AddChannelMembersForUserWithEvents(_ context.Context, userID string, workspaceID string, channelID string, members []message.ChannelMemberAddRequest) ([]message.ChannelMemberEventResult, error) {
	s.batchAdded.userID = userID
	s.batchAdded.workspaceID = workspaceID
	s.batchAdded.channelID = channelID
	s.batchAdded.members = append([]message.ChannelMemberAddRequest(nil), members...)
	results := make([]message.ChannelMemberEventResult, 0, len(members))
	if s.memberEventResult.Changed {
		for range members {
			results = append(results, s.memberEventResult)
		}
	}
	return results, nil
}

func (s *channelHandlerTestStore) RemoveChannelAgentForUserWithEvent(_ context.Context, userID string, workspaceID string, channelID string, agentID string) (message.ChannelMemberEventResult, error) {
	s.removedAgent.userID = userID
	s.removedAgent.workspaceID = workspaceID
	s.removedAgent.channelID = channelID
	s.removedAgent.agentID = agentID
	return s.memberEventResult, nil
}

func (s *channelHandlerTestStore) RemoveChannelServiceForUserWithEvent(_ context.Context, userID string, workspaceID string, channelID string, serviceMemberID string) (message.ChannelMemberEventResult, error) {
	s.removedService.userID = userID
	s.removedService.workspaceID = workspaceID
	s.removedService.channelID = channelID
	s.removedService.serviceMemberID = serviceMemberID
	return s.memberEventResult, nil
}

func (s *channelHandlerTestStore) RemoveChannelHumanForUserWithEvent(_ context.Context, userID string, workspaceID string, channelID string, humanUserID string) (message.ChannelMemberEventResult, error) {
	s.removedHuman.userID = userID
	s.removedHuman.workspaceID = workspaceID
	s.removedHuman.channelID = channelID
	s.removedHuman.humanUserID = humanUserID
	return s.memberEventResult, nil
}

func (s *channelHandlerTestStore) ListDMChannelsForUserWithLimit(_ context.Context, userID string, workspaceID string, limit int) ([]message.DMChannel, error) {
	s.dmList.userID = userID
	s.dmList.workspaceID = workspaceID
	s.dmList.limit = limit
	return []message.DMChannel{{ID: "dm_1", PeerType: "user", PeerID: "usr_2", PeerName: "alice"}}, nil
}

func (s *channelHandlerTestStore) GetDMChannelForUser(_ context.Context, userID string, workspaceID string, channelID string) (message.DMChannel, error) {
	s.dmGet.userID = userID
	s.dmGet.workspaceID = workspaceID
	s.dmGet.channelID = channelID
	switch userID {
	case "usr_2":
		return message.DMChannel{ID: channelID, PeerType: "user", PeerID: "usr_1", PeerName: "owner"}, nil
	default:
		return message.DMChannel{ID: channelID, PeerType: "user", PeerID: "usr_2", PeerName: "alice"}, nil
	}
}

func (s *channelHandlerTestStore) OpenDMChannelWithAgent(_ context.Context, userID string, workspaceID string, agentID string) (message.DMChannel, error) {
	s.dmAgent.userID = userID
	s.dmAgent.workspaceID = workspaceID
	s.dmAgent.agentID = agentID
	return message.DMChannel{ID: "dm_agent", PeerType: "agent", PeerID: agentID}, nil
}

func (s *channelHandlerTestStore) OpenDMChannelWithUser(_ context.Context, userID string, workspaceID string, peerUserID string) (message.DMChannel, error) {
	s.dmUser.userID = userID
	s.dmUser.workspaceID = workspaceID
	s.dmUser.peerUserID = peerUserID
	return message.DMChannel{ID: "dm_user", PeerType: "user", PeerID: peerUserID}, nil
}

func (s *channelHandlerTestStore) HideDMChannelForUser(_ context.Context, userID string, workspaceID string, channelID string) (message.DMChannel, error) {
	s.dmHidden.userID = userID
	s.dmHidden.workspaceID = workspaceID
	s.dmHidden.channelID = channelID
	return message.DMChannel{ID: channelID, PeerType: "agent", PeerID: "agt_hidden", SidebarHidden: true}, nil
}

func (s *channelHandlerTestStore) ListRealtimeRoomIDsForChannel(_ context.Context, _ string, channelID string) ([]string, error) {
	s.listRoomsChannelID = channelID
	if s.listRoomsErr != nil {
		return nil, s.listRoomsErr
	}
	if len(s.listRooms) > 0 {
		return s.listRooms, nil
	}
	return []string{channelID}, nil
}

type channelHandlerTestRealtime struct {
	updated           []string
	updatedForUser    []string
	membersUpdated    []string
	wakeCleared       []string
	unreadUpdated     []unread.UnreadDelta
	scopeSubscribed   []string
	scopeUnsubscribed []string
}

func (r *channelHandlerTestRealtime) EmitChannelUpdated(workspaceID string) {
	r.updated = append(r.updated, workspaceID)
}

func (r *channelHandlerTestRealtime) EmitChannelUpdatedForUser(workspaceID string, userID string) {
	r.updatedForUser = append(r.updatedForUser, workspaceID+":"+userID)
}

func (r *channelHandlerTestRealtime) EmitChannelMembersUpdated(workspaceID string, channelID string) {
	r.membersUpdated = append(r.membersUpdated, workspaceID+":"+channelID)
}

func (r *channelHandlerTestRealtime) ClearAgentWakeStatusesForChannel(workspaceID string, channelID string, agentID string) {
	r.wakeCleared = append(r.wakeCleared, workspaceID+":"+channelID+":"+agentID)
}

func (r *channelHandlerTestRealtime) EmitUnreadUpdated(_ string, delta unread.UnreadDelta) {
	r.unreadUpdated = append(r.unreadUpdated, delta)
}

func (r *channelHandlerTestRealtime) SubscribeScopeForUser(workspaceID string, userID string, scopeID string) {
	r.scopeSubscribed = append(r.scopeSubscribed, workspaceID+":"+userID+":"+scopeID)
}

func (r *channelHandlerTestRealtime) UnsubscribeScopeForUser(workspaceID string, userID string, scopeID string) {
	r.scopeUnsubscribed = append(r.scopeUnsubscribed, workspaceID+":"+userID+":"+scopeID)
}

func channelWorkspaceRequest(params ...gin.Param) api.WorkspaceScopedRequest {
	ctx, _ := gin.CreateTestContext(httptest.NewRecorder())
	ctx.Request = httptest.NewRequest(http.MethodGet, "/", nil)
	ctx.Params = params
	return api.WorkspaceScopedRequest{
		AuthenticatedRequest: api.AuthenticatedRequest{Gin: ctx, User: identity.AuthenticatedUser{User: identity.User{ID: "usr_1"}}},
		Workspace:            workspace.Summary{WorkspaceID: "wsp_1", MemberID: "mem_1"},
	}
}

func channelWorkspaceJSON[T any](body T) api.WorkspaceScopedJSON[T] {
	return channelWorkspaceJSONWithParams(body)
}

func channelWorkspaceJSONWithParams[T any](body T, params ...gin.Param) api.WorkspaceScopedJSON[T] {
	return api.WorkspaceScopedJSON[T]{WorkspaceScopedRequest: channelWorkspaceRequest(params...), Body: body}
}

func requireHTTPError(t *testing.T, err error, status int, message string) {
	t.Helper()
	var httpErr api.HTTPError
	require.ErrorAs(t, err, &httpErr)
	require.Equal(t, status, httpErr.Status)
	require.Equal(t, message, httpErr.Message)
}

func derefString(value *string) string {
	if value == nil {
		return ""
	}
	return *value
}

func channelSuppressedUnreadDelta(scopeID string, workspaceHasUnread bool) unread.UnreadDelta {
	scopeType := unread.ScopeTypeChannel
	switch {
	case strings.HasPrefix(scopeID, "thr_"):
		scopeType = unread.ScopeTypeThread
	case strings.HasPrefix(scopeID, "dm_"):
		scopeType = unread.ScopeTypeDM
	}
	return unread.UnreadDelta{
		WorkspaceID:                      "wsp_1",
		UserID:                           "usr_1",
		MemberID:                         "mem_1",
		ScopeID:                          scopeID,
		ScopeType:                        scopeType,
		WorkspaceHasUnread:               workspaceHasUnread,
		WorkspaceUnreadConversationCount: 0,
		ServingState:                     unread.ServingStateStale,
	}
}
