import json
from contextlib import asynccontextmanager

import httpx
import respx
from fastmcp import Client, Context, FastMCP

from mcsci_hub.cache import TTLCache
from mcsci_hub.clients.crossref import CrossRefClient
from mcsci_hub.clients.openalex import OpenAlexClient
from mcsci_hub.clients.scihub import SciHubClient
from mcsci_hub.clients.semantic_scholar import SemanticScholarClient
from mcsci_hub.tools import register_all
from mcsci_hub.tools.paper import aggregate_paper
from tests.conftest import (
    SAMPLE_CROSSREF_WORK,
    SAMPLE_OPENALEX_WORK,
    SAMPLE_S2_PAPER,
)


def _make_server() -> FastMCP:
    @asynccontextmanager
    async def test_lifespan(server: FastMCP):
        async with httpx.AsyncClient() as http_client:
            yield {
                "crossref": CrossRefClient(http_client, mailto="test@test.com"),
                "openalex": OpenAlexClient(http_client, mailto="test@test.com"),
                "s2": SemanticScholarClient(http_client),
                "scihub": SciHubClient(http_client, mirrors=["sci-hub.test"]),
                "cache": TTLCache(max_size=100, ttl=60),
            }

    app = FastMCP("test-mcsci-hub", lifespan=test_lifespan)
    register_all(app)

    # Register the dynamic resource (same as in server.py)
    @app.resource("doi://{doi_prefix}/{doi_suffix}")
    async def paper_resource(doi_prefix: str, doi_suffix: str, ctx: Context) -> str:
        """Paper metadata by DOI."""
        doi = f"{doi_prefix}/{doi_suffix}"
        state = ctx.request_context.lifespan_context

        cache: TTLCache = state["cache"]
        cached = cache.get(f"paper:{doi}")
        if cached:
            return cached

        paper = await aggregate_paper(
            doi,
            openalex=state["openalex"],
            s2=state["s2"],
            crossref=state["crossref"],
            scihub=state["scihub"],
        )

        if paper is None:
            return json.dumps({"error": f"Paper not found for DOI: {doi}"})

        result = json.dumps(paper.model_dump(), indent=2)
        cache.set(f"paper:{doi}", result)
        return result

    return app


@respx.mock
async def test_read_paper_resource():
    respx.get("https://api.openalex.org/works/doi:10.1038/nature12373").mock(
        return_value=httpx.Response(200, json=SAMPLE_OPENALEX_WORK)
    )
    respx.get("https://api.semanticscholar.org/graph/v1/paper/DOI:10.1038/nature12373").mock(
        return_value=httpx.Response(200, json=SAMPLE_S2_PAPER)
    )
    respx.get("https://api.crossref.org/works/10.1038/nature12373").mock(
        return_value=httpx.Response(200, json={"message": SAMPLE_CROSSREF_WORK})
    )
    respx.get("https://sci-hub.test/10.1038/nature12373").mock(
        return_value=httpx.Response(200, text="<html><body>no pdf</body></html>")
    )

    async with Client(_make_server()) as client:
        result = await client.read_resource("doi://10.1038/nature12373")
        data = json.loads(result[0].text)
        assert data["doi"] == "10.1038/nature12373"
        assert data["title"] == "Attention Is All You Need"
        assert "crossref" in data["sources"]
        assert "openalex" in data["sources"]
        assert "semantic_scholar" in data["sources"]


@respx.mock
async def test_read_paper_resource_not_found():
    respx.get("https://api.openalex.org/works/doi:10.0000/nope").mock(
        return_value=httpx.Response(404)
    )
    respx.get("https://api.semanticscholar.org/graph/v1/paper/DOI:10.0000/nope").mock(
        return_value=httpx.Response(404)
    )
    respx.get("https://api.crossref.org/works/10.0000/nope").mock(
        return_value=httpx.Response(404)
    )

    async with Client(_make_server()) as client:
        result = await client.read_resource("doi://10.0000/nope")
        data = json.loads(result[0].text)
        assert "error" in data


@respx.mock
async def test_resource_template_listed():
    async with Client(_make_server()) as client:
        templates = await client.list_resource_templates()
        uri_templates = [t.uriTemplate for t in templates]
        assert "doi://{doi_prefix}/{doi_suffix}" in uri_templates
