package channel

import (
	"context"
	"log/slog"
	"net/http"
	"sort"
	"strconv"
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/api"
	"github.com/tidbcloud/AgentTeam/internal/api/dto"
	"github.com/tidbcloud/AgentTeam/internal/api/dto/converter"
	"github.com/tidbcloud/AgentTeam/internal/store/message"
	"github.com/tidbcloud/AgentTeam/internal/unread"
)

// ChannelMembershipStore owns channel self-membership mutations.
type ChannelMembershipStore interface {
	JoinChannelForUser(ctx context.Context, userID string, workspaceID string, channelID string) error
	LeaveChannelForUser(ctx context.Context, userID string, workspaceID string, channelID string) error
}

// ChannelMembershipAppStore owns channel member list and member mutations.
type ChannelMembershipAppStore interface {
	ListRealtimeRoomIDsForChannel(ctx context.Context, workspaceID string, channelID string) ([]string, error)
	ChannelMembersForUser(ctx context.Context, userID string, workspaceID string, channelID string) (message.ChannelMemberList, error)
	ChannelAgentTaskSummariesForUser(ctx context.Context, userID string, workspaceID string, channelID string) ([]message.ChannelAgentTaskSummary, error)
	ListChannelAddableAgentsForUser(ctx context.Context, userID string, workspaceID string, channelID string, options channelAgentDirectoryOptions) (channelAgentDirectoryPage, error)
	ListChannelAddablePeopleForUser(ctx context.Context, userID string, workspaceID string, channelID string, options channelAddablePeopleOptions) (channelAddablePeoplePage, error)
	AddChannelAgentForUserWithEvent(ctx context.Context, userID string, workspaceID string, channelID string, agentID string) (message.ChannelMemberEventResult, error)
	AddChannelPrivateAgentForUserWithEvent(ctx context.Context, userID string, workspaceID string, channelID string, agentID string) (message.ChannelMemberEventResult, error)
	UpdateChannelAgentVisibilityForUserWithEvent(ctx context.Context, userID string, workspaceID string, channelID string, agentID string, visibility string) (message.ChannelMemberEventResult, error)
	AddChannelServiceForUserWithEvent(ctx context.Context, userID string, workspaceID string, channelID string, serviceMemberID string) (message.ChannelMemberEventResult, error)
	AddChannelHumanForUserWithEvent(ctx context.Context, userID string, workspaceID string, channelID string, humanUserID string) (message.ChannelMemberEventResult, error)
	AddChannelMembersForUserWithEvents(ctx context.Context, userID string, workspaceID string, channelID string, members []message.ChannelMemberAddRequest) ([]message.ChannelMemberEventResult, error)
	RemoveChannelAgentForUserWithEvent(ctx context.Context, userID string, workspaceID string, channelID string, agentID string) (message.ChannelMemberEventResult, error)
	RemoveChannelServiceForUserWithEvent(ctx context.Context, userID string, workspaceID string, channelID string, serviceMemberID string) (message.ChannelMemberEventResult, error)
	RemoveChannelHumanForUserWithEvent(ctx context.Context, userID string, workspaceID string, channelID string, humanUserID string) (message.ChannelMemberEventResult, error)
}

// ChannelMembershipHandler owns channel join, leave, and member routes.
type ChannelMembershipHandler struct {
	channels      ChannelMembershipStore
	appStore      ChannelMembershipAppStore
	realtime      ChannelRealtime
	messageFanout MessageFanout
	unreadRuntime ChannelUnreadReader
	logger        *slog.Logger
}

func (h *ChannelMembershipHandler) warn(message string, args ...any) {
	if h.logger != nil {
		h.logger.Warn(message, args...)
	}
}

func (h *ChannelMembershipHandler) emitChannelMemberEvent(ctx context.Context, workspaceID string, result message.ChannelMemberEventResult) {
	if h.messageFanout == nil || !result.Changed {
		return
	}
	// Channel membership transcript rows are intentionally presentation-only:
	// they update the live message stream, but do not create ordinary unread,
	// notifications, Agent wake, or a recoverable ordinary-message outbox.
	h.messageFanout.EmitCommittedMessageProjectionOnly(ctx, workspaceID, result.Message, result.Delivery)
}

func validateAddChannelMemberRequest(request dto.AddChannelMemberRequest) (message.ChannelMemberAddRequest, error) {
	agentID := strings.TrimSpace(request.AgentID)
	userID := strings.TrimSpace(request.UserID)
	serviceMemberID := strings.TrimSpace(request.ServiceMemberID)
	selectedCount := 0
	if agentID != "" {
		selectedCount++
	}
	if userID != "" {
		selectedCount++
	}
	if serviceMemberID != "" {
		selectedCount++
	}
	if selectedCount == 0 {
		return message.ChannelMemberAddRequest{}, api.NewHTTPError(http.StatusBadRequest, "agentId, userId, or serviceMemberId is required")
	}
	if selectedCount > 1 {
		return message.ChannelMemberAddRequest{}, api.NewHTTPError(http.StatusBadRequest, "agentId, userId, and serviceMemberId are mutually exclusive")
	}
	visibility := strings.ToLower(strings.TrimSpace(request.Visibility))
	if agentID != "" {
		switch visibility {
		case "", "private", "public":
		default:
			return message.ChannelMemberAddRequest{}, api.NewHTTPError(http.StatusBadRequest, "agent visibility must be public or private")
		}
	} else if visibility != "" {
		return message.ChannelMemberAddRequest{}, api.NewHTTPError(http.StatusBadRequest, "visibility is only supported for agent members")
	}
	return message.ChannelMemberAddRequest{
		AgentID:         agentID,
		UserID:          userID,
		ServiceMemberID: serviceMemberID,
		Visibility:      visibility,
	}, nil
}

func validateUpdateChannelAgentVisibilityRequest(request dto.UpdateChannelAgentVisibilityRequest) (string, error) {
	visibility := strings.ToLower(strings.TrimSpace(request.Visibility))
	switch visibility {
	case "public", "private":
		return visibility, nil
	default:
		return "", api.NewHTTPError(http.StatusBadRequest, "agent visibility must be public or private")
	}
}

// leaveChannelRealtimeRoomsForUser removes the channel scope and every child
// reply-thread scope from each active streaming connection the user owns, so
// existing connections stop receiving channel-scoped live-only events
// (runtime.task_*, agent_wake_status, etc.) without waiting for reconnect.
// Failing to enumerate child scopes falls back to unsubscribing only the
// channel scope so the leave path stays best-effort and never blocks.
func (h *ChannelMembershipHandler) leaveChannelRealtimeRoomsForUser(ctx context.Context, workspaceID string, userID string, channelID string) {
	if h.realtime == nil {
		return
	}
	scopeIDs := []string{channelID}
	if h.appStore != nil {
		if roomIDs, err := h.appStore.ListRealtimeRoomIDsForChannel(ctx, workspaceID, channelID); err == nil {
			merged := make(map[string]struct{}, 1+len(roomIDs))
			merged[channelID] = struct{}{}
			for _, roomID := range roomIDs {
				if trimmed := strings.TrimSpace(roomID); trimmed != "" {
					merged[trimmed] = struct{}{}
				}
			}
			scopeIDs = scopeIDs[:0]
			for id := range merged {
				scopeIDs = append(scopeIDs, id)
			}
			sort.Strings(scopeIDs)
		} else {
			h.warn("list realtime rooms for channel leave failed; falling back to channel scope only",
				"workspace_id", workspaceID, "channel_id", channelID, "user_id", userID, "error", err)
		}
	}
	for _, scopeID := range scopeIDs {
		h.realtime.UnsubscribeScopeForUser(workspaceID, userID, scopeID)
	}
}

// Join handles POST /api/channels/:channelId/join.
// Adds the authenticated user as a member of the specified channel.
func (h *ChannelMembershipHandler) Join(r api.WorkspaceScopedRequest) (api.Response, error) {
	authenticated := r.User
	workspaceID := r.WorkspaceID()

	channelID := strings.TrimSpace(r.Gin.Param("channelId"))
	if channelID == "" {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "channelId is required")
	}

	if err := h.channels.JoinChannelForUser(r.Context(), authenticated.User.ID, workspaceID, channelID); err != nil {
		return api.Response{}, err

	}
	applyChannelMemberScopeInvalidated(h.realtime, h.unreadRuntime, workspaceID, authenticated.User.ID, r.Workspace.MemberID, channelID, unread.ScopeTypeChannel, "channel_join")
	if h.realtime != nil {
		h.realtime.SubscribeScopeForUser(workspaceID, authenticated.User.ID, channelID)
		h.realtime.EmitChannelUpdated(workspaceID)
	}
	return api.OK(dto.EmptyResponse{}), nil
}

// Leave handles POST /api/channels/:channelId/leave.
// Removes the authenticated user from the specified channel.
func (h *ChannelMembershipHandler) Leave(r api.WorkspaceScopedRequest) (api.Response, error) {
	authenticated := r.User
	workspaceID := r.WorkspaceID()

	channelID := strings.TrimSpace(r.Gin.Param("channelId"))
	if channelID == "" {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "channelId is required")
	}

	if err := h.channels.LeaveChannelForUser(r.Context(), authenticated.User.ID, workspaceID, channelID); err != nil {
		return api.Response{}, err

	}
	applyChannelMemberScopeInvalidated(h.realtime, h.unreadRuntime, workspaceID, authenticated.User.ID, r.Workspace.MemberID, channelID, unread.ScopeTypeChannel, "channel_leave")
	if h.realtime != nil {
		h.leaveChannelRealtimeRoomsForUser(r.Context(), workspaceID, authenticated.User.ID, channelID)
		h.realtime.EmitChannelUpdated(workspaceID)
	}
	return api.OK(dto.EmptyResponse{}), nil
}

// GetMembers handles GET /api/channels/:channelId/members.
// Returns the member list for the specified channel.

func (h *ChannelMembershipHandler) GetMembers(r api.WorkspaceScopedRequest) (api.Response, error) {
	authenticated := r.User
	workspaceID := r.WorkspaceID()

	channelID := strings.TrimSpace(r.Gin.Param("channelId"))
	if channelID == "" {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "channelId is required")
	}

	limit, err := parseChannelListLimit(r.Gin.Query("limit"))
	if err != nil {
		return api.Response{}, err
	}
	options := message.ChannelMemberListOptions{
		Limit:  limit,
		Cursor: strings.TrimSpace(r.Gin.Query("cursor")),
		Query:  strings.TrimSpace(r.Gin.Query("q")),
	}
	members, err := message.ChannelMemberList{}, error(nil)
	if pager, ok := h.appStore.(interface {
		ChannelMembersForUserPage(context.Context, string, string, string, message.ChannelMemberListOptions) (message.ChannelMemberList, error)
	}); ok {
		members, err = pager.ChannelMembersForUserPage(r.Context(), authenticated.User.ID, workspaceID, channelID, options)
	} else {
		members, err = h.appStore.ChannelMembersForUser(r.Context(), authenticated.User.ID, workspaceID, channelID)
	}

	if err != nil {
		return api.Response{}, err
	}
	return api.OK(converter.ToChannelMemberList(members)), nil
}

// AddableAgents handles GET /api/channels/:channelId/addable-agents.
// Returns a bounded contains-searchable list of agents the user can add.
func (h *ChannelMembershipHandler) AddableAgents(r api.WorkspaceScopedRequest) (api.Response, error) {
	authenticated := r.User
	workspaceID := r.WorkspaceID()

	channelID := strings.TrimSpace(r.Gin.Param("channelId"))
	if channelID == "" {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "channelId is required")
	}

	limit := channelDefaultAgentSearchLimit
	if rawLimit := strings.TrimSpace(r.Gin.Query("limit")); rawLimit != "" {
		parsed, err := strconv.Atoi(rawLimit)
		if err != nil || parsed <= 0 {
			return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "limit must be positive")
		}
		limit = parsed
	}
	if limit > channelMaxAgentSearchLimit {
		limit = channelMaxAgentSearchLimit
	}

	page, err := h.appStore.ListChannelAddableAgentsForUser(r.Context(), authenticated.User.ID, workspaceID, channelID, channelAgentDirectoryOptions{
		Query:  strings.TrimSpace(r.Gin.Query("q")),
		Limit:  limit,
		Cursor: strings.TrimSpace(r.Gin.Query("cursor")),
	})
	if err != nil {
		return api.Response{}, err
	}

	return api.OK(dto.AgentDirectoryResponse{
		Agents:     converter.ToAgentDirectoryItems(page.Agents, false),
		Limit:      page.Limit,
		Cursor:     page.Cursor,
		HasMore:    page.HasMore,
		NextCursor: page.NextCursor,
	}), nil
}

// AddablePeople handles GET /api/channels/:channelId/addable-people.
// Returns a bounded searchable list of human and service members the user can add.
func (h *ChannelMembershipHandler) AddablePeople(r api.WorkspaceScopedRequest) (api.Response, error) {
	authenticated := r.User
	workspaceID := r.WorkspaceID()

	channelID := strings.TrimSpace(r.Gin.Param("channelId"))
	if channelID == "" {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "channelId is required")
	}

	limit := 50
	if rawLimit := strings.TrimSpace(r.Gin.Query("limit")); rawLimit != "" {
		parsed, err := strconv.Atoi(rawLimit)
		if err != nil || parsed <= 0 {
			return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "limit must be positive")
		}
		limit = parsed
	}
	if limit > 100 {
		limit = 100
	}

	page, err := h.appStore.ListChannelAddablePeopleForUser(r.Context(), authenticated.User.ID, workspaceID, channelID, channelAddablePeopleOptions{
		Query:  strings.TrimSpace(r.Gin.Query("q")),
		Limit:  limit,
		Cursor: strings.TrimSpace(r.Gin.Query("cursor")),
	})
	if err != nil {
		return api.Response{}, err
	}

	return api.OK(dto.ChannelAddablePeopleResponse{
		Humans:     converter.ToWorkspaceMemberResponses(page.Humans),
		Services:   converter.ToAppInstallationResponses(page.Services),
		HasMore:    page.HasMore,
		NextCursor: page.NextCursor,
	}), nil
}

// AddMember handles POST /api/channels/:channelId/members.
// Adds an agent, user, or service member to the specified channel.
func (h *ChannelMembershipHandler) AddMember(r api.WorkspaceScopedJSON[dto.AddChannelMemberRequest]) (api.Response, error) {
	request := r.Body
	authenticated := r.User
	workspaceID := r.WorkspaceID()

	channelID := strings.TrimSpace(r.Gin.Param("channelId"))
	if channelID == "" {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "channelId is required")
	}

	member, err := validateAddChannelMemberRequest(request)
	if err != nil {
		return api.Response{}, err
	}

	if member.AgentID != "" {
		var eventResult message.ChannelMemberEventResult
		switch member.Visibility {
		case "", "private":
			var err error
			eventResult, err = h.appStore.AddChannelPrivateAgentForUserWithEvent(r.Context(), authenticated.User.ID, workspaceID, channelID, member.AgentID)
			if err != nil {
				return api.Response{}, err
			}
		case "public":
			var err error
			eventResult, err = h.appStore.AddChannelAgentForUserWithEvent(r.Context(), authenticated.User.ID, workspaceID, channelID, member.AgentID)
			if err != nil {
				return api.Response{}, err

			}
		}
		h.applyMemberEventInvalidated(workspaceID, channelID, eventResult, "channel_member_add")
		if h.realtime != nil {
			h.realtime.EmitChannelMembersUpdated(workspaceID, channelID)
			h.realtime.EmitChannelUpdated(workspaceID)
		}
		h.emitChannelMemberEvent(r.Context(), workspaceID, eventResult)
		return api.OK(dto.EmptyResponse{}), nil
	}

	if member.ServiceMemberID != "" {

		eventResult, err := h.appStore.AddChannelServiceForUserWithEvent(r.Context(), authenticated.User.ID, workspaceID, channelID, member.ServiceMemberID)
		if err != nil {
			return api.Response{}, err

		}
		h.applyMemberEventInvalidated(workspaceID, channelID, eventResult, "channel_member_add")
		if h.realtime != nil {
			h.realtime.EmitChannelMembersUpdated(workspaceID, channelID)
			h.realtime.EmitChannelUpdated(workspaceID)
		}
		h.emitChannelMemberEvent(r.Context(), workspaceID, eventResult)
		return api.OK(dto.EmptyResponse{}), nil
	}

	eventResult, err := h.appStore.AddChannelHumanForUserWithEvent(r.Context(), authenticated.User.ID, workspaceID, channelID, member.UserID)
	if err != nil {
		return api.Response{}, err

	}
	h.applyMemberEventInvalidated(workspaceID, channelID, eventResult, "channel_member_add")
	if h.realtime != nil {
		h.realtime.SubscribeScopeForUser(workspaceID, member.UserID, channelID)
		h.realtime.EmitChannelMembersUpdated(workspaceID, channelID)
		h.realtime.EmitChannelUpdated(workspaceID)
	}
	h.emitChannelMemberEvent(r.Context(), workspaceID, eventResult)
	return api.OK(dto.EmptyResponse{}), nil
}

// BatchAddMembers handles POST /api/channels/:channelId/members/batch.
// Adds all requested members to the specified channel as one all-or-nothing mutation.
func (h *ChannelMembershipHandler) BatchAddMembers(r api.WorkspaceScopedJSON[dto.BatchAddChannelMembersRequest]) (api.Response, error) {
	authenticated := r.User
	workspaceID := r.WorkspaceID()

	channelID := strings.TrimSpace(r.Gin.Param("channelId"))
	if channelID == "" {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "channelId is required")
	}
	if len(r.Body.Members) == 0 {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "members is required")
	}

	members := make([]message.ChannelMemberAddRequest, 0, len(r.Body.Members))
	normalized := make([]dto.AddChannelMemberRequest, 0, len(r.Body.Members))
	humanUserIDs := make([]string, 0, len(r.Body.Members))
	for _, request := range r.Body.Members {
		member, err := validateAddChannelMemberRequest(request)
		if err != nil {
			return api.Response{}, err
		}
		members = append(members, member)
		normalized = append(normalized, dto.AddChannelMemberRequest{
			AgentID:         member.AgentID,
			UserID:          member.UserID,
			ServiceMemberID: member.ServiceMemberID,
			Visibility:      member.Visibility,
		})
		if member.UserID != "" {
			humanUserIDs = append(humanUserIDs, member.UserID)
		}
	}

	eventResults, err := h.appStore.AddChannelMembersForUserWithEvents(r.Context(), authenticated.User.ID, workspaceID, channelID, members)
	if err != nil {
		return api.Response{}, err
	}
	for _, result := range eventResults {
		h.applyMemberEventInvalidated(workspaceID, channelID, result, "channel_member_add")
	}
	if h.realtime != nil {
		for _, userID := range humanUserIDs {
			h.realtime.SubscribeScopeForUser(workspaceID, userID, channelID)
		}
		h.realtime.EmitChannelMembersUpdated(workspaceID, channelID)
		h.realtime.EmitChannelUpdated(workspaceID)
	}
	for _, result := range eventResults {
		h.emitChannelMemberEvent(r.Context(), workspaceID, result)
	}
	return api.OK(dto.BatchAddChannelMembersResponse{AddedCount: channelMemberEventResultsChangedCount(eventResults), Members: normalized}), nil
}

func channelMemberEventResultsChangedCount(results []message.ChannelMemberEventResult) int {
	count := 0
	for _, result := range results {
		if !result.Changed {
			continue
		}
		if result.ChangedCount > 0 {
			count += result.ChangedCount
			continue
		}
		count++
	}
	return count
}

// RemoveAgentMember handles DELETE /api/channels/:channelId/members/agent/:agentId.
// Removes an agent member from the specified channel.
func (h *ChannelMembershipHandler) RemoveAgentMember(r api.WorkspaceScopedRequest) (api.Response, error) {
	authenticated := r.User
	workspaceID := r.WorkspaceID()

	channelID := strings.TrimSpace(r.Gin.Param("channelId"))
	agentID := strings.TrimSpace(r.Gin.Param("agentId"))
	if channelID == "" || agentID == "" {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "Invalid request")
	}

	eventResult, err := h.appStore.RemoveChannelAgentForUserWithEvent(r.Context(), authenticated.User.ID, workspaceID, channelID, agentID)
	if err != nil {
		return api.Response{}, err

	}
	h.applyMemberEventInvalidated(workspaceID, channelID, eventResult, "channel_member_remove")
	if h.realtime != nil {
		h.realtime.ClearAgentWakeStatusesForChannel(workspaceID, channelID, agentID)
		h.realtime.EmitChannelMembersUpdated(workspaceID, channelID)
		h.realtime.EmitChannelUpdated(workspaceID)
	}
	h.emitChannelMemberEvent(r.Context(), workspaceID, eventResult)
	return api.OK(dto.EmptyResponse{}), nil
}

// UpdateAgentVisibility handles PATCH /api/channels/:channelId/members/agent/:agentId.
// Changes an existing channel agent member between owner-private and channel-public.
func (h *ChannelMembershipHandler) UpdateAgentVisibility(r api.WorkspaceScopedJSON[dto.UpdateChannelAgentVisibilityRequest]) (api.Response, error) {
	authenticated := r.User
	workspaceID := r.WorkspaceID()

	channelID := strings.TrimSpace(r.Gin.Param("channelId"))
	agentID := strings.TrimSpace(r.Gin.Param("agentId"))
	if channelID == "" || agentID == "" {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "Invalid request")
	}

	visibility, err := validateUpdateChannelAgentVisibilityRequest(r.Body)
	if err != nil {
		return api.Response{}, err
	}

	eventResult, err := h.appStore.UpdateChannelAgentVisibilityForUserWithEvent(r.Context(), authenticated.User.ID, workspaceID, channelID, agentID, visibility)
	if err != nil {
		return api.Response{}, err
	}
	h.applyMemberEventInvalidated(workspaceID, channelID, eventResult, "channel_member_visibility")
	if h.realtime != nil {
		h.realtime.EmitChannelMembersUpdated(workspaceID, channelID)
		h.realtime.EmitChannelUpdated(workspaceID)
	}
	h.emitChannelMemberEvent(r.Context(), workspaceID, eventResult)
	return api.OK(dto.EmptyResponse{}), nil
}

// RemoveServiceMember handles DELETE /api/channels/:channelId/members/service/:memberId.
// Removes a service member from the specified channel.
func (h *ChannelMembershipHandler) RemoveServiceMember(r api.WorkspaceScopedRequest) (api.Response, error) {
	authenticated := r.User
	workspaceID := r.WorkspaceID()

	channelID := strings.TrimSpace(r.Gin.Param("channelId"))
	serviceMemberID := strings.TrimSpace(r.Gin.Param("memberId"))
	if channelID == "" || serviceMemberID == "" {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "Invalid request")
	}

	eventResult, err := h.appStore.RemoveChannelServiceForUserWithEvent(r.Context(), authenticated.User.ID, workspaceID, channelID, serviceMemberID)
	if err != nil {
		return api.Response{}, err

	}
	h.applyMemberEventInvalidated(workspaceID, channelID, eventResult, "channel_member_remove")
	if h.realtime != nil {
		h.realtime.EmitChannelMembersUpdated(workspaceID, channelID)
		h.realtime.EmitChannelUpdated(workspaceID)
	}
	h.emitChannelMemberEvent(r.Context(), workspaceID, eventResult)
	return api.OK(dto.EmptyResponse{}), nil
}

// RemoveUserMember handles DELETE /api/channels/:channelId/members/user/:userId.
// Removes a human user member from the specified channel.
func (h *ChannelMembershipHandler) RemoveUserMember(r api.WorkspaceScopedRequest) (api.Response, error) {
	authenticated := r.User
	workspaceID := r.WorkspaceID()

	channelID := strings.TrimSpace(r.Gin.Param("channelId"))
	userID := strings.TrimSpace(r.Gin.Param("userId"))
	if channelID == "" || userID == "" {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "Invalid request")
	}

	eventResult, err := h.appStore.RemoveChannelHumanForUserWithEvent(r.Context(), authenticated.User.ID, workspaceID, channelID, userID)
	if err != nil {
		return api.Response{}, err

	}
	h.applyMemberEventInvalidated(workspaceID, channelID, eventResult, "channel_member_remove")
	if h.realtime != nil {
		h.leaveChannelRealtimeRoomsForUser(r.Context(), workspaceID, userID, channelID)
		h.realtime.EmitChannelMembersUpdated(workspaceID, channelID)
		h.realtime.EmitChannelUpdated(workspaceID)
	}
	h.emitChannelMemberEvent(r.Context(), workspaceID, eventResult)
	return api.OK(dto.EmptyResponse{}), nil
}

func (h *ChannelMembershipHandler) applyScopeInvalidated(workspaceID string, scopeID string, memberID string, reason string) {
	if h == nil {
		return
	}
	applyChannelScopeInvalidated(h.unreadRuntime, workspaceID, scopeID, memberID, reason)
}

func (h *ChannelMembershipHandler) applyMemberEventInvalidated(workspaceID string, scopeID string, result message.ChannelMemberEventResult, reason string) {
	if h == nil || !result.Changed {
		return
	}
	memberIDs := compactChannelMemberIDs(result.AffectedMemberIDs)
	if len(memberIDs) == 0 {
		return
	}
	for _, memberID := range memberIDs {
		h.applyScopeInvalidated(workspaceID, scopeID, memberID, reason)
	}
}

func compactChannelMemberIDs(values []string) []string {
	if len(values) == 0 {
		return nil
	}
	seen := make(map[string]struct{}, len(values))
	memberIDs := make([]string, 0, len(values))
	for _, value := range values {
		memberID := strings.TrimSpace(value)
		if memberID == "" {
			continue
		}
		if _, ok := seen[memberID]; ok {
			continue
		}
		seen[memberID] = struct{}{}
		memberIDs = append(memberIDs, memberID)
	}
	return memberIDs
}

func parseChannelListLimit(rawLimit string) (int, error) {
	trimmed := strings.TrimSpace(rawLimit)
	if trimmed == "" {
		return message.DefaultChannelMemberListLimit, nil
	}
	limit, err := strconv.Atoi(trimmed)
	if err != nil {
		return 0, api.NewHTTPError(http.StatusBadRequest, "limit must be an integer")
	}
	if limit <= 0 {
		return 0, api.NewHTTPError(http.StatusBadRequest, "limit must be a positive integer")
	}
	if limit > message.MaxChannelMemberListLimit {
		return message.MaxChannelMemberListLimit, nil
	}
	return limit, nil
}
