import json
from contextlib import asynccontextmanager

import httpx
import respx
from fastmcp import Client, 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 tests.conftest import SAMPLE_CROSSREF_WORK, SAMPLE_S2_PAPER

S2_BASE = "https://api.semanticscholar.org/graph/v1"


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)
    return app


@respx.mock
async def test_get_bibtex_from_s2():
    respx.get(f"{S2_BASE}/paper/DOI:10.1038/nature12373").mock(
        return_value=httpx.Response(200, json=SAMPLE_S2_PAPER)
    )

    async with Client(_make_server()) as client:
        result = await client.call_tool("get_bibtex", {"doi": "10.1038/nature12373"})
        data = json.loads(result.content[0].text)
        assert data["source"] == "semantic_scholar"
        assert "@article" in data["bibtex"]


@respx.mock
async def test_get_bibtex_crossref_fallback():
    s2_no_bibtex = {**SAMPLE_S2_PAPER, "citationStyles": {}}
    respx.get(f"{S2_BASE}/paper/DOI:10.1038/nature12373").mock(
        return_value=httpx.Response(200, json=s2_no_bibtex)
    )
    respx.get("https://api.crossref.org/works/10.1038/nature12373").mock(
        return_value=httpx.Response(200, json={"message": SAMPLE_CROSSREF_WORK})
    )

    async with Client(_make_server()) as client:
        result = await client.call_tool("get_bibtex", {"doi": "10.1038/nature12373"})
        data = json.loads(result.content[0].text)
        assert data["source"] == "crossref"
        assert "Vaswani" in data["bibtex"]
        assert "10.1038/nature12373" in data["bibtex"]


@respx.mock
async def test_get_bibtex_not_found():
    respx.get(f"{S2_BASE}/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.call_tool("get_bibtex", {"doi": "10.0000/nope"})
        data = json.loads(result.content[0].text)
        assert "error" in data
