package im

import (
	"net/http"
	"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"
)

// ScopeExtrasHandler owns IM scope-local settings and auxiliary message-surface mutations.
type ScopeExtrasHandler struct {
	ownerContexts        OwnerContextStore
	channelNotifications ChannelNotificationStore
	threadNotifications  ThreadNotificationStore
	conversations        ConversationStore
	streamWriter         StreamEventWriter
	unreadRuntime        UnreadRuntime
	unreadSink           UnreadDeltaSink
}

// DismissOwnerContext handles POST /api/v1/workspaces/:workspaceId/scopes/:scopeId/owner-context-dismiss.
func (h *ScopeExtrasHandler) DismissOwnerContext(r api.WorkspaceScopedJSON[dto.ChannelDismissOwnerContextRequest]) (api.Response, error) {
	if h.ownerContexts == nil {
		return api.Response{}, api.NewHTTPError(http.StatusInternalServerError, "Internal server error")
	}
	scopeID, err := routeScopeID(r.WorkspaceScopedRequest)
	if err != nil {
		return api.Response{}, err
	}
	if err := h.ownerContexts.DismissPendingOwnerContextForUser(r.Context(), r.UserID(), r.WorkspaceID(), scopeID, r.Body.DismissedThroughGlobalSeq); err != nil {
		return api.Response{}, err
	}
	if err := publishOwnerContextDismissedEvent(r.Context(), h.streamWriter, r.WorkspaceID(), r.Workspace.MemberID, scopeID, r.Body.DismissedThroughGlobalSeq); err != nil {
		return api.Response{}, err
	}
	return api.OK(dto.EmptyResponse{}), nil
}

// NotificationsGet handles GET /api/v1/workspaces/:workspaceId/scopes/:scopeId/notifications.
func (h *ScopeExtrasHandler) NotificationsGet(r api.WorkspaceScopedRequest) (api.Response, error) {
	scopeID, isThread, err := h.notificationScope(r)
	if err != nil {
		return api.Response{}, err
	}
	page, err := api.ReadScopeNotificationAgentPage(r)
	if err != nil {
		return api.Response{}, err
	}
	if isThread {
		if h.threadNotifications == nil {
			return api.Response{}, api.NewHTTPError(http.StatusInternalServerError, "Internal server error")
		}
		panel, err := h.threadNotifications.GetThreadNotificationPanelForUser(r.Context(), r.UserID(), r.WorkspaceID(), scopeID, page.Cursor, page.Limit)
		if err != nil {
			return api.Response{}, err
		}
		return api.OK(converter.ToScopeNotificationPanel(panel)), nil
	}
	if h.channelNotifications == nil {
		return api.Response{}, api.NewHTTPError(http.StatusInternalServerError, "Internal server error")
	}
	panel, err := h.channelNotifications.GetChannelNotificationPanelForUser(r.Context(), r.UserID(), r.WorkspaceID(), scopeID, page.Cursor, page.Limit)
	if err != nil {
		return api.Response{}, err
	}
	return api.OK(converter.ToScopeNotificationPanel(panel)), nil
}

// NotificationsSelfSet handles POST /api/v1/workspaces/:workspaceId/scopes/:scopeId/notifications/self.
func (h *ScopeExtrasHandler) NotificationsSelfSet(r api.WorkspaceScopedJSON[dto.NotificationStateRequest]) (api.Response, error) {
	scopeID, isThread, err := h.notificationScope(r.WorkspaceScopedRequest)
	if err != nil {
		return api.Response{}, err
	}
	state := strings.TrimSpace(r.Body.State)
	if isThread {
		if h.threadNotifications == nil {
			return api.Response{}, api.NewHTTPError(http.StatusInternalServerError, "Internal server error")
		}
		panel, err := h.threadNotifications.SetThreadNotificationSelfStateForUser(r.Context(), r.UserID(), r.WorkspaceID(), scopeID, state)
		if err != nil {
			return api.Response{}, err
		}
		dtoPanel := converter.ToScopeNotificationPanel(panel)
		if err := publishNotificationUpdatedEvent(r.Context(), h.streamWriter, r.WorkspaceID(), r.Workspace.MemberID, scopeID, "self", dtoPanel); err != nil {
			return api.Response{}, err
		}
		h.applyNotificationSelfState(r.WorkspaceID(), r.UserID(), r.Workspace.MemberID, scopeID, unread.ScopeTypeThread, state, "thread_notification_self")
		return api.OK(dtoPanel), nil
	}
	if h.channelNotifications == nil {
		return api.Response{}, api.NewHTTPError(http.StatusInternalServerError, "Internal server error")
	}
	panel, err := h.channelNotifications.SetChannelNotificationSelfStateForUser(r.Context(), r.UserID(), r.WorkspaceID(), scopeID, state)
	if err != nil {
		return api.Response{}, err
	}
	dtoPanel := converter.ToScopeNotificationPanel(panel)
	if err := publishNotificationUpdatedEvent(r.Context(), h.streamWriter, r.WorkspaceID(), r.Workspace.MemberID, scopeID, "self", dtoPanel); err != nil {
		return api.Response{}, err
	}
	h.applyNotificationSelfState(r.WorkspaceID(), r.UserID(), r.Workspace.MemberID, scopeID, unread.ScopeTypeChannel, state, "channel_notification_self")
	return api.OK(dtoPanel), nil
}

// NotificationsAgentSet handles POST /api/v1/workspaces/:workspaceId/scopes/:scopeId/notifications/agents/:agentId.
func (h *ScopeExtrasHandler) NotificationsAgentSet(r api.WorkspaceScopedJSON[dto.NotificationWakeStateRequest]) (api.Response, error) {
	scopeID, isThread, err := h.notificationScope(r.WorkspaceScopedRequest)
	if err != nil {
		return api.Response{}, err
	}
	agentID := strings.TrimSpace(r.Gin.Param("agentId"))
	if agentID == "" {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "agentId is required")
	}
	wakeState := strings.TrimSpace(r.Body.WakeState)
	if isThread {
		if h.threadNotifications == nil {
			return api.Response{}, api.NewHTTPError(http.StatusInternalServerError, "Internal server error")
		}
		panel, err := h.threadNotifications.SetThreadNotificationAgentWakeStateForUser(r.Context(), r.UserID(), r.WorkspaceID(), scopeID, agentID, wakeState)
		if err != nil {
			return api.Response{}, err
		}
		dtoPanel := converter.ToScopeNotificationPanel(panel)
		if err := publishNotificationUpdatedEvent(r.Context(), h.streamWriter, r.WorkspaceID(), r.Workspace.MemberID, scopeID, "agent", dtoPanel); err != nil {
			return api.Response{}, err
		}
		return api.OK(dtoPanel), nil
	}
	if h.channelNotifications == nil {
		return api.Response{}, api.NewHTTPError(http.StatusInternalServerError, "Internal server error")
	}
	panel, err := h.channelNotifications.SetChannelNotificationAgentWakeStateForUser(r.Context(), r.UserID(), r.WorkspaceID(), scopeID, agentID, wakeState)
	if err != nil {
		return api.Response{}, err
	}
	dtoPanel := converter.ToScopeNotificationPanel(panel)
	if err := publishNotificationUpdatedEvent(r.Context(), h.streamWriter, r.WorkspaceID(), r.Workspace.MemberID, scopeID, "agent", dtoPanel); err != nil {
		return api.Response{}, err
	}
	return api.OK(dtoPanel), nil
}

// NotificationsAgentsSet handles POST /api/v1/workspaces/:workspaceId/scopes/:scopeId/notifications/agents/all.
// It bulk-applies a wake state to every agent the caller can toggle in the
// surface notification panel, backing the mute-all / unmute-all quick buttons
// on the channel and thread members pages.
func (h *ScopeExtrasHandler) NotificationsAgentsSet(r api.WorkspaceScopedJSON[dto.NotificationWakeStateRequest]) (api.Response, error) {
	scopeID, isThread, err := h.notificationScope(r.WorkspaceScopedRequest)
	if err != nil {
		return api.Response{}, err
	}
	wakeState := strings.TrimSpace(r.Body.WakeState)
	if isThread {
		if h.threadNotifications == nil {
			return api.Response{}, api.NewHTTPError(http.StatusInternalServerError, "Internal server error")
		}
		panel, err := h.threadNotifications.SetNotificationAgentsWakeStateForUser(r.Context(), r.UserID(), r.WorkspaceID(), "thread", scopeID, wakeState)
		if err != nil {
			return api.Response{}, err
		}
		dtoPanel := converter.ToScopeNotificationPanel(panel)
		if err := publishNotificationUpdatedEvent(r.Context(), h.streamWriter, r.WorkspaceID(), r.Workspace.MemberID, scopeID, "agent", dtoPanel); err != nil {
			return api.Response{}, err
		}
		return api.OK(dtoPanel), nil
	}
	if h.channelNotifications == nil {
		return api.Response{}, api.NewHTTPError(http.StatusInternalServerError, "Internal server error")
	}
	panel, err := h.channelNotifications.SetNotificationAgentsWakeStateForUser(r.Context(), r.UserID(), r.WorkspaceID(), "channel", scopeID, wakeState)
	if err != nil {
		return api.Response{}, err
	}
	dtoPanel := converter.ToScopeNotificationPanel(panel)
	if err := publishNotificationUpdatedEvent(r.Context(), h.streamWriter, r.WorkspaceID(), r.Workspace.MemberID, scopeID, "agent", dtoPanel); err != nil {
		return api.Response{}, err
	}
	return api.OK(dtoPanel), nil
}

// applyNotificationSelfState reconciles the owner-local unread runtime with a
// committed notification self-state change. Mute clears and suppresses the
// member's loaded unread so current unread disappears and messages posted while
// muted stay uncounted. Active and quiet must not emit a fabricated zero unread
// delta; instead they request a scope roster refresh so the newly persisted
// state replaces the prior mute baseline and future messages regain unread
// eligibility.
func (h *ScopeExtrasHandler) applyNotificationSelfState(workspaceID, userID, memberID, scopeID string, scopeType unread.ScopeType, state, reason string) {
	if h == nil {
		return
	}
	if strings.EqualFold(strings.TrimSpace(state), "mute") {
		h.emitNotificationMuted(workspaceID, userID, memberID, scopeID, scopeType, reason)
		return
	}
	if h.unreadRuntime == nil {
		return
	}
	_ = h.unreadRuntime.RefreshScopeRoster(strings.TrimSpace(workspaceID), strings.TrimSpace(scopeID), scopeType, []string{strings.TrimSpace(memberID)})
}

// emitNotificationMuted clears and suppresses one member's loaded unread after
// a committed notification mute and emits the resulting zero delta so clients
// drop the badge immediately. It must only be called for a normalized "mute"
// state: re-activation to active/quiet is handled by applyNotificationSelfState
// through a roster refresh instead, which must not emit a fake zero delta.
func (h *ScopeExtrasHandler) emitNotificationMuted(workspaceID string, userID string, memberID string, scopeID string, scopeType unread.ScopeType, reason string) {
	if h == nil {
		return
	}
	workspaceID = strings.TrimSpace(workspaceID)
	userID = strings.TrimSpace(userID)
	memberID = strings.TrimSpace(memberID)
	scopeID = strings.TrimSpace(scopeID)
	if workspaceID == "" || memberID == "" || scopeID == "" {
		return
	}
	if h.unreadRuntime != nil {
		event := unread.ScopeInvalidated{
			WorkspaceID: workspaceID,
			ScopeID:     scopeID,
			Reason:      strings.TrimSpace(reason),
		}
		if memberID != "" {
			event.MemberIDs = []string{memberID}
		}
		_ = h.unreadRuntime.ApplyNotificationMuted(event)
	}
	if h.unreadSink == nil || userID == "" {
		return
	}
	workspaceHasUnread := false
	workspaceUnreadConversationCount := int64(0)
	if h.unreadRuntime != nil {
		workspaceHasUnread = h.unreadRuntime.WorkspaceHasUnread(workspaceID, memberID)
		workspaceUnreadConversationCount = h.unreadRuntime.WorkspaceUnreadConversationCount(workspaceID, memberID)
	}
	h.unreadSink.EmitUnreadUpdated(workspaceID, unread.UnreadDelta{
		WorkspaceID:                      workspaceID,
		UserID:                           userID,
		MemberID:                         memberID,
		ScopeID:                          scopeID,
		ScopeType:                        scopeType,
		Count:                            0,
		ManualUnread:                     false,
		HasUnread:                        false,
		WorkspaceHasUnread:               workspaceHasUnread,
		WorkspaceUnreadConversationCount: workspaceUnreadConversationCount,
		ServingState:                     unread.ServingStateStale,
	})
}

func (h *ScopeExtrasHandler) notificationScope(r api.WorkspaceScopedRequest) (string, bool, error) {
	scopeID, err := routeScopeID(r)
	if err != nil {
		return "", false, err
	}
	if h.conversations == nil {
		return "", false, api.NewHTTPError(http.StatusInternalServerError, "Internal server error")
	}
	conversation, err := h.conversations.LoadConversationForUser(r.Context(), r.UserID(), r.WorkspaceID(), scopeID, true)
	if err != nil {
		return "", false, err
	}
	return scopeID, strings.TrimSpace(conversation.Kind) == message.ConversationKindThread, nil
}
