"""Unit tests for BibGraphTool — input validation + error paths."""

from __future__ import annotations

from unittest.mock import MagicMock, patch

from axm.tools.base import ToolResult

from axm_bib.models import Paper
from axm_bib.tools.bib_graph import BibGraphTool


def _make_paper(**overrides: object) -> Paper:
    defaults: dict[str, object] = {
        "title": "Test Paper",
        "authors": ["Alice"],
        "year": 2024,
        "doi": "10.1234/test",
        "source": "semantic_scholar",
    }
    defaults.update(overrides)
    return Paper(**defaults)  # type: ignore[arg-type]


class TestBibGraphToolValidation:
    """Input validation tests."""

    def test_empty_paper_id(self) -> None:
        """paper_id='' → success=False."""
        tool = BibGraphTool()
        result = tool.execute(paper_id="")
        assert result.success is False
        assert result.error is not None
        assert "paper_id" in result.error.lower()

    def test_whitespace_paper_id(self) -> None:
        """paper_id='  ' → success=False."""
        tool = BibGraphTool()
        result = tool.execute(paper_id="   ")
        assert result.success is False
        assert "paper_id" in result.error.lower()  # type: ignore[union-attr]

    def test_invalid_direction(self) -> None:
        """direction='invalid' → success=False."""
        tool = BibGraphTool()
        result = tool.execute(paper_id="10.1234/test", direction="invalid")
        assert result.success is False
        assert "direction" in result.error.lower()  # type: ignore[union-attr]


class TestBibGraphToolSuccess:
    """Successful execution (mocked pipeline)."""

    @patch("axm_bib.tools.bib_graph.BibPipeline")
    def test_execute_citations(self, mock_cls: MagicMock) -> None:
        """direction='citations' → returns papers."""
        mock_cls.return_value.graph.return_value = [
            _make_paper(title="Citing Paper"),
        ]
        tool = BibGraphTool()
        result = tool.execute(paper_id="10.1234/test", direction="citations")
        assert result.success is True
        assert result.data["count"] == 1
        assert result.data["papers"][0]["title"] == "Citing Paper"
        mock_cls.return_value.graph.assert_called_once_with(
            "10.1234/test", direction="citations", limit=20
        )

    @patch("axm_bib.tools.bib_graph.BibPipeline")
    def test_execute_references(self, mock_cls: MagicMock) -> None:
        """direction='references' → delegates correctly."""
        mock_cls.return_value.graph.return_value = [
            _make_paper(title="Referenced Paper"),
        ]
        tool = BibGraphTool()
        result = tool.execute(paper_id="10.1234/test", direction="references")
        assert result.success is True
        assert result.data["papers"][0]["title"] == "Referenced Paper"
        mock_cls.return_value.graph.assert_called_once_with(
            "10.1234/test", direction="references", limit=20
        )

    @patch("axm_bib.tools.bib_graph.BibPipeline")
    def test_execute_similar(self, mock_cls: MagicMock) -> None:
        """direction='similar' → delegates correctly."""
        mock_cls.return_value.graph.return_value = [
            _make_paper(title="Similar Paper"),
        ]
        tool = BibGraphTool()
        result = tool.execute(paper_id="10.1234/test", direction="similar")
        assert result.success is True
        assert result.data["papers"][0]["title"] == "Similar Paper"

    @patch("axm_bib.tools.bib_graph.BibPipeline")
    def test_result_is_tool_result(self, mock_cls: MagicMock) -> None:
        """Return type is ToolResult."""
        mock_cls.return_value.graph.return_value = []
        tool = BibGraphTool()
        result = tool.execute(paper_id="10.1234/test")
        assert isinstance(result, ToolResult)

    @patch("axm_bib.tools.bib_graph.BibPipeline")
    def test_hint_present(self, mock_cls: MagicMock) -> None:
        """Success result includes usage hint."""
        mock_cls.return_value.graph.return_value = []
        tool = BibGraphTool()
        result = tool.execute(paper_id="10.1234/test")
        assert result.hint is not None

    @patch("axm_bib.tools.bib_graph.BibPipeline")
    def test_custom_limit(self, mock_cls: MagicMock) -> None:
        """Custom limit is forwarded to pipeline."""
        mock_cls.return_value.graph.return_value = []
        tool = BibGraphTool()
        tool.execute(paper_id="10.1234/test", limit=50)
        mock_cls.return_value.graph.assert_called_once_with(
            "10.1234/test", direction="citations", limit=50
        )


class TestBibGraphToolErrors:
    """Error handling tests."""

    @patch("axm_bib.tools.bib_graph.BibPipeline")
    def test_pipeline_exception(self, mock_cls: MagicMock) -> None:
        """Pipeline exception → success=False with error message."""
        mock_cls.return_value.graph.side_effect = Exception("API timeout")
        tool = BibGraphTool()
        result = tool.execute(paper_id="10.1234/test")
        assert result.success is False
        assert "API timeout" in result.error  # type: ignore[operator]

    @patch("axm_bib.tools.bib_graph.BibPipeline")
    def test_pipeline_runtime_error(self, mock_cls: MagicMock) -> None:
        """RuntimeError propagated correctly."""
        mock_cls.return_value.graph.side_effect = RuntimeError("not found")
        tool = BibGraphTool()
        result = tool.execute(paper_id="10.1234/test")
        assert result.success is False
        assert "not found" in result.error  # type: ignore[operator]


class TestBibGraphToolNameProperty:
    """Tool name registration."""

    def test_name_is_bib_graph(self) -> None:
        """Tool name property returns 'bib_graph'."""
        assert BibGraphTool().name == "bib_graph"
