from __future__ import annotations

import os
import sys
from urllib.parse import urlparse
import httpx
from dotenv import load_dotenv
from mcp.server.fastmcp import FastMCP
from mcp.types import ImageContent, TextContent, ToolAnnotations

from obris_mcp import routes
from obris_mcp.decorators import APIError, handle_api_errors

load_dotenv()

mcp = FastMCP("obris")
API_BASE = os.environ.get("OBRIS_API_URL", "https://api.obris.ai")
API_KEY = os.environ.get("OBRIS_API_KEY", "")

SOURCE_PARAM = "source"
SOURCE_MCP = "mcp"


async def api_request(path: str, params: dict = None) -> dict:
    async with httpx.AsyncClient() as client:
        resp = await client.get(
            f"{API_BASE}{path}",
            params=params,
            headers={"X-API-Key": API_KEY},
            timeout=30.0,
        )
        if resp.status_code == 401:
            raise APIError(
                "Invalid or missing API key. "
                "Ensure OBRIS_API_KEY is set in your environment."
            )
        if resp.status_code == 403:
            raise APIError(
                "Access denied. "
                "Your API key doesn't have permission for this resource."
            )
        resp.raise_for_status()
        return resp.json()


@mcp.tool(annotations=ToolAnnotations(title="List Projects", readOnlyHint=True, openWorldHint=True))
@handle_api_errors
async def list_projects() -> list[TextContent]:
    """List all projects available to the current user.
    Call this to help the user identify which project they want to work within.
    """
    data = await api_request(routes.projects())
    projects = data.get("results", [])
    if not projects:
        return [TextContent(type="text", text="No projects found.")]
    lines = [f"- {p['name']} (id: {p['id']})" for p in projects]
    return [TextContent(type="text", text="Projects:\n" + "\n".join(lines))]


@mcp.tool(annotations=ToolAnnotations(title="Get Project References", readOnlyHint=True, openWorldHint=True))
@handle_api_errors
async def get_project_references(project_id: str, cursor: str | None = None) -> list[TextContent]:
    """Get saved references for a specific project.
    Call list_projects first to get the project ID.

    References with a `summary` and no `content` have been chunked due to size.
    Call get_project_reference_details to fetch the full content for those.

    If has_more is true, call again with the returned cursor to get the next page.

    Args:
        project_id: The project ID (from list_projects).
        cursor: Pagination cursor from a previous call (omit for first page).
    """
    params = {SOURCE_PARAM: SOURCE_MCP}
    if cursor:
        params["cursor"] = cursor
    data = await api_request(routes.project_references(project_id), params=params)

    items = data.get("items", [])
    if not items:
        return [TextContent(type="text", text="No references found for this project.")]

    blocks = []
    for ref in items:
        parts = [f"### {ref['title']} (id: {ref['id']})"]
        if ref.get("file_url"):
            if ref.get("summary"):
                parts.append(ref["summary"])
            file_url = ref["file_url"]
            parsed = urlparse(file_url)
            base = f"{parsed.scheme}://{parsed.hostname}"
            parts.append(f"Image: {file_url}")
            parts.append(
                f"(You MUST download this image URL to reference it in the conversation. "
                f"If the download fails, the user may need to allow the domain {base}.)"
            )
        elif ref.get("summary") and not ref.get("content"):
            parts.append(f"Summary: {ref['summary']}")
            parts.append("(chunked — call get_project_reference_details for full content if this summary is relevant to the user's question)")
        elif ref.get("content"):
            parts.append(ref["content"])
        if ref.get("url"):
            parts.append(f"(source: {ref['url']})")
        blocks.append(TextContent(type="text", text="\n".join(parts)))

    if data.get("has_more"):
        blocks.append(TextContent(
            type="text",
            text=f'More references available. Call again with cursor="{data["next_cursor"]}" to get the next page.',
        ))

    return blocks


@mcp.tool(annotations=ToolAnnotations(title="Get Reference Details", readOnlyHint=True, openWorldHint=True))
@handle_api_errors
async def get_project_reference_details(project_id: str, reference_id: str, cursor: str | None = None) -> list[TextContent | ImageContent]:
    """Get the full chunked content for a project reference.
    Only needed when get_project_references shows a summary with no content,
    meaning the reference was too large and has been split into chunks.

    If has_more is true, call again with the returned cursor to get the next page.

    Args:
        project_id: The project ID.
        reference_id: The reference ID (from get_project_references).
        cursor: Pagination cursor from a previous call (omit for first page).
    """
    params = {SOURCE_PARAM: SOURCE_MCP}
    if cursor:
        params["cursor"] = cursor
    data = await api_request(
        routes.project_reference_chunks(project_id, reference_id),
        params=params,
    )

    items = data.get("items", [])
    if not items:
        return [TextContent(type="text", text="No content found for this reference.")]

    blocks = []
    for item in items:
        mime = item.get("content_type", "text/plain")
        if mime.startswith("image/"):
            blocks.append(ImageContent(
                type="image",
                data=item["content"],
                mimeType=mime,
            ))
        else:
            blocks.append(TextContent(type="text", text=item["content"]))

    if data.get("has_more"):
        blocks.append(TextContent(
            type="text",
            text=f'More chunks available. Call again with cursor="{data["next_cursor"]}" to get the next page.',
        ))

    return blocks


def main():
    print("Obris MCP server running on stdio", file=sys.stderr)
    mcp.run(transport="stdio")


if __name__ == "__main__":
    main()