"""DBLP API client — search + native BibTeX resolution.

Provides title/venue-based paper discovery from the DBLP computer science
bibliography (6M+ publications). Uses the public REST API directly
(no third-party wrapper). Native BibTeX via ``dblp.org/rec/{key}.bib``.
"""

from __future__ import annotations

import logging
import re
from typing import Any

import bibtexparser

from axm_bib.http import request_with_retry
from axm_bib.keys import generate_key
from axm_bib.models import BibEntry, Paper

logger = logging.getLogger(__name__)

__all__ = ["DBLPClient", "DBLPError"]

_SEARCH_URL = "https://dblp.org/search/publ/api"
_REC_URL = "https://dblp.org/rec"


class DBLPError(Exception):
    """Error raised by DBLP operations."""


class DBLPClient:
    """DBLP API client (direct httpx).

    No authentication required. Uses the public REST API
    for search and native BibTeX endpoints for resolution.
    """

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

        Supports venue filtering via ``venue:NeurIPS:`` syntax in the query.

        Args:
            query: Search query string (may include ``venue:`` filter).
            limit: Maximum number of results.

        Returns:
            List of normalized Paper objects.

        Raises:
            DBLPError: On API failure.
        """
        if not query.strip():
            return []

        params: dict[str, str | int] = {
            "q": query,
            "h": limit,
            "format": "json",
        }

        try:
            resp = request_with_retry(
                "GET",
                _SEARCH_URL,
                params=params,
                timeout=30.0,
            )
        except Exception as exc:
            msg = f"DBLP search failed: {exc}"
            raise DBLPError(msg) from exc

        hits_data = resp.json().get("result", {}).get("hits", {})
        total = int(hits_data.get("@total", "0"))
        if total == 0:
            return []

        raw_hits = hits_data.get("hit", [])

        papers: list[Paper] = []
        for hit in raw_hits:
            paper = self._normalize(hit.get("info", {}))
            if paper is not None:
                papers.append(paper)

        return papers

    def bibtex(self, dblp_key: str) -> BibEntry:
        """Fetch native BibTeX for a DBLP record.

        Args:
            dblp_key: DBLP record key (e.g. ``conf/nips/VaswaniSPUJGKP17``).

        Returns:
            Parsed and normalized BibEntry.

        Raises:
            DBLPError: On fetch failure or parse error.
        """
        url = f"{_REC_URL}/{dblp_key}.bib"

        try:
            resp = request_with_retry("GET", url, timeout=30.0)
        except Exception as exc:
            msg = f"DBLP BibTeX fetch failed for '{dblp_key}': {exc}"
            raise DBLPError(msg) from exc

        return self._parse_bibtex(resp.text, dblp_key)

    @staticmethod
    def _normalize(info: dict[str, Any]) -> Paper | None:
        """Convert a DBLP search hit info dict to a Paper model."""
        try:
            title = info.get("title", "Untitled")
            # DBLP titles often end with "." — strip it
            if title.endswith("."):
                title = title[:-1]

            # Authors: can be dict (single) or list
            raw_authors = info.get("authors", {}).get("author", [])
            if isinstance(raw_authors, dict):
                raw_authors = [raw_authors]
            authors = [a.get("text", "Unknown") for a in raw_authors if a]
            if not authors:
                authors = ["Unknown"]

            year: int | None = None
            year_str = info.get("year", "")
            if year_str:
                try:
                    year = int(year_str)
                except (ValueError, TypeError):
                    pass

            return Paper(
                title=title,
                authors=authors,
                year=year,
                doi=info.get("doi"),
                dblp_key=info.get("key"),
                venue=info.get("venue"),
                source="dblp",
            )
        except Exception:  # noqa: BLE001
            logger.debug(
                "Failed to normalize DBLP result: %s",
                info.get("title", "?"),
                exc_info=True,
            )
            return None

    @staticmethod
    def _parse_bibtex(bibtex_str: str, dblp_key: str) -> BibEntry:
        """Parse raw BibTeX from DBLP and normalize the key.

        Args:
            bibtex_str: Raw BibTeX string from DBLP.
            dblp_key: Original DBLP record key.

        Returns:
            Normalized BibEntry.

        Raises:
            DBLPError: On parse failure.
        """
        try:
            library = bibtexparser.loads(bibtex_str)
        except Exception as exc:
            msg = f"Failed to parse DBLP BibTeX for '{dblp_key}': {exc}"
            raise DBLPError(msg) from exc

        if not library.entries:
            msg = f"No entries found in DBLP BibTeX for '{dblp_key}'"
            raise DBLPError(msg)

        entry = library.entries[0]

        # Extract fields
        authors_str = entry.get("author", "")
        authors = (
            [a.strip() for a in re.split(r"\s+and\s+", authors_str)]
            if authors_str
            else []
        )

        title = entry.get("title", "")

        year: int | None = None
        year_str = entry.get("year", "")
        if year_str:
            try:
                year = int(year_str)
            except (ValueError, TypeError):
                pass

        entry_type = entry.get("ENTRYTYPE", "misc")
        doi = entry.get("doi")

        # Generate normalized key
        key = generate_key(authors if authors else ["unknown"], year, title)

        # Rebuild BibTeX with normalized key
        raw_with_key = re.sub(
            r"@(\w+)\{[^,]*,",
            rf"@\1{{{key},",
            bibtex_str,
            count=1,
        )

        return BibEntry(
            key=key,
            entry_type=entry_type,
            raw=raw_with_key,
            doi=doi,
            title=title or None,
            authors=authors,
            year=year,
        )
