"""Semantic Scholar API client — direct httpx calls.

Provides title-based paper discovery from the Semantic Scholar
academic graph (200M+ papers). Uses the public REST API directly
instead of the ``semanticscholar`` wrapper library.
"""

from __future__ import annotations

from typing import Any

import httpx

from axm_bib.models import Paper

__all__ = ["SemanticScholarClient", "SemanticScholarError"]

_BASE_URL = "https://api.semanticscholar.org/graph/v1"
_SEARCH_FIELDS = ",".join(
    [
        "title",
        "authors",
        "year",
        "externalIds",
        "abstract",
        "venue",
        "citationCount",
        "isOpenAccess",
        "openAccessPdf",
    ]
)


class SemanticScholarError(Exception):
    """Error raised by Semantic Scholar operations."""


class SemanticScholarClient:
    """Semantic Scholar API client (direct httpx).

    Args:
        api_key: Optional API key for higher rate limits.
    """

    def __init__(self, api_key: str | None = None) -> None:
        self._headers: dict[str, str] = {}
        if api_key:
            self._headers["x-api-key"] = api_key

    def search_paper(self, title: str, limit: int = 5) -> list[Paper]:
        """Search papers by title/keywords.

        Args:
            title: Search query string.
            limit: Maximum number of results.

        Returns:
            List of normalized Paper objects.

        Raises:
            SemanticScholarError: On API failure.
        """
        if not title.strip():
            return []

        params: dict[str, str | int] = {
            "query": title,
            "limit": limit,
            "fields": _SEARCH_FIELDS,
        }

        try:
            resp = httpx.get(
                f"{_BASE_URL}/paper/search",
                params=params,
                headers=self._headers,
                timeout=30.0,
            )
            resp.raise_for_status()
        except Exception as exc:
            msg = f"Semantic Scholar search failed: {exc}"
            raise SemanticScholarError(msg) from exc

        data = resp.json().get("data", [])

        papers: list[Paper] = []
        for item in data:
            paper = self._normalize(item)
            if paper is not None:
                papers.append(paper)

        return papers

    @staticmethod
    def _normalize(item: dict[str, Any]) -> Paper | None:
        """Convert a Semantic Scholar result dict to a Paper model."""
        try:
            external_ids = item.get("externalIds") or {}
            doi = external_ids.get("DOI")
            arxiv_id = external_ids.get("ArXiv")

            raw_authors = item.get("authors") or []
            authors = [a.get("name", "Unknown") for a in raw_authors if a]
            if not authors:
                authors = ["Unknown"]

            oa_pdf = item.get("openAccessPdf")
            pdf_url: str | None = None
            if isinstance(oa_pdf, dict):
                pdf_url = oa_pdf.get("url")

            return Paper(
                title=item.get("title") or "Untitled",
                authors=authors,
                year=item.get("year"),
                doi=doi,
                arxiv_id=arxiv_id,
                abstract=item.get("abstract"),
                venue=item.get("venue"),
                citation_count=item.get("citationCount"),
                open_access_pdf=pdf_url,
                source="semantic_scholar",
            )
        except Exception:
            return None
