"""Unit tests for BibPdfTool — 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 DownloadResult, ExtractionResult, FigureInfo
from axm_bib.tools.bib_pdf import BibPdfTool
from tests.conftest import SAMPLE_DOI


class TestBibPdfToolValidation:
    """Input validation tests."""

    def test_empty_ref(self) -> None:
        """Empty ref → success=False."""
        tool = BibPdfTool()
        result = tool.execute(ref="")
        assert result.success is False
        assert result.error == "identifier is required"

    def test_whitespace_ref(self) -> None:
        """Whitespace-only ref → success=False."""
        tool = BibPdfTool()
        result = tool.execute(ref="   ")
        assert result.success is False
        assert result.error == "identifier is required"


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

    @patch("axm_bib.tools.bib_pdf.BibPipeline")
    def test_not_open_access(self, mock_cls: MagicMock) -> None:
        """Pipeline returns None → not open access."""
        mock_cls.return_value.download_pdf.return_value = None
        tool = BibPdfTool()
        result = tool.execute(ref=SAMPLE_DOI)
        assert result.success is True
        assert result.data["is_open_access"] is False
        assert result.data["path"] is None

    @patch("axm_bib.tools.bib_pdf.BibPipeline")
    def test_success_with_extraction(self, mock_cls: MagicMock) -> None:
        """Full pipeline with PDF + extraction."""
        extraction = ExtractionResult(
            source_pdf="/tmp/paper.pdf",
            output_dir="/tmp/out",
            markdown_path="/tmp/out/content.md",
            figures=[
                FigureInfo(
                    index=1,
                    filename="fig_001.png",
                    page=0,
                    width=800,
                    height=600,
                    xref=42,
                ),
            ],
            page_count=10,
            word_count=5000,
            char_count=30000,
        )
        download = DownloadResult(
            doi=SAMPLE_DOI,
            path="/tmp/paper.pdf",
            size_bytes=2048,
            is_open_access=True,
            paper_dir="/tmp/papers/smith2024deep",
            bibtex_path="/tmp/papers/smith2024deep/paper.bib",
            extraction=extraction,
        )
        mock_cls.return_value.download_pdf.return_value = download
        tool = BibPdfTool()
        result = tool.execute(ref=SAMPLE_DOI)
        assert result.success is True
        assert result.data["is_open_access"] is True
        assert result.data["path"] == "/tmp/paper.pdf"
        assert result.data["size_bytes"] == 2048
        assert result.data["markdown_path"] == "/tmp/out/content.md"
        assert result.data["page_count"] == 10
        assert result.data["figure_count"] == 1

    @patch("axm_bib.tools.bib_pdf.BibPipeline")
    def test_success_without_extraction(self, mock_cls: MagicMock) -> None:
        """Download without extraction → no markdown fields."""
        download = DownloadResult(
            doi=SAMPLE_DOI,
            path="/tmp/paper.pdf",
            size_bytes=1024,
            is_open_access=True,
        )
        mock_cls.return_value.download_pdf.return_value = download
        tool = BibPdfTool()
        result = tool.execute(ref=SAMPLE_DOI)
        assert result.success is True
        assert "markdown_path" not in result.data

    @patch("axm_bib.tools.bib_pdf.BibPipeline")
    def test_result_is_tool_result(self, mock_cls: MagicMock) -> None:
        """Return type is ToolResult."""
        mock_cls.return_value.download_pdf.return_value = None
        tool = BibPdfTool()
        result = tool.execute(ref="10.1234/test")
        assert isinstance(result, ToolResult)

    @patch("axm_bib.tools.bib_pdf.BibPipeline")
    def test_tool_accepts_arxiv_id(self, mock_cls: MagicMock) -> None:
        """Tool accepts arXiv ID as ref input."""
        download = DownloadResult(
            path="/tmp/paper.pdf",
            size_bytes=1024,
            is_open_access=True,
        )
        mock_cls.return_value.download_pdf.return_value = download
        tool = BibPdfTool()
        result = tool.execute(ref="arXiv:2503.18813")
        assert result.success is True
        mock_cls.return_value.download_pdf.assert_called_once_with("arXiv:2503.18813")

    @patch("axm_bib.tools.bib_pdf.BibPipeline")
    def test_tool_accepts_doi(self, mock_cls: MagicMock) -> None:
        """Tool accepts DOI as ref input."""
        download = DownloadResult(
            doi=SAMPLE_DOI,
            path="/tmp/paper.pdf",
            size_bytes=1024,
            is_open_access=True,
        )
        mock_cls.return_value.download_pdf.return_value = download
        tool = BibPdfTool()
        result = tool.execute(ref=SAMPLE_DOI)
        assert result.success is True
        mock_cls.return_value.download_pdf.assert_called_once_with(SAMPLE_DOI)


class TestBibPdfToolErrors:
    """Error handling tests."""

    @patch("axm_bib.tools.bib_pdf.BibPipeline")
    def test_pipeline_exception(self, mock_cls: MagicMock) -> None:
        """Pipeline exception → success=False with error message."""
        mock_cls.return_value.download_pdf.side_effect = Exception("Download failed")
        tool = BibPdfTool()
        result = tool.execute(ref=SAMPLE_DOI)
        assert result.success is False
        assert "Download failed" in result.error  # type: ignore[operator]

    @patch("axm_bib.tools.bib_pdf.BibPipeline")
    def test_os_error(self, mock_cls: MagicMock) -> None:
        """OSError from filesystem propagated."""
        mock_cls.return_value.download_pdf.side_effect = OSError("Disk full")
        tool = BibPdfTool()
        result = tool.execute(ref=SAMPLE_DOI)
        assert result.success is False
        assert "Disk full" in result.error  # type: ignore[operator]


class TestBibPdfToolNameProperty:
    """Tool name registration."""

    def test_name_is_bib_pdf(self) -> None:
        """Tool name property returns 'bib_pdf'."""
        assert BibPdfTool().name == "bib_pdf"
