import asyncio
import json

from fastmcp import 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.models import PaperMetadata


async def aggregate_paper(
    doi: str,
    openalex: OpenAlexClient,
    s2: SemanticScholarClient,
    crossref: CrossRefClient,
    scihub: SciHubClient,
) -> PaperMetadata | None:
    """Query all sources in parallel and merge into a single PaperMetadata."""
    oa_result, s2_result, cr_result = await asyncio.gather(
        openalex.get_work(doi),
        s2.get_paper(doi),
        crossref.get_work(doi),
        return_exceptions=True,
    )

    if isinstance(oa_result, BaseException):
        oa_result = None
    if isinstance(s2_result, BaseException):
        s2_result = None
    if isinstance(cr_result, BaseException):
        cr_result = None

    if not any([oa_result, s2_result, cr_result]):
        return None

    sources = []
    paper = PaperMetadata(doi=doi)

    if cr_result:
        sources.append("crossref")
        paper.title = CrossRefClient.parse_title(cr_result) or paper.title
        paper.authors = CrossRefClient.parse_authors(cr_result) or paper.authors
        paper.year = CrossRefClient.parse_year(cr_result) or paper.year
        paper.journal = CrossRefClient.parse_journal(cr_result) or paper.journal
        paper.volume = cr_result.get("volume", "") or ""
        paper.issue = cr_result.get("issue", "") or ""
        paper.pages = cr_result.get("page", "") or ""

    if oa_result:
        sources.append("openalex")
        paper.title = paper.title or OpenAlexClient.parse_title(oa_result)
        paper.authors = paper.authors or OpenAlexClient.parse_authors(oa_result)
        paper.year = paper.year or OpenAlexClient.parse_year(oa_result)
        paper.journal = paper.journal or OpenAlexClient.parse_journal(oa_result)
        abstract_idx = oa_result.get("abstract_inverted_index")
        paper.abstract = OpenAlexClient.decode_abstract(abstract_idx)
        paper.open_access = OpenAlexClient.is_oa(oa_result)
        paper.open_access_url = OpenAlexClient.get_oa_url(oa_result)
        paper.citation_count = oa_result.get("cited_by_count") or paper.citation_count

    if s2_result:
        sources.append("semantic_scholar")
        paper.title = paper.title or (s2_result.get("title") or "")
        paper.authors = paper.authors or SemanticScholarClient.parse_authors(s2_result)
        paper.year = paper.year or s2_result.get("year")
        paper.journal = paper.journal or (s2_result.get("venue") or "")
        paper.abstract = paper.abstract or (s2_result.get("abstract") or "")
        paper.citation_count = paper.citation_count or s2_result.get("citationCount")
        paper.influential_citation_count = s2_result.get("influentialCitationCount")
        paper.fields_of_study = s2_result.get("fieldsOfStudy") or []
        if not paper.open_access_url:
            paper.open_access_url = SemanticScholarClient.get_oa_url(s2_result)
            if paper.open_access_url:
                paper.open_access = True

    scihub_url = await scihub.find_pdf_url(doi)
    paper.pdf_available_on_scihub = scihub_url is not None

    paper.sources = sources
    return paper


def register(mcp: FastMCP) -> None:
    @mcp.tool()
    async def get_paper(ctx: Context, doi: str) -> str:
        """Get full paper details by DOI, aggregated from multiple academic databases.

        Returns title, authors, year, journal, abstract, citation counts, fields
        of study, open access info, and Sci-Hub availability. Data is merged from
        OpenAlex, Semantic Scholar, and CrossRef for maximum completeness.

        Use fetch_pdf(doi) to download the full-text PDF.
        Use get_citations(doi) or get_references(doi) to explore the citation graph.
        Use get_bibtex(doi) to get a BibTeX citation entry.

        Args:
            doi: The DOI (Digital Object Identifier) of the paper, e.g. '10.1038/nature12373'
        """
        state = ctx.request_context.lifespan_context

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

        await ctx.report_progress(progress=0, total=3)

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

        await ctx.report_progress(progress=2, total=3)

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

        await ctx.report_progress(progress=3, total=3)

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