"""MCP tools for Nextcloud News app."""

import logging

from httpx import HTTPStatusError, RequestError
from mcp.server.fastmcp import Context, FastMCP
from mcp.shared.exceptions import McpError
from mcp.types import ErrorData, ToolAnnotations

from nextcloud_mcp_server.auth import require_scopes
from nextcloud_mcp_server.client.news import NewsItemType
from nextcloud_mcp_server.context import get_client
from nextcloud_mcp_server.models.news import (
    FeedHealthResponse,
    GetItemResponse,
    GetStatusResponse,
    ListFeedsResponse,
    ListFoldersResponse,
    ListItemsResponse,
    NewsFeed,
    NewsFolder,
    NewsItem,
    NewsItemSummary,
)
from nextcloud_mcp_server.observability.metrics import instrument_tool

logger = logging.getLogger(__name__)


def configure_news_tools(mcp: FastMCP):
    """Configure News app MCP tools."""

    @mcp.tool(
        title="List News Folders",
        annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
    )
    @require_scopes("news:read")
    @instrument_tool
    async def nc_news_list_folders(ctx: Context) -> ListFoldersResponse:
        """List all News folders (requires news:read scope)."""
        client = await get_client(ctx)
        try:
            folders_data = await client.news.get_folders()
            folders = [NewsFolder(**f) for f in folders_data]
            return ListFoldersResponse(results=folders, total_count=len(folders))
        except RequestError as e:
            raise McpError(
                ErrorData(code=-1, message=f"Network error listing folders: {str(e)}")
            )
        except HTTPStatusError as e:
            raise McpError(
                ErrorData(
                    code=-1,
                    message=f"Failed to list folders: {e.response.status_code}",
                )
            )

    @mcp.tool(
        title="List News Feeds",
        annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
    )
    @require_scopes("news:read")
    @instrument_tool
    async def nc_news_list_feeds(ctx: Context) -> ListFeedsResponse:
        """List all News feeds with metadata (requires news:read scope).

        Returns feeds with unread counts, error status, and overall starred count.
        """
        client = await get_client(ctx)
        try:
            data = await client.news.get_feeds()
            feeds = [NewsFeed(**f) for f in data.get("feeds", [])]
            return ListFeedsResponse(
                results=feeds,
                starred_count=data.get("starredCount", 0),
                newest_item_id=data.get("newestItemId"),
                total_count=len(feeds),
            )
        except RequestError as e:
            raise McpError(
                ErrorData(code=-1, message=f"Network error listing feeds: {str(e)}")
            )
        except HTTPStatusError as e:
            raise McpError(
                ErrorData(
                    code=-1,
                    message=f"Failed to list feeds: {e.response.status_code}",
                )
            )

    @mcp.tool(
        title="List News Items",
        annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
    )
    @require_scopes("news:read")
    @instrument_tool
    async def nc_news_list_items(
        ctx: Context,
        feed_id: int | None = None,
        folder_id: int | None = None,
        starred_only: bool = False,
        unread_only: bool = False,
        limit: int = 50,
        offset: int = 0,
    ) -> ListItemsResponse:
        """List News items (articles) with optional filtering (requires news:read scope).

        Args:
            feed_id: Filter by specific feed ID
            folder_id: Filter by specific folder ID
            starred_only: Return only starred items
            unread_only: Return only unread items
            limit: Maximum number of items to return (default 50, -1 for all)
            offset: Item ID to start after (for pagination)

        Returns:
            ListItemsResponse with items, count, and pagination info
        """
        client = await get_client(ctx)

        # Determine item type filter
        type_ = NewsItemType.ALL
        id_ = 0
        if starred_only:
            type_ = NewsItemType.STARRED
        elif feed_id is not None:
            type_ = NewsItemType.FEED
            id_ = feed_id
        elif folder_id is not None:
            type_ = NewsItemType.FOLDER
            id_ = folder_id

        try:
            items_data = await client.news.get_items(
                batch_size=limit,
                offset=offset,
                type_=type_,
                id_=id_,
                get_read=not unread_only,
            )
            items = [NewsItemSummary(**i) for i in items_data]

            # Determine pagination info
            oldest_id = min((i.id for i in items), default=None) if items else None
            has_more = len(items) == limit and limit > 0

            return ListItemsResponse(
                results=items,
                total_count=len(items),
                has_more=has_more,
                oldest_id=oldest_id,
            )
        except RequestError as e:
            raise McpError(
                ErrorData(code=-1, message=f"Network error listing items: {str(e)}")
            )
        except HTTPStatusError as e:
            raise McpError(
                ErrorData(
                    code=-1,
                    message=f"Failed to list items: {e.response.status_code}",
                )
            )

    @mcp.tool(
        title="Get News Item",
        annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
    )
    @require_scopes("news:read")
    @instrument_tool
    async def nc_news_get_item(item_id: int, ctx: Context) -> GetItemResponse:
        """Get a specific News item by ID with full content (requires news:read scope).

        Args:
            item_id: Item ID

        Returns:
            GetItemResponse with full item details including HTML body
        """
        client = await get_client(ctx)
        try:
            item_data = await client.news.get_item(item_id)
            item = NewsItem(**item_data)
            return GetItemResponse(item=item)
        except ValueError as e:
            raise McpError(ErrorData(code=-1, message=str(e)))
        except RequestError as e:
            raise McpError(
                ErrorData(
                    code=-1, message=f"Network error getting item {item_id}: {str(e)}"
                )
            )
        except HTTPStatusError as e:
            if e.response.status_code == 404:
                raise McpError(ErrorData(code=-1, message=f"Item {item_id} not found"))
            raise McpError(
                ErrorData(
                    code=-1,
                    message=f"Failed to get item {item_id}: {e.response.status_code}",
                )
            )

    @mcp.tool(
        title="Get Starred News Items",
        annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
    )
    @require_scopes("news:read")
    @instrument_tool
    async def nc_news_get_starred_items(
        ctx: Context, limit: int = 50, offset: int = 0
    ) -> ListItemsResponse:
        """Get starred (favorited) News items (requires news:read scope).

        Convenience method for retrieving user's starred articles.

        Args:
            limit: Maximum number of items to return (default 50, -1 for all)
            offset: Item ID to start after (for pagination)

        Returns:
            ListItemsResponse with starred items
        """
        client = await get_client(ctx)
        try:
            items_data = await client.news.get_items(
                batch_size=limit,
                offset=offset,
                type_=NewsItemType.STARRED,
                get_read=True,  # Include read starred items
            )
            items = [NewsItemSummary(**i) for i in items_data]

            oldest_id = min((i.id for i in items), default=None) if items else None
            has_more = len(items) == limit and limit > 0

            return ListItemsResponse(
                results=items,
                total_count=len(items),
                has_more=has_more,
                oldest_id=oldest_id,
            )
        except RequestError as e:
            raise McpError(
                ErrorData(
                    code=-1, message=f"Network error getting starred items: {str(e)}"
                )
            )
        except HTTPStatusError as e:
            raise McpError(
                ErrorData(
                    code=-1,
                    message=f"Failed to get starred items: {e.response.status_code}",
                )
            )

    @mcp.tool(
        title="Get Unread News Items",
        annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
    )
    @require_scopes("news:read")
    @instrument_tool
    async def nc_news_get_unread_items(
        ctx: Context, limit: int = 50, offset: int = 0
    ) -> ListItemsResponse:
        """Get unread News items (requires news:read scope).

        Convenience method for retrieving unread articles across all feeds.

        Args:
            limit: Maximum number of items to return (default 50, -1 for all)
            offset: Item ID to start after (for pagination)

        Returns:
            ListItemsResponse with unread items
        """
        client = await get_client(ctx)
        try:
            items_data = await client.news.get_items(
                batch_size=limit,
                offset=offset,
                type_=NewsItemType.ALL,
                get_read=False,  # Only unread items
            )
            items = [NewsItemSummary(**i) for i in items_data]

            oldest_id = min((i.id for i in items), default=None) if items else None
            has_more = len(items) == limit and limit > 0

            return ListItemsResponse(
                results=items,
                total_count=len(items),
                has_more=has_more,
                oldest_id=oldest_id,
            )
        except RequestError as e:
            raise McpError(
                ErrorData(
                    code=-1, message=f"Network error getting unread items: {str(e)}"
                )
            )
        except HTTPStatusError as e:
            raise McpError(
                ErrorData(
                    code=-1,
                    message=f"Failed to get unread items: {e.response.status_code}",
                )
            )

    @mcp.tool(
        title="Get News Feed Health",
        annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
    )
    @require_scopes("news:read")
    @instrument_tool
    async def nc_news_get_feed_health(feed_id: int, ctx: Context) -> FeedHealthResponse:
        """Get health status for a specific feed (requires news:read scope).

        Returns error count and last error message if the feed has update issues.

        Args:
            feed_id: Feed ID to check

        Returns:
            FeedHealthResponse with error status
        """
        client = await get_client(ctx)
        try:
            data = await client.news.get_feeds()
            for feed_data in data.get("feeds", []):
                if feed_data.get("id") == feed_id:
                    feed = NewsFeed(**feed_data)
                    return FeedHealthResponse(
                        feed_id=feed.id,
                        title=feed.title,
                        url=feed.url,
                        has_errors=feed.has_errors,
                        error_count=feed.update_error_count,
                        last_error=feed.last_update_error,
                    )
            raise McpError(ErrorData(code=-1, message=f"Feed {feed_id} not found"))
        except RequestError as e:
            raise McpError(
                ErrorData(
                    code=-1,
                    message=f"Network error getting feed health: {str(e)}",
                )
            )
        except HTTPStatusError as e:
            raise McpError(
                ErrorData(
                    code=-1,
                    message=f"Failed to get feed health: {e.response.status_code}",
                )
            )

    @mcp.tool(
        title="Get News App Status",
        annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
    )
    @require_scopes("news:read")
    @instrument_tool
    async def nc_news_get_status(ctx: Context) -> GetStatusResponse:
        """Get News app status and version (requires news:read scope).

        Returns version information and any configuration warnings.
        """
        client = await get_client(ctx)
        try:
            status_data = await client.news.get_status()
            return GetStatusResponse(
                version=status_data.get("version", "unknown"),
                warnings=status_data.get("warnings", {}),
            )
        except RequestError as e:
            raise McpError(
                ErrorData(code=-1, message=f"Network error getting status: {str(e)}")
            )
        except HTTPStatusError as e:
            raise McpError(
                ErrorData(
                    code=-1,
                    message=f"Failed to get status: {e.response.status_code}",
                )
            )
