"""Unit tests for BibResolveTool."""

from __future__ import annotations

from unittest.mock import MagicMock, patch

from axm_bib.models import BibEntry
from axm_bib.resolver import IdentifierType
from axm_bib.tools.bib_resolve import BibResolveTool


def _entry(key: str = "test2024", doi: str = "10.1234/test") -> BibEntry:
    return BibEntry(
        key=key,
        entry_type="article",
        raw=f"@article{{{key}, title={{Test}}}}",
        doi=doi,
        title="Test Paper",
        authors=["Author"],
    )


class TestBibResolveTool:
    """BibResolveTool tests."""

    def test_name(self) -> None:
        """Tool name is 'bib_resolve'."""
        assert BibResolveTool().name == "bib_resolve"

    def test_empty_ref_fails(self) -> None:
        """Empty ref returns error."""
        result = BibResolveTool().execute(ref="")
        assert result.success is False
        assert "required" in (result.error or "")

    def test_whitespace_ref_fails(self) -> None:
        """Whitespace-only ref returns error."""
        result = BibResolveTool().execute(ref="   ")
        assert result.success is False

    # ── Title rejection (AC1) ──────────────────────────────────────────

    @patch("axm_bib.tools.bib_resolve.detect_identifier")
    def test_resolve_rejects_title(self, mock_detect: MagicMock) -> None:
        """Title input returns error with 'bib_search' hint."""
        mock_detect.return_value = (IdentifierType.TITLE, "Some Paper")
        result = BibResolveTool().execute(ref="Some Paper")
        assert result.success is False
        assert "bib_search" in (result.error or "")

    # ── Single ref (no merge) ──────────────────────────────────────────

    @patch("axm_bib.tools.bib_resolve.BibPipeline")
    @patch("axm_bib.tools.bib_resolve.detect_identifier")
    def test_resolve_single_doi(
        self,
        mock_detect: MagicMock,
        mock_cls: MagicMock,
    ) -> None:
        """Single DOI → returns BibEntry from pipeline."""
        mock_detect.return_value = (IdentifierType.DOI, "10.1234/test")
        mock_cls.return_value.resolve.return_value = _entry()

        result = BibResolveTool().execute(ref="10.1234/test")
        assert result.success is True
        assert result.data is not None
        assert result.data["key"] == "test2024"

    @patch("axm_bib.tools.bib_resolve.BibPipeline")
    @patch("axm_bib.tools.bib_resolve.detect_identifier")
    def test_resolve_single_arxiv(
        self,
        mock_detect: MagicMock,
        mock_cls: MagicMock,
    ) -> None:
        """Single arXiv → returns BibEntry."""
        mock_detect.return_value = (IdentifierType.ARXIV, "2503.18813")
        mock_cls.return_value.resolve.return_value = _entry(key="arxiv2025")

        result = BibResolveTool().execute(ref="arXiv:2503.18813")
        assert result.success is True
        assert result.data is not None
        assert result.data["key"] == "arxiv2025"

    # ── Multi-ref merge (AC2) ──────────────────────────────────────────

    @patch("axm_bib.tools.bib_resolve.BibPipeline")
    @patch("axm_bib.tools.bib_resolve.check_coherence", return_value=True)
    @patch("axm_bib.tools.bib_resolve.merge_entries")
    @patch("axm_bib.tools.bib_resolve.detect_identifier")
    def test_resolve_multi_ref_merge(
        self,
        mock_detect: MagicMock,
        mock_merge: MagicMock,
        _mock_coherence: MagicMock,
        mock_cls: MagicMock,
    ) -> None:
        """Multiple refs for same paper → merge."""
        mock_detect.side_effect = [
            (IdentifierType.DOI, "10.1234/test"),
            (IdentifierType.ARXIV, "2503.18813"),
        ]
        e1, e2 = _entry(key="e1"), _entry(key="e2")
        mock_cls.return_value.resolve.side_effect = [e1, e2]
        merged = _entry(key="merged")
        mock_merge.return_value = merged

        result = BibResolveTool().execute(
            refs=["10.1234/test", "arXiv:2503.18813"],
        )
        assert result.success is True
        assert result.data is not None
        assert result.data["key"] == "merged"

    # ── Multi-ref incoherent (AC3) ─────────────────────────────────────

    @patch("axm_bib.tools.bib_resolve.BibPipeline")
    @patch("axm_bib.tools.bib_resolve.check_coherence", return_value=False)
    @patch("axm_bib.tools.bib_resolve.detect_identifier")
    def test_resolve_multi_ref_incoherent(
        self,
        mock_detect: MagicMock,
        _mock_coherence: MagicMock,
        mock_cls: MagicMock,
    ) -> None:
        """Incoherent refs → error."""
        mock_detect.side_effect = [
            (IdentifierType.DOI, "10.1/a"),
            (IdentifierType.DOI, "10.1/b"),
        ]
        mock_cls.return_value.resolve.side_effect = [
            _entry(key="a"),
            _entry(key="b"),
        ]

        result = BibResolveTool().execute(refs=["10.1/a", "10.1/b"])
        assert result.success is False
        assert "different papers" in (result.error or "")

    # ── Edge cases ─────────────────────────────────────────────────────

    @patch("axm_bib.tools.bib_resolve.BibPipeline")
    @patch("axm_bib.tools.bib_resolve.detect_identifier")
    def test_same_id_twice_deduped(
        self,
        mock_detect: MagicMock,
        mock_cls: MagicMock,
    ) -> None:
        """Same ID twice → dedup, single resolve, no merge."""
        mock_detect.return_value = (IdentifierType.DOI, "10.1234/test")
        mock_cls.return_value.resolve.return_value = _entry()

        result = BibResolveTool().execute(
            refs=["10.1234/test", "10.1234/test"],
        )
        assert result.success is True
        mock_cls.return_value.resolve.assert_called_once()

    @patch("axm_bib.tools.bib_resolve.BibPipeline")
    @patch("axm_bib.tools.bib_resolve.detect_identifier")
    def test_resolve_error_returns_failure(
        self,
        mock_detect: MagicMock,
        mock_cls: MagicMock,
    ) -> None:
        """Pipeline error → ToolResult(success=False)."""
        mock_detect.return_value = (IdentifierType.DOI, "10.0000/missing")
        mock_cls.return_value.resolve.side_effect = ValueError("not found")

        result = BibResolveTool().execute(ref="10.0000/missing")
        assert result.success is False
        assert "not found" in (result.error or "")
