import json
import re
from pathlib import Path

from fastmcp import Context, FastMCP

from mcsci_hub.clients.openalex import OpenAlexClient
from mcsci_hub.clients.scihub import SciHubClient
from mcsci_hub.config import settings
from mcsci_hub.models import PDFResult


def _doi_to_filename(doi: str) -> str:
    """Convert DOI to safe filename — only alphanumeric, underscore, hyphen."""
    return re.sub(r"[^\w\-]", "_", doi) + ".pdf"


def register(mcp: FastMCP) -> None:
    @mcp.tool()
    async def fetch_pdf(ctx: Context, doi: str) -> str:
        """Download the full-text PDF for a paper by DOI.

        Tries open access sources first (legal, free), then falls back to
        Sci-Hub mirrors. Saves the PDF locally and returns the file path.

        After fetching, use mcp-pdf tools to parse the PDF:
        - extract_text: Get all text from the PDF
        - extract_tables: Extract tabular data
        - pdf_info: Get PDF metadata and page count

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

        openalex: OpenAlexClient = state["openalex"]
        scihub: SciHubClient = state["scihub"]

        save_dir = Path(settings.pdf_save_dir).resolve()
        save_path = (save_dir / _doi_to_filename(doi)).resolve()

        # Path containment check — prevent traversal outside save directory
        if not save_path.is_relative_to(save_dir):
            return json.dumps({
                "status": "error",
                "doi": doi,
                "message": "Invalid DOI produced unsafe file path.",
            }, indent=2)

        # Already downloaded?
        if save_path.exists() and save_path.stat().st_size > 0:
            return json.dumps(PDFResult(
                file_path=str(save_path), source="cached", doi=doi,
            ).model_dump(), indent=2)

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

        # Step 1: Check OpenAlex for OA URL
        oa_url = ""
        oa_work = await openalex.get_work(doi)
        if oa_work:
            oa_url = OpenAlexClient.get_oa_url(oa_work)

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

        # Step 2: Try OA download
        if oa_url:
            success = await scihub.download_pdf(
                oa_url, save_path, max_size=settings.max_pdf_size,
            )
            if success:
                return json.dumps(PDFResult(
                    file_path=str(save_path), source="open_access", doi=doi,
                ).model_dump(), indent=2)

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

        # Step 3: Fall back to Sci-Hub
        pdf_url = await scihub.find_pdf_url(doi)
        if pdf_url:
            success = await scihub.download_pdf(
                pdf_url, save_path, max_size=settings.max_pdf_size,
            )
            if success:
                return json.dumps(PDFResult(
                    file_path=str(save_path), source="scihub", doi=doi,
                ).model_dump(), indent=2)

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

        return json.dumps({
            "status": "error",
            "doi": doi,
            "message": "Could not find or download PDF from any source.",
            "hint": "The paper may not be available through open access or Sci-Hub mirrors.",
        }, indent=2)
