package im

import (
	"context"

	"github.com/tidbcloud/AgentTeam/internal/runtimecontrol"
	messageservice "github.com/tidbcloud/AgentTeam/internal/service/message"
	"github.com/tidbcloud/AgentTeam/internal/store/channel"
	"github.com/tidbcloud/AgentTeam/internal/store/identity"
	storeim "github.com/tidbcloud/AgentTeam/internal/store/im"
	"github.com/tidbcloud/AgentTeam/internal/store/message"
	"github.com/tidbcloud/AgentTeam/internal/store/rewind"
	"github.com/tidbcloud/AgentTeam/internal/store/taskboard"
	"github.com/tidbcloud/AgentTeam/internal/unread"
)

// ChannelAccessResolver owns authoritative Channel access decisions for
// Channel-shaped scope snapshot admission.
type ChannelAccessResolver interface {
	ResolveChannelAccessForUser(ctx context.Context, userID string, workspaceID string, channelID string) (channel.ChannelAccessResolution, error)
}

// HumanMessageWriter is the narrow human message creation workflow reused by IM mutations.
type HumanMessageWriter interface {
	ValidateHumanMessage(ctx context.Context, user identity.User, workspaceID string, channelID string, content string, options messageservice.SendOptions) error
	SendHumanMessage(ctx context.Context, user identity.User, workspaceID string, channelID string, content string, attachmentIDs []string, options messageservice.SendOptions) (message.AppMessage, message.MessageDelivery, error)
}

// HumanMessageEditor owns ordinary text message edit mutations.
type HumanMessageEditor interface {
	EditHumanMessage(ctx context.Context, user identity.User, workspaceID string, channelID string, messageID string, content string, options messageservice.EditOptions) (message.AppMessage, error)
}

// HumanMessageRecaller owns ordinary single-message IM recall.
type HumanMessageRecaller interface {
	RecallHumanMessage(ctx context.Context, user identity.User, workspaceID string, channelID string, messageID string) (message.AppMessageRecallResult, error)
}

// SentMessageDecorator decorates freshly-mutated messages for the browser response.
type SentMessageDecorator interface {
	DecorateAppMessageForUser(ctx context.Context, userID string, workspaceID string, appMessage message.AppMessage) (message.AppMessage, error)
}

// MessageStore is the scope-local message history read port reused by IM.
type MessageStore interface {
	ListMessagesAroundForUser(ctx context.Context, userID string, workspaceID string, scopeID string, aroundMessageID string, limit int) (message.AppMessagePage, error)
	ListMessagesForUser(ctx context.Context, userID string, workspaceID string, scopeID string, limit int, before *int64, after *int64) (message.AppMessagePage, error)
}

// ChannelStore owns first-paint channel list reads for the Workspace IM snapshot.
type ChannelStore interface {
	ListChannelsForUser(ctx context.Context, userID string, workspaceID string) ([]message.ChannelSummary, error)
}

// MessageQueryStore owns IM-adjacent message diagnostics and search reads.
type MessageQueryStore interface {
	GetMessageDeliveryDiagnosticsForUser(ctx context.Context, userID string, workspaceID string, messageID string) (message.MessageDeliveryDiagnostics, error)
	SupportsMessageSearch(ctx context.Context) bool
	SearchMessagesForUser(ctx context.Context, userID string, workspaceID string, query string, scopeID string, options message.MessageSearchOptions) (message.MessageSearchResult, error)
}

// MessageSearchStore owns workspace-scoped message search readiness for the
// Workspace IM snapshot.
type MessageSearchStore interface {
	SupportsWorkspaceMessageSearch(ctx context.Context, workspaceID string) bool
}

// ChannelMemberStore owns optional scope snapshot member hydration for channel scopes.
type ChannelMemberStore interface {
	ChannelMembersForUser(ctx context.Context, userID string, workspaceID string, channelID string) (message.ChannelMemberList, error)
}

// ScopeSnapshotTaskBoard owns optional task and private-agent capability
// hydration for channel-backed scope snapshots.
type ScopeSnapshotTaskBoard interface {
	ListTasks(ctx context.Context, request taskboard.ListTasksRequest) (taskboard.ListTasksResponse, error)
	ResolvePrivateAgentContextContributionCapability(ctx context.Context, request taskboard.ResolvePrivateAgentContextContributionCapabilityRequest) (taskboard.ResolvePrivateAgentContextContributionCapabilityResponse, error)
}

func NewScopeSnapshotTaskBoard(store *taskboard.Store) ScopeSnapshotTaskBoard {
	if store == nil {
		return nil
	}
	return store
}

// AgentDeliveryStore replays an existing visible message to agents with an explicit delivery policy.
type AgentDeliveryStore interface {
	PrepareMessageAgentDeliveryForUser(ctx context.Context, userID string, workspaceID string, messageID string, interruptPolicy string) (message.AppMessage, message.MessageDelivery, error)
	CommitMessageAgentDeliveryForUser(ctx context.Context, userID string, workspaceID string, messageID string, interruptPolicy string) (message.AppMessage, error)
	MarkMessageAgentDeliveryStatusForUser(ctx context.Context, userID string, workspaceID string, messageID string, interruptPolicy string, status string) (message.AppMessage, error)
}

// AgentNotifier forwards agent wake payloads for IM mutations that intentionally avoid legacy Socket.IO browser fanout.
type AgentNotifier interface {
	NotifyAgentMessage(ctx context.Context, recipient message.AgentRecipient, seq int64, message message.ReceiveMessage) error
}

// ReplyThreadRootPreloader decorates wake payloads with visible thread root context.
type ReplyThreadRootPreloader interface {
	PreloadReplyThreadRootForRecipient(ctx context.Context, workspaceID string, recipient message.AgentRecipient, msg message.ReceiveMessage) (message.ReceiveMessage, error)
}

// ScopeFanoutDeliveryMarker marks already-delivered outbox rows for synchronous agent delivery.
type ScopeFanoutDeliveryMarker interface {
	MarkScopeFanoutDeliveredForMessage(ctx context.Context, messageID string) error
}

// RewindStore is the conversation rewind planning and operation surface.
type RewindStore interface {
	PrepareRewindForUser(ctx context.Context, userID string, workspaceID string, messageID string) (rewind.Plan, error)
	BeginRewindOperation(ctx context.Context, plan rewind.Plan) (string, error)
	MarkRewindOperationFailed(ctx context.Context, operationID string, reason string) error
	MarkRewindOperationRepairRequired(ctx context.Context, operationID string, reason string) error
	ValidateRewindPlan(ctx context.Context, plan rewind.Plan) error
	ApplyRewind(ctx context.Context, operationID string, plan rewind.Plan) error
}

// RewindHub restores agent runtime recovery points before applying rewound tombstones.
type RewindHub interface {
	AgentNotifier
	RestoreAgentRecoveryPoint(ctx context.Context, machineID string, request runtimecontrol.RestoreAgentRecoveryPointRequest) error
	InterruptAgentRun(machineID string, agentID string) error
}

// DMStore owns direct-message scope reads and opens for the Workspace IM surface.
type DMStore interface {
	ListDMChannelsForUserWithLimit(ctx context.Context, userID string, workspaceID string, limit int) ([]message.DMChannel, error)
	GetDMChannelForUser(ctx context.Context, userID string, workspaceID string, channelID string) (message.DMChannel, error)
	OpenDMChannelWithAgent(ctx context.Context, userID string, workspaceID string, agentID string) (message.DMChannel, error)
	OpenDMChannelWithUser(ctx context.Context, userID string, workspaceID string, peerUserID string) (message.DMChannel, error)
}

// OwnerContextStore owns pending owner-context dismissal for message surfaces.
type OwnerContextStore interface {
	DismissPendingOwnerContextForUser(ctx context.Context, userID string, workspaceID string, channelID string, dismissedThroughSeq int64) error
}

// ChannelNotificationStore owns channel-level notification persistence.
type ChannelNotificationStore interface {
	GetChannelNotificationPanelForUser(ctx context.Context, userID string, workspaceID string, channelID string, agentCursor string, agentLimit int) (message.ScopeNotificationPanel, error)
	SetChannelNotificationSelfStateForUser(ctx context.Context, userID string, workspaceID string, channelID string, state string) (message.ScopeNotificationPanel, error)
	SetChannelNotificationAgentWakeStateForUser(ctx context.Context, userID string, workspaceID string, channelID string, agentID string, wakeState string) (message.ScopeNotificationPanel, error)
	SetNotificationAgentsWakeStateForUser(ctx context.Context, userID, workspaceID, scopeType, scopeID, wakeState string) (message.ScopeNotificationPanel, error)
}

// ThreadNotificationStore owns thread-level notification persistence.
type ThreadNotificationStore interface {
	GetThreadNotificationPanelForUser(ctx context.Context, userID string, workspaceID string, threadChannelID string, agentCursor string, agentLimit int) (message.ScopeNotificationPanel, error)
	SetThreadNotificationSelfStateForUser(ctx context.Context, userID string, workspaceID string, threadChannelID string, state string) (message.ScopeNotificationPanel, error)
	SetThreadNotificationAgentWakeStateForUser(ctx context.Context, userID string, workspaceID string, threadChannelID string, agentID string, wakeState string) (message.ScopeNotificationPanel, error)
	SetNotificationAgentsWakeStateForUser(ctx context.Context, userID, workspaceID, scopeType, scopeID, wakeState string) (message.ScopeNotificationPanel, error)
}

// ConversationStore is the existing backend-unified conversation port reused by IM.
type ConversationStore interface {
	ListConversationsForUserFiltered(ctx context.Context, userID string, workspaceID string, limit int, cursor message.ConversationCursor, filter message.ConversationKindFilter) (message.ConversationPage, error)
	MarkConversationsReadForUser(ctx context.Context, userID string, workspaceID string, filter message.ConversationKindFilter) (message.ConversationBulkActionResult, error)
	PerformConversationActionForUser(ctx context.Context, userID string, workspaceID string, conversationID string, action string) (message.ConversationItem, error)
	LoadConversationForUser(ctx context.Context, userID string, workspaceID string, scopeID string, includeHidden bool) (message.ConversationItem, error)
}

// UnreadRuntime is the owner-local memory source for Workspace IM unread
// serving and the post-commit event sink for conversation actions.
type UnreadRuntime interface {
	ScopeStates(workspaceID string, memberID string, scopeIDs []string) map[string]unread.ScopeUnreadState
	WorkspaceStates(workspaceID string, memberID string) map[string]unread.ScopeUnreadState
	WorkspaceHasUnread(workspaceID string, memberID string) bool
	WorkspaceUnreadConversationCount(workspaceID string, memberID string) int64
	ApplyReadCursor(event unread.ReadCursorAdvanced) error
	ApplyActivityReadCursor(event unread.ActivityReadCursorAdvanced) error
	ApplyManualUnread(event unread.ManualUnreadChanged) error
	ApplyScopeInvalidated(event unread.ScopeInvalidated) error
	ApplyScopeInvalidatedWithDeltas(event unread.ScopeInvalidated) ([]unread.UnreadDelta, error)
	// ApplyNotificationMuted clears and suppresses one member's loaded unread for
	// a committed notification mute.
	ApplyNotificationMuted(event unread.ScopeInvalidated) error
	// RefreshScopeRoster forces a scope roster reload from the store after a
	// notification self-state change away from mute, so the persisted state
	// replaces the old mute baseline and future messages regain eligibility.
	RefreshScopeRoster(workspaceID string, scopeID string, scopeType unread.ScopeType, memberIDs []string) error
}

// UnreadDeltaSink emits owner-local unread deltas after Workspace IM
// conversation actions update runtime memory.
type UnreadDeltaSink interface {
	EmitUnreadUpdated(workspaceID string, delta unread.UnreadDelta)
}

// ConversationSidebarThreadStore owns the IM-only reply-thread preview used by the conversation sidebar.
type ConversationSidebarThreadStore interface {
	ListSubscribedThreadInboxPageForUser(ctx context.Context, userID string, workspaceID string, limit int, cursor message.ThreadInboxCursor) (message.ThreadInboxPage, error)
}

// ReactionStore is the existing reaction aggregate and mutation port reused by IM.
type ReactionStore interface {
	GetMessageReactionsForUser(ctx context.Context, userID string, workspaceID string, messageID string) ([]message.MessageReactionAggregate, error)
	AddReactionForUser(ctx context.Context, userID string, workspaceID string, messageID string, emoji string) (message.AddReactionResult, error)
	RemoveReactionForUser(ctx context.Context, userID string, workspaceID string, messageID string, emoji string) (message.RemoveReactionResult, error)
}

// PinStore owns shared message pins and top-message banner state for IM.
type PinStore interface {
	ListMessagePinsForUser(ctx context.Context, userID string, workspaceID string, scopeID string, cursor message.MessagePinCursor, limit int) (message.MessagePinList, error)
	PinMessageForUser(ctx context.Context, userID string, workspaceID string, scopeID string, messageID string) (message.MessagePinMutationResult, error)
	UnpinMessageForEveryoneForUser(ctx context.Context, userID string, workspaceID string, scopeID string, messageID string) (message.MessagePinMutationResult, error)
	GetMessageTopNoticeForUser(ctx context.Context, userID string, workspaceID string, scopeID string) (message.MessageTopNoticeState, error)
	TopMessageForUser(ctx context.Context, userID string, workspaceID string, scopeID string, messageID string) (message.MessageTopNoticeMutationResult, error)
	UntopMessageForEveryoneForUser(ctx context.Context, userID string, workspaceID string, scopeID string, messageID string) (message.MessageTopNoticeMutationResult, error)
	HideMessageTopNoticeBannerForUser(ctx context.Context, userID string, workspaceID string, scopeID string, messageID string, topNoticeID string) (message.MessageTopNoticeMutationResult, error)
	ShowMessageTopNoticeBannerForUser(ctx context.Context, userID string, workspaceID string, scopeID string, messageID string, topNoticeID string) (message.MessageTopNoticeMutationResult, error)
	ResetMessageTopNoticeHidesForUser(ctx context.Context, userID string, workspaceID string, scopeID string) (message.MessageTopNoticeMutationResult, error)
}

// StreamEventStore owns im_stream_events replay reads and low-level appends.
type StreamEventStore interface {
	AppendEvents(ctx context.Context, workspaceID string, memberID string, events []storeim.StreamEvent) error
	ListEventsAfter(ctx context.Context, workspaceID string, memberID string, afterSeq int64, limit int) ([]storeim.StreamEvent, error)
	GetLatestSeq(ctx context.Context, workspaceID string, memberID string) (int64, error)
}

// VisibleScopeAuthorizer validates a connection-local visible-scope
// subscription before the live hub starts delivering scope-keyed events.
type VisibleScopeAuthorizer interface {
	CanReadVisibleScope(ctx context.Context, workspaceID string, memberID string, scopeID string) (bool, error)
}

// StreamEventWriter owns production write-path fanout into im_stream_events and
// im_conversation_index. Scope events fan out to active scope members; member
// events are for self-only conversation/read-state mutations.
type StreamEventWriter interface {
	AppendScopeEvent(ctx context.Context, request storeim.ScopeEventRequest) ([]storeim.StreamEvent, error)
	AppendMemberEvent(ctx context.Context, workspaceID string, memberID string, scopeID string, event storeim.StreamEvent, conversation *storeim.ConversationIndexItem) (storeim.StreamEvent, error)
	AppendMemberWorkspaceEvent(ctx context.Context, workspaceID string, memberID string, event storeim.StreamEvent) (storeim.StreamEvent, error)
	AppendConversationBulkReadEvent(ctx context.Context, workspaceID string, memberID string, event storeim.StreamEvent, kinds []string) (storeim.StreamEvent, error)
	ArchiveIndexedConversation(ctx context.Context, request storeim.IndexedConversationArchiveRequest) (storeim.StreamEvent, storeim.ConversationIndexItem, error)
}

// StreamCursorStore owns im_stream_cursors. The only production ack path is the
// WebSocket ack frame handled by EventsWSHandler.
type StreamCursorStore interface {
	UpdateCursor(ctx context.Context, workspaceID string, memberID string, clientID string, ackedSeq int64) error
	GetCursor(ctx context.Context, workspaceID string, memberID string, clientID string) (storeim.StreamCursor, error)
}

// StreamCursorEventUpdater is implemented by stores that persist the optional
// event id and cursor generation sent with an ack frame.
type StreamCursorEventUpdater interface {
	UpdateCursorWithEvent(ctx context.Context, workspaceID string, memberID string, clientID string, ackedSeq int64, ackedEventID string, cursorGeneration string) error
}

// ConversationIndexStore owns im_conversation_index materialized projections.
type ConversationIndexStore interface {
	ListConversations(ctx context.Context, workspaceID string, memberID string, limit int, cursor storeim.ConversationCursor, kinds []string) (storeim.ConversationIndexPage, error)
	GetConversation(ctx context.Context, workspaceID string, memberID string, conversationID string) (storeim.ConversationIndexItem, error)
	LookupMemberVisibleConversationProjectionsByIDs(ctx context.Context, workspaceID string, memberID string, conversationIDs []string) ([]storeim.ConversationIndexItem, error)
	UpsertConversation(ctx context.Context, workspaceID string, memberID string, item storeim.ConversationIndexItem) error
}
