"""Unit tests for axm_bib.venue — VenueEnricher, infer_entry_type, patch_raw_bibtex."""

from __future__ import annotations

from unittest.mock import MagicMock

from axm_bib.models import BibEntry, Paper
from axm_bib.venue import VenueEnricher, infer_entry_type, patch_raw_bibtex

# ── Helpers ──────────────────────────────────────────────────────────────


def _bib_entry(
    *,
    arxiv_id: str = "2301.00001",
    entry_type: str = "misc",
    venue: str | None = None,
) -> BibEntry:
    raw = (
        f"@{entry_type}{{test2024,\n"
        f"  author = {{Test Author}},\n"
        f"  title = {{Test Paper}},\n"
        f"  year = {{2024}}\n"
        f"}}"
    )
    return BibEntry(
        key="test2024",
        entry_type=entry_type,
        raw=raw,
        doi=None,
        title="Test Paper",
        authors=["Test Author"],
        year=2024,
    )


def _paper(title: str = "Test", venue: str | None = None) -> Paper:
    return Paper(
        title=title,
        authors=["Author"],
        year=2024,
        venue=venue,
        source="semantic_scholar",
    )


# ── infer_entry_type ─────────────────────────────────────────────────────


class TestInferEntryType:
    """Tests for infer_entry_type."""

    def test_conference(self) -> None:
        assert infer_entry_type("International Conference on ML") == "inproceedings"

    def test_workshop(self) -> None:
        assert infer_entry_type("NeurIPS Workshop") == "inproceedings"

    def test_journal(self) -> None:
        assert infer_entry_type("Nature Machine Intelligence (Journal)") == "article"

    def test_symposium(self) -> None:
        assert infer_entry_type("AAAI Symposium") == "inproceedings"

    def test_default_named_venue(self) -> None:
        """Named venues without keywords default to inproceedings."""
        assert infer_entry_type("ICML 2024") == "inproceedings"


# ── patch_raw_bibtex ─────────────────────────────────────────────────────


class TestPatchRawBibtex:
    """Tests for patch_raw_bibtex."""

    def test_promotes_type(self) -> None:
        raw = "@misc{key,\n  title = {T}\n}"
        result = patch_raw_bibtex(raw, "ICML", "misc", "inproceedings")
        assert result.startswith("@inproceedings{key,")

    def test_injects_venue(self) -> None:
        raw = "@misc{key,\n  title = {T}\n}"
        result = patch_raw_bibtex(raw, "NeurIPS", "misc", "inproceedings")
        assert "booktitle = {NeurIPS}" in result

    def test_case_insensitive_type(self) -> None:
        raw = "@Misc{key,\n  title = {T}\n}"
        result = patch_raw_bibtex(raw, "ICML", "misc", "inproceedings")
        assert "@inproceedings{" in result


# ── VenueEnricher ────────────────────────────────────────────────────────


class TestVenueEnricher:
    """Tests for VenueEnricher."""

    def test_enriches_with_s2_venue(self) -> None:
        scholar = MagicMock()
        scholar.get_paper.return_value = _paper(venue="ICML")
        dblp = MagicMock()
        enricher = VenueEnricher(scholar=scholar, dblp=dblp)

        entry = _bib_entry()
        result = enricher.enrich("2301.00001", entry)

        assert result.entry_type == "inproceedings"
        assert "booktitle = {ICML}" in result.raw

    def test_falls_back_to_dblp(self) -> None:
        scholar = MagicMock()
        scholar.get_paper.return_value = _paper(venue=None)
        dblp = MagicMock()
        dblp.search.return_value = [_paper(venue="NeurIPS")]
        enricher = VenueEnricher(scholar=scholar, dblp=dblp)

        entry = _bib_entry()
        result = enricher.enrich("2301.00001", entry)

        assert "booktitle = {NeurIPS}" in result.raw

    def test_returns_original_when_no_venue(self) -> None:
        scholar = MagicMock()
        scholar.get_paper.return_value = _paper(venue=None)
        dblp = MagicMock()
        dblp.search.return_value = []
        enricher = VenueEnricher(scholar=scholar, dblp=dblp)

        entry = _bib_entry()
        result = enricher.enrich("2301.00001", entry)

        assert result is entry  # unchanged

    def test_skips_corr_in_dblp(self) -> None:
        scholar = MagicMock()
        scholar.get_paper.return_value = _paper(venue=None)
        dblp = MagicMock()
        # First call (by arXiv ID) returns CoRR, second (by title) returns real venue
        dblp.search.side_effect = [
            [_paper(venue="CoRR")],
            [_paper(venue="ICLR")],
        ]
        enricher = VenueEnricher(scholar=scholar, dblp=dblp)

        entry = _bib_entry()
        result = enricher.enrich("2301.00001", entry)

        assert "booktitle = {ICLR}" in result.raw

    def test_s2_error_falls_through(self) -> None:
        from axm_bib.clients.semantic_scholar import SemanticScholarError

        scholar = MagicMock()
        scholar.get_paper.side_effect = SemanticScholarError("rate limited")
        dblp = MagicMock()
        dblp.search.return_value = [_paper(venue="AAAI")]
        enricher = VenueEnricher(scholar=scholar, dblp=dblp)

        entry = _bib_entry()
        result = enricher.enrich("2301.00001", entry)

        assert "booktitle = {AAAI}" in result.raw
