"""Tests for the pdf_extract tool."""

from __future__ import annotations

import builtins
import sys
from unittest.mock import MagicMock

from openjarvis.tools.pdf_tool import PDFExtractTool, _parse_pages


class TestPDFExtractTool:
    def test_spec(self):
        tool = PDFExtractTool()
        assert tool.spec.name == "pdf_extract"
        assert tool.spec.category == "media"
        assert "file_path" in tool.spec.parameters["properties"]
        assert "file_path" in tool.spec.parameters["required"]
        assert tool.spec.required_capabilities == ["file:read"]

    def test_tool_id(self):
        tool = PDFExtractTool()
        assert tool.tool_id == "pdf_extract"

    def test_no_file_path(self):
        tool = PDFExtractTool()
        result = tool.execute(file_path="")
        assert result.success is False
        assert "No file_path" in result.content

    def test_no_file_path_param(self):
        tool = PDFExtractTool()
        result = tool.execute()
        assert result.success is False
        assert "No file_path" in result.content

    def test_file_not_found(self):
        tool = PDFExtractTool()
        result = tool.execute(file_path="/nonexistent/doc.pdf")
        assert result.success is False
        assert "File not found" in result.content

    def test_not_a_pdf(self, tmp_path):
        f = tmp_path / "document.txt"
        f.write_text("not a pdf", encoding="utf-8")
        tool = PDFExtractTool()
        result = tool.execute(file_path=str(f))
        assert result.success is False
        assert "Not a PDF" in result.content

    def test_pdfplumber_not_installed(self, tmp_path, monkeypatch):
        f = tmp_path / "doc.pdf"
        f.write_bytes(b"%PDF-1.4 fake pdf content")

        monkeypatch.delitem(sys.modules, "pdfplumber", raising=False)
        original_import = builtins.__import__

        def _mock_import(name, *args, **kwargs):
            if name == "pdfplumber":
                raise ImportError("No module named 'pdfplumber'")
            return original_import(name, *args, **kwargs)

        monkeypatch.setattr(builtins, "__import__", _mock_import)

        tool = PDFExtractTool()
        result = tool.execute(file_path=str(f))
        assert result.success is False
        assert "pdfplumber package not installed" in result.content

    def test_successful_extraction(self, tmp_path, monkeypatch):
        f = tmp_path / "doc.pdf"
        f.write_bytes(b"%PDF-1.4 fake")

        # Mock pdfplumber
        mock_page1 = MagicMock()
        mock_page1.extract_text.return_value = "Page one text."
        mock_page2 = MagicMock()
        mock_page2.extract_text.return_value = "Page two text."

        mock_pdf = MagicMock()
        mock_pdf.pages = [mock_page1, mock_page2]
        mock_pdf.__enter__ = MagicMock(return_value=mock_pdf)
        mock_pdf.__exit__ = MagicMock(return_value=False)

        mock_pdfplumber = MagicMock()
        mock_pdfplumber.open.return_value = mock_pdf
        monkeypatch.setitem(sys.modules, "pdfplumber", mock_pdfplumber)

        tool = PDFExtractTool()
        result = tool.execute(file_path=str(f))
        assert result.success is True
        assert "Page one text." in result.content
        assert "Page two text." in result.content
        assert result.metadata["total_pages"] == 2
        assert result.metadata["pages_extracted"] == 2

    def test_extraction_with_page_range(self, tmp_path, monkeypatch):
        f = tmp_path / "doc.pdf"
        f.write_bytes(b"%PDF-1.4 fake")

        mock_page1 = MagicMock()
        mock_page1.extract_text.return_value = "First page."
        mock_page2 = MagicMock()
        mock_page2.extract_text.return_value = "Second page."
        mock_page3 = MagicMock()
        mock_page3.extract_text.return_value = "Third page."

        mock_pdf = MagicMock()
        mock_pdf.pages = [mock_page1, mock_page2, mock_page3]
        mock_pdf.__enter__ = MagicMock(return_value=mock_pdf)
        mock_pdf.__exit__ = MagicMock(return_value=False)

        mock_pdfplumber = MagicMock()
        mock_pdfplumber.open.return_value = mock_pdf
        monkeypatch.setitem(sys.modules, "pdfplumber", mock_pdfplumber)

        tool = PDFExtractTool()
        # Extract only pages 1 and 3 (1-indexed)
        result = tool.execute(file_path=str(f), pages="1,3")
        assert result.success is True
        assert "First page." in result.content
        assert "Third page." in result.content
        assert "Second page." not in result.content
        assert result.metadata["pages_extracted"] == 2

    def test_max_chars_truncation(self, tmp_path, monkeypatch):
        f = tmp_path / "doc.pdf"
        f.write_bytes(b"%PDF-1.4 fake")

        mock_page = MagicMock()
        mock_page.extract_text.return_value = "A" * 1000

        mock_pdf = MagicMock()
        mock_pdf.pages = [mock_page]
        mock_pdf.__enter__ = MagicMock(return_value=mock_pdf)
        mock_pdf.__exit__ = MagicMock(return_value=False)

        mock_pdfplumber = MagicMock()
        mock_pdfplumber.open.return_value = mock_pdf
        monkeypatch.setitem(sys.modules, "pdfplumber", mock_pdfplumber)

        tool = PDFExtractTool()
        result = tool.execute(file_path=str(f), max_chars=100)
        assert result.success is True
        assert "[Content truncated]" in result.content
        # The content before truncation marker should be <= max_chars
        truncated_idx = result.content.index("\n\n[Content truncated]")
        assert truncated_idx == 100

    def test_pdf_extraction_error(self, tmp_path, monkeypatch):
        f = tmp_path / "bad.pdf"
        f.write_bytes(b"%PDF-1.4 corrupt")

        mock_pdfplumber = MagicMock()
        mock_pdfplumber.open.side_effect = RuntimeError("Corrupt PDF")
        monkeypatch.setitem(sys.modules, "pdfplumber", mock_pdfplumber)

        tool = PDFExtractTool()
        result = tool.execute(file_path=str(f))
        assert result.success is False
        assert "PDF extraction error" in result.content

    def test_to_openai_function(self):
        tool = PDFExtractTool()
        fn = tool.to_openai_function()
        assert fn["type"] == "function"
        assert fn["function"]["name"] == "pdf_extract"


class TestParsePages:
    def test_single_page(self):
        assert _parse_pages("3", 10) == [2]

    def test_range(self):
        assert _parse_pages("1-5", 10) == [0, 1, 2, 3, 4]

    def test_comma_separated(self):
        assert _parse_pages("1,3,5", 10) == [0, 2, 4]

    def test_mixed(self):
        result = _parse_pages("1-3,5", 10)
        assert result == [0, 1, 2, 4]

    def test_out_of_range_clamped(self):
        # Page 20 is out of range for a 5-page doc
        assert _parse_pages("20", 5) == []

    def test_range_clamped_to_total(self):
        # "1-100" on a 3-page doc should only return 3 pages
        assert _parse_pages("1-100", 3) == [0, 1, 2]

    def test_duplicates_removed(self):
        result = _parse_pages("1,1,2,2", 5)
        assert result == [0, 1]

    def test_empty_string(self):
        assert _parse_pages("", 5) == []
