"""Tests for PDFMuxLoader."""

from __future__ import annotations

from pathlib import Path
from unittest.mock import MagicMock, patch

import pytest
from langchain_core.documents import Document

from langchain_pdfmux.document_loader import PDFMuxLoader


# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------

SAMPLE_CHUNKS = [
    {
        "text": "Introduction to the report.",
        "title": "Introduction",
        "page_start": 1,
        "page_end": 2,
        "tokens": 120,
        "confidence": 0.95,
    },
    {
        "text": "Detailed findings section.",
        "title": "Findings",
        "page_start": 3,
        "page_end": 5,
        "tokens": 340,
        "confidence": 0.88,
    },
]


@pytest.fixture()
def sample_pdf(tmp_path: Path) -> Path:
    """Create a dummy PDF file (content doesn't matter since pdfmux is mocked)."""
    pdf = tmp_path / "report.pdf"
    pdf.write_bytes(b"%PDF-1.4 fake")
    return pdf


@pytest.fixture()
def sample_dir(tmp_path: Path) -> Path:
    """Create a directory with multiple dummy PDF files."""
    for name in ("a.pdf", "b.pdf", "notes.txt"):
        (tmp_path / name).write_bytes(b"%PDF-1.4 fake")
    return tmp_path


# ---------------------------------------------------------------------------
# Initialization tests
# ---------------------------------------------------------------------------


class TestInit:
    def test_default_quality(self, sample_pdf: Path) -> None:
        loader = PDFMuxLoader(sample_pdf)
        assert loader.quality == "standard"

    def test_custom_quality(self, sample_pdf: Path) -> None:
        for q in ("fast", "standard", "high"):
            loader = PDFMuxLoader(sample_pdf, quality=q)
            assert loader.quality == q

    def test_invalid_quality_raises(self, sample_pdf: Path) -> None:
        with pytest.raises(ValueError, match="Invalid quality"):
            PDFMuxLoader(sample_pdf, quality="ultra")

    def test_file_path_stored_as_pathlib(self, sample_pdf: Path) -> None:
        loader = PDFMuxLoader(str(sample_pdf))
        assert isinstance(loader.file_path, Path)

    def test_nonexistent_path_raises(self) -> None:
        with pytest.raises(ValueError, match="Path does not exist"):
            PDFMuxLoader("/tmp/does_not_exist_abc123.pdf")

    def test_default_glob(self, sample_pdf: Path) -> None:
        loader = PDFMuxLoader(sample_pdf)
        assert loader.glob == "*.pdf"

    def test_custom_glob(self, sample_pdf: Path) -> None:
        loader = PDFMuxLoader(sample_pdf, glob="**/*.pdf")
        assert loader.glob == "**/*.pdf"


# ---------------------------------------------------------------------------
# lazy_load tests
# ---------------------------------------------------------------------------


class TestLazyLoad:
    @patch("langchain_pdfmux.document_loader.pdfmux", create=True)
    def test_returns_documents(self, mock_pdfmux: MagicMock, sample_pdf: Path) -> None:
        mock_pdfmux.load_llm_context.return_value = SAMPLE_CHUNKS
        with patch.dict("sys.modules", {"pdfmux": mock_pdfmux}):
            loader = PDFMuxLoader(sample_pdf)
            docs = list(loader.lazy_load())

        assert len(docs) == 2
        assert all(isinstance(d, Document) for d in docs)

    @patch("langchain_pdfmux.document_loader.pdfmux", create=True)
    def test_document_content(self, mock_pdfmux: MagicMock, sample_pdf: Path) -> None:
        mock_pdfmux.load_llm_context.return_value = SAMPLE_CHUNKS
        with patch.dict("sys.modules", {"pdfmux": mock_pdfmux}):
            docs = list(PDFMuxLoader(sample_pdf).lazy_load())

        assert docs[0].page_content == "Introduction to the report."
        assert docs[1].page_content == "Detailed findings section."

    @patch("langchain_pdfmux.document_loader.pdfmux", create=True)
    def test_metadata_fields(self, mock_pdfmux: MagicMock, sample_pdf: Path) -> None:
        mock_pdfmux.load_llm_context.return_value = SAMPLE_CHUNKS
        with patch.dict("sys.modules", {"pdfmux": mock_pdfmux}):
            docs = list(PDFMuxLoader(sample_pdf).lazy_load())

        meta = docs[0].metadata
        assert meta["source"] == str(sample_pdf)
        assert meta["title"] == "Introduction"
        assert meta["page_start"] == 1
        assert meta["page_end"] == 2
        assert meta["tokens"] == 120
        assert meta["confidence"] == 0.95

    @patch("langchain_pdfmux.document_loader.pdfmux", create=True)
    def test_quality_passed_to_pdfmux(self, mock_pdfmux: MagicMock, sample_pdf: Path) -> None:
        mock_pdfmux.load_llm_context.return_value = []
        with patch.dict("sys.modules", {"pdfmux": mock_pdfmux}):
            list(PDFMuxLoader(sample_pdf, quality="high").lazy_load())

        mock_pdfmux.load_llm_context.assert_called_once_with(sample_pdf, quality="high")


# ---------------------------------------------------------------------------
# Directory loading tests
# ---------------------------------------------------------------------------


class TestDirectoryLoading:
    @patch("langchain_pdfmux.document_loader.pdfmux", create=True)
    def test_loads_all_pdfs_in_directory(self, mock_pdfmux: MagicMock, sample_dir: Path) -> None:
        mock_pdfmux.load_llm_context.return_value = SAMPLE_CHUNKS[:1]
        with patch.dict("sys.modules", {"pdfmux": mock_pdfmux}):
            docs = list(PDFMuxLoader(sample_dir).lazy_load())

        # sample_dir has a.pdf and b.pdf (notes.txt excluded by glob)
        assert len(docs) == 2
        sources = {d.metadata["source"] for d in docs}
        assert str(sample_dir / "a.pdf") in sources
        assert str(sample_dir / "b.pdf") in sources

    @patch("langchain_pdfmux.document_loader.pdfmux", create=True)
    def test_custom_glob_pattern(self, mock_pdfmux: MagicMock, sample_dir: Path) -> None:
        mock_pdfmux.load_llm_context.return_value = SAMPLE_CHUNKS[:1]
        with patch.dict("sys.modules", {"pdfmux": mock_pdfmux}):
            docs = list(PDFMuxLoader(sample_dir, glob="a.*").lazy_load())

        assert len(docs) == 1
        assert docs[0].metadata["source"] == str(sample_dir / "a.pdf")

    @patch("langchain_pdfmux.document_loader.pdfmux", create=True)
    def test_empty_directory_yields_nothing(self, mock_pdfmux: MagicMock, tmp_path: Path) -> None:
        with patch.dict("sys.modules", {"pdfmux": mock_pdfmux}):
            docs = list(PDFMuxLoader(tmp_path).lazy_load())

        assert docs == []


# ---------------------------------------------------------------------------
# Error handling tests
# ---------------------------------------------------------------------------


class TestErrorHandling:
    def test_import_error_message(self, sample_pdf: Path) -> None:
        with patch.dict("sys.modules", {"pdfmux": None}):
            loader = PDFMuxLoader(sample_pdf)
            with pytest.raises(ImportError, match="pip install pdfmux"):
                list(loader.lazy_load())

    @patch("langchain_pdfmux.document_loader.pdfmux", create=True)
    def test_conversion_failure_raises_runtime_error(
        self, mock_pdfmux: MagicMock, sample_pdf: Path
    ) -> None:
        mock_pdfmux.load_llm_context.side_effect = Exception("corrupt PDF")
        with patch.dict("sys.modules", {"pdfmux": mock_pdfmux}):
            loader = PDFMuxLoader(sample_pdf)
            with pytest.raises(RuntimeError, match="pdfmux failed to process"):
                list(loader.lazy_load())
