"""Discovery MCP tools: list libraries, collections, and items.

Authorization for every call is expressed as ``resolved_libraries`` —
the Library UID list the auth middleware attached to the FastMCP
``Context``. Tools never consult token claim shapes; they read
:func:`mcp_server.context.get_mcp_resolved_libraries` and forward the
result as a Cypher parameter.

See ``docs/DAEDALUS_PALLAS_INTEGRATION_v1.md`` §3.3 for the unified
auth model.
"""

from __future__ import annotations

from typing import Any

from asgiref.sync import sync_to_async
from fastmcp.server.context import Context

from ..context import get_mcp_resolved_libraries
from ..metrics import record_tool_call

DEFAULT_LIMIT = 50
MAX_LIMIT = 200


def _clamp(limit: int) -> int:
    if limit < 1:
        return 1
    return min(limit, MAX_LIMIT)


def register_discovery_tools(mcp):
    @mcp.tool
    async def list_libraries(
        limit: int = DEFAULT_LIMIT,
        offset: int = 0,
        ctx: Context | None = None,
    ) -> dict[str, Any]:
        """List Mnemosyne libraries. Each library has a content-aware library_type
        (fiction, nonfiction, technical, music, film, art, journal, business,
        finance) that drives chunking, embedding, and re-ranking. Returns uid,
        name, library_type, description for each library — use the uid or
        library_type to scope a subsequent search.
        """
        resolved_libraries = await get_mcp_resolved_libraries(ctx)
        with record_tool_call("list_libraries"):
            return await sync_to_async(_query_libraries, thread_sensitive=True)(
                _clamp(limit), max(offset, 0), resolved_libraries
            )

    @mcp.tool
    async def list_collections(
        library_uid: str | None = None,
        limit: int = DEFAULT_LIMIT,
        offset: int = 0,
        ctx: Context | None = None,
    ) -> dict[str, Any]:
        """List collections, optionally filtered by parent library_uid.
        Collections group related items inside a library (e.g. a series of novels,
        a multi-volume manual). Returns uid, name, description, library_uid,
        library_name. Use the uid to scope a subsequent search to one collection.
        """
        resolved_libraries = await get_mcp_resolved_libraries(ctx)
        with record_tool_call("list_collections"):
            return await sync_to_async(_query_collections, thread_sensitive=True)(
                library_uid, _clamp(limit), max(offset, 0), resolved_libraries
            )

    @mcp.tool
    async def list_items(
        collection_uid: str | None = None,
        library_uid: str | None = None,
        limit: int = DEFAULT_LIMIT,
        offset: int = 0,
        ctx: Context | None = None,
    ) -> dict[str, Any]:
        """List items (the indexed documents/files), optionally filtered by
        collection_uid or library_uid. Returns uid, title, item_type, file_type,
        chunk_count, image_count, embedding_status. Use chunk_count to gauge
        document size; use embedding_status to skip items that are not yet
        searchable (only 'completed' items appear in search results).
        """
        resolved_libraries = await get_mcp_resolved_libraries(ctx)
        with record_tool_call("list_items"):
            return await sync_to_async(_query_items, thread_sensitive=True)(
                collection_uid,
                library_uid,
                _clamp(limit),
                max(offset, 0),
                resolved_libraries,
            )


# Single authorization clause shared across every discovery query.
# Matches ``library.services.search._RESOLVED_LIBRARIES_CLAUSE`` — the
# ``None`` branch lets trusted callers bypass, a non-empty list scopes
# to those UIDs, and an empty list is fail-closed (yields nothing).
_RESOLVED_LIBRARIES_CLAUSE = (
    "($resolved_libraries IS NULL OR l.uid IN $resolved_libraries)"
)


def _query_libraries(
    limit: int,
    offset: int,
    resolved_libraries: list[str] | None,
) -> dict[str, Any]:
    from neomodel import db

    rows, _ = db.cypher_query(
        "MATCH (l:Library) "
        f"WHERE {_RESOLVED_LIBRARIES_CLAUSE} "
        "RETURN l.uid, l.name, l.library_type, l.description "
        "ORDER BY l.name SKIP $offset LIMIT $limit",
        {
            "offset": offset,
            "limit": limit,
            "resolved_libraries": resolved_libraries,
        },
    )
    return {
        "libraries": [
            {
                "uid": uid,
                "name": name,
                "library_type": library_type,
                "description": description,
            }
            for (uid, name, library_type, description) in rows
        ],
        "limit": limit,
        "offset": offset,
    }


def _query_collections(
    library_uid: str | None,
    limit: int,
    offset: int,
    resolved_libraries: list[str] | None,
) -> dict[str, Any]:
    from neomodel import db

    base_params = {
        "offset": offset,
        "limit": limit,
        "resolved_libraries": resolved_libraries,
    }
    if library_uid:
        cypher = (
            "MATCH (l:Library {uid: $library_uid})-[:CONTAINS]->(c:Collection) "
            f"WHERE {_RESOLVED_LIBRARIES_CLAUSE} "
            "RETURN c.uid, c.name, c.description, l.uid, l.name "
            "ORDER BY c.name SKIP $offset LIMIT $limit"
        )
        params = {**base_params, "library_uid": library_uid}
    else:
        cypher = (
            "MATCH (l:Library)-[:CONTAINS]->(c:Collection) "
            f"WHERE {_RESOLVED_LIBRARIES_CLAUSE} "
            "RETURN c.uid, c.name, c.description, l.uid, l.name "
            "ORDER BY l.name, c.name SKIP $offset LIMIT $limit"
        )
        params = base_params

    rows, _ = db.cypher_query(cypher, params)
    return {
        "collections": [
            {
                "uid": c_uid,
                "name": c_name,
                "description": c_desc,
                "library_uid": l_uid,
                "library_name": l_name,
            }
            for (c_uid, c_name, c_desc, l_uid, l_name) in rows
        ],
        "limit": limit,
        "offset": offset,
    }


def _query_items(
    collection_uid: str | None,
    library_uid: str | None,
    limit: int,
    offset: int,
    resolved_libraries: list[str] | None,
) -> dict[str, Any]:
    from neomodel import db

    where = [_RESOLVED_LIBRARIES_CLAUSE]
    params: dict[str, Any] = {
        "offset": offset,
        "limit": limit,
        "resolved_libraries": resolved_libraries,
    }
    if collection_uid:
        where.append("c.uid = $collection_uid")
        params["collection_uid"] = collection_uid
    if library_uid:
        where.append("l.uid = $library_uid")
        params["library_uid"] = library_uid

    where_clause = "WHERE " + " AND ".join(where)
    cypher = (
        "MATCH (l:Library)-[:CONTAINS]->(c:Collection)-[:CONTAINS]->(i:Item) "
        f"{where_clause} "
        "RETURN i.uid, i.title, i.item_type, i.file_type, "
        "i.chunk_count, i.image_count, i.embedding_status "
        "ORDER BY i.title SKIP $offset LIMIT $limit"
    )

    rows, _ = db.cypher_query(cypher, params)
    return {
        "items": [
            {
                "uid": uid,
                "title": title,
                "item_type": item_type,
                "file_type": file_type,
                "chunk_count": chunk_count,
                "image_count": image_count,
                "embedding_status": embedding_status,
            }
            for (
                uid,
                title,
                item_type,
                file_type,
                chunk_count,
                image_count,
                embedding_status,
            ) in rows
        ],
        "limit": limit,
        "offset": offset,
    }
