"""CrossRef API client — wraps habanero for DOI→BibTeX resolution.

Uses CrossRef content negotiation (Accept: application/x-bibtex)
via habanero's standalone content_negotiation() function.
"""

from __future__ import annotations

import re
from typing import Any

import bibtexparser
from habanero import Crossref
from habanero.cn.cn import content_negotiation

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

__all__ = ["CrossRefClient", "CrossRefError"]

_DOI_PATTERN = re.compile(r"^10\.\d{4,}/.+$")


class CrossRefError(Exception):
    """Error raised by CrossRef operations."""


class CrossRefClient:
    """CrossRef API client via habanero.

    Args:
        mailto: Email for polite pool (better rate limits).
    """

    def __init__(self, mailto: str = "") -> None:
        self._mailto = mailto
        self._cr = Crossref(mailto=mailto) if mailto else Crossref()

    def doi_to_bibtex(self, doi: str) -> BibEntry:
        """Resolve a DOI to a clean BibTeX entry.

        Args:
            doi: Digital Object Identifier (e.g. "10.1145/3292500.3330919").

        Returns:
            Parsed BibEntry with normalized key.

        Raises:
            CrossRefError: On invalid DOI, network error, or timeout.
        """
        doi = doi.strip()

        if not _DOI_PATTERN.match(doi):
            msg = f"Invalid DOI format: '{doi}'"
            raise CrossRefError(msg)

        try:
            bibtex_str: str = content_negotiation(ids=doi, format="bibtex")
        except Exception as exc:
            msg = f"CrossRef resolution failed for DOI '{doi}': {exc}"
            raise CrossRefError(msg) from exc

        return self._parse_bibtex(bibtex_str, doi)

    def search_by_title(self, title: str, limit: int = 5) -> list[Paper]:
        """Search papers by title via CrossRef.

        Args:
            title: Search query.
            limit: Maximum results.

        Returns:
            List of Paper objects.
        """
        try:
            result: dict[str, Any] = self._cr.works(query=title, limit=limit)
        except Exception as exc:
            msg = f"CrossRef search failed: {exc}"
            raise CrossRefError(msg) from exc

        items = result.get("message", {}).get("items", [])
        papers: list[Paper] = []

        for item in items:
            authors = self._extract_authors(item)
            title_str = (item.get("title") or [""])[0]
            year = self._extract_year(item)

            papers.append(
                Paper(
                    title=title_str,
                    authors=authors if authors else ["Unknown"],
                    year=year,
                    doi=item.get("DOI"),
                    source="crossref",
                )
            )

        return papers

    def _parse_bibtex(self, bibtex_str: str, doi: str) -> BibEntry:
        """Parse raw BibTeX and normalize the key."""
        library = bibtexparser.loads(bibtex_str)

        if not library.entries:
            return BibEntry(
                key=f"unknown{doi.replace('/', '_').replace('.', '_')}",
                entry_type="misc",
                raw=bibtex_str,
                doi=doi,
            )

        entry = library.entries[0]

        # bibtexparser v1.x: entries are dicts
        authors_str = entry.get("author", "")
        authors = [a.strip() for a in authors_str.split(" and ")] 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")

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

        # Rebuild BibTeX with new 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,
        )

    @staticmethod
    def _extract_authors(item: dict[str, Any]) -> list[str]:
        """Extract author names from CrossRef item."""
        authors_raw = item.get("author", [])
        names: list[str] = []
        for a in authors_raw:
            given = a.get("given", "")
            family = a.get("family", "")
            if given and family:
                names.append(f"{given} {family}")
            elif family:
                names.append(family)
        return names

    @staticmethod
    def _extract_year(item: dict[str, Any]) -> int | None:
        """Extract publication year from CrossRef item."""
        for date_key in ("published-print", "published-online", "created"):
            date_parts = item.get(date_key, {}).get("date-parts", [[]])
            if date_parts and date_parts[0]:
                try:
                    return int(date_parts[0][0])
                except (ValueError, IndexError, TypeError):
                    continue
        return None
