"""MCP tool registration for the Nextcloud Talk (spreed) integration."""

import logging
import uuid

from mcp.server.fastmcp import Context, FastMCP
from mcp.types import ToolAnnotations

from nextcloud_mcp_server.auth import require_scopes
from nextcloud_mcp_server.context import get_client
from nextcloud_mcp_server.models.talk import (
    GetConversationResponse,
    ListConversationsResponse,
    ListMessagesResponse,
    ListParticipantsResponse,
    MarkAsReadResponse,
    SendMessageResponse,
)
from nextcloud_mcp_server.observability.metrics import instrument_tool

logger = logging.getLogger(__name__)


# spreed advertises a 32000-character limit on chat messages (docs/chat.md);
# we enforce it client-side for a clearer error than the server's 413.
_MESSAGE_MAX_LENGTH = 32000


def _validate_message_text(message: str) -> None:
    # Reject both empty strings and whitespace-only strings — spreed
    # would happily post the latter as a visually-blank message.
    if not message or not message.strip():
        raise ValueError("Message text must not be empty or whitespace-only")
    if len(message) > _MESSAGE_MAX_LENGTH:
        raise ValueError(
            f"Message too long: {len(message)} characters (max {_MESSAGE_MAX_LENGTH})"
        )


def configure_talk_tools(mcp: FastMCP) -> None:
    """Configure Nextcloud Talk (spreed) MCP tools."""

    # Read tools

    @mcp.tool(
        title="List Talk Conversations",
        annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
    )
    @require_scopes("talk.read")
    @instrument_tool
    async def talk_list_conversations(
        ctx: Context,
        modified_since: int | None = None,
        include_status: bool = False,
    ) -> ListConversationsResponse:
        """List the user's Talk conversations (rooms).

        Args:
            modified_since: Optional Unix timestamp; only conversations
                modified after this time are returned.
            include_status: Whether to include user-status info for
                one-to-one conversations.
        """
        client = await get_client(ctx)
        rooms = await client.talk.list_conversations(
            modified_since=modified_since,
            include_status=include_status,
        )
        return ListConversationsResponse(results=rooms, total=len(rooms))

    @mcp.tool(
        title="Get Talk Conversation",
        annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
    )
    @require_scopes("talk.read")
    @instrument_tool
    async def talk_get_conversation(
        ctx: Context, token: str
    ) -> GetConversationResponse:
        """Get details of a Talk conversation by its token.

        Args:
            token: Unique room token (returned by ``talk_list_conversations``).
        """
        client = await get_client(ctx)
        conversation = await client.talk.get_conversation(token)
        return GetConversationResponse(conversation=conversation)

    @mcp.tool(
        title="Get Talk Messages",
        annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
    )
    @require_scopes("talk.read")
    @instrument_tool
    async def talk_get_messages(
        ctx: Context,
        token: str,
        limit: int = 50,
        last_known_message_id: int | None = None,
        include_last_known: bool = False,
    ) -> ListMessagesResponse:
        """Read chat history for a Talk conversation.

        Returns the most recent messages (older first when paginated).
        Does not move the user's read marker; call
        ``talk_mark_as_read`` separately if desired.

        Args:
            token: Conversation token.
            limit: Max messages per page. Valid range is 1-200 (spreed
                caps server-side at 200); values outside this range are
                clamped. Default 50.
            last_known_message_id: Pagination cursor — pass the
                ``last_known_message_id`` from the previous response to
                fetch the next (older) page.
            include_last_known: Include the cursor message in the page
                instead of starting just before it.
        """
        client = await get_client(ctx)
        messages, last_given = await client.talk.get_messages(
            token,
            limit=limit,
            last_known_message_id=last_known_message_id,
            look_into_future=False,
            set_read_marker=False,
            include_last_known=include_last_known,
        )
        return ListMessagesResponse(
            conversation_token=token,
            results=messages,
            count=len(messages),
            last_known_message_id=last_given,
        )

    @mcp.tool(
        title="List Talk Conversation Participants",
        annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
    )
    @require_scopes("talk.read")
    @instrument_tool
    async def talk_list_participants(
        ctx: Context, token: str, include_status: bool = False
    ) -> ListParticipantsResponse:
        """List the participants of a Talk conversation.

        Args:
            token: Conversation token.
            include_status: Include each participant's user-status info.
        """
        client = await get_client(ctx)
        participants = await client.talk.list_participants(
            token, include_status=include_status
        )
        return ListParticipantsResponse(
            conversation_token=token,
            results=participants,
            count=len(participants),
        )

    # Write tools

    @mcp.tool(
        title="Send Talk Message",
        annotations=ToolAnnotations(idempotentHint=False, openWorldHint=True),
    )
    @require_scopes("talk.write")
    @instrument_tool
    async def talk_send_message(
        ctx: Context,
        token: str,
        message: str,
        reply_to: int | None = None,
        silent: bool = False,
    ) -> SendMessageResponse:
        """Post a chat message into a Talk conversation as the user.

        A random ``referenceId`` is attached so spreed dedupes the post
        if the request is retried.

        Args:
            token: Conversation token.
            message: Message text (max 32000 characters).
            reply_to: Optional parent message ID to thread the reply.
            silent: When True the message is delivered without push
                notifications (e.g. for status updates).
        """
        _validate_message_text(message)
        client = await get_client(ctx)
        posted = await client.talk.send_message(
            token,
            message,
            reply_to=reply_to,
            # 32 hex chars, no dashes — spreed accepts either UUID format.
            reference_id=uuid.uuid4().hex,
            silent=silent,
        )
        return SendMessageResponse(message=posted)

    @mcp.tool(
        title="Mark Talk Conversation as Read",
        annotations=ToolAnnotations(idempotentHint=True, openWorldHint=True),
    )
    @require_scopes("talk.write")
    @instrument_tool
    async def talk_mark_as_read(
        ctx: Context,
        token: str,
        last_read_message: int | None = None,
    ) -> MarkAsReadResponse:
        """Move the user's read marker forward in a Talk conversation.

        Args:
            token: Conversation token.
            last_read_message: Optional message ID to mark as the new
                read position. When omitted, spreed marks everything
                currently in the room as read.
        """
        client = await get_client(ctx)
        await client.talk.mark_as_read(token, last_read_message=last_read_message)
        return MarkAsReadResponse(
            success=True,
            message="Conversation marked as read",
            conversation_token=token,
            last_read_message=last_read_message,
        )
