"""TDD tests for axm_bib.extract — PDF extraction via PyMuPDF.

Uses lazy imports of pymupdf inside fixtures/tests to avoid segfault
when loading pymupdf's C extension via pytest's import hooks.
"""

from __future__ import annotations

from pathlib import Path

import pytest

# ── Helpers ─────────────────────────────────────────────────────────────────


def _make_pdf(
    path: Path,
    *,
    pages: int = 2,
    text: str | None = "Hello World",
    images: list[tuple[int, int]] | None = None,
    encrypt: bool = False,
) -> None:
    """Create a minimal real PDF for testing."""
    import pymupdf

    doc = pymupdf.open()
    for i in range(pages):
        page = doc.new_page(width=612, height=792)
        if text:
            page.insert_text((72, 100 + i * 20), text, fontsize=12)

    if images:
        page0 = doc[0]
        y_offset = 200
        for w, h in images:
            pix = pymupdf.Pixmap(pymupdf.csRGB, pymupdf.IRect(0, 0, w, h), 1)
            pix.clear_with(200)  # fill with gray value
            img_bytes = pix.tobytes("png")
            rect = pymupdf.Rect(72, y_offset, 72 + w, y_offset + h)
            page0.insert_image(rect, stream=img_bytes)
            y_offset += h + 10

    if encrypt:
        doc.save(
            str(path),
            encryption=pymupdf.PDF_ENCRYPT_AES_256,
            owner_pw="owner",
            user_pw="user",
        )
    else:
        doc.save(str(path))
    doc.close()


def _make_non_pdf(path: Path) -> None:
    """Create a file that is not a valid PDF."""
    path.write_text("This is definitely not a PDF file.")


# ── TestExtractMarkdown ─────────────────────────────────────────────────────


class TestExtractMarkdown:
    """Tests for extract_markdown()."""

    def test_converts_pdf_to_markdown(self, tmp_path: Path) -> None:
        """Happy path: returns a non-empty markdown string."""
        pdf = tmp_path / "test.pdf"
        _make_pdf(pdf, text="Some important text")

        from axm_bib.extract import extract_markdown

        result = extract_markdown(pdf)
        assert isinstance(result, str)
        assert len(result) > 0

    def test_file_not_found(self) -> None:
        """Raises FileNotFoundError for missing PDF."""
        from axm_bib.extract import extract_markdown

        with pytest.raises(FileNotFoundError):
            extract_markdown(Path("/nonexistent/paper.pdf"))

    def test_invalid_pdf(self, tmp_path: Path) -> None:
        """Raises ValueError for non-PDF file."""
        txt = tmp_path / "notes.txt"
        _make_non_pdf(txt)

        from axm_bib.extract import extract_markdown

        with pytest.raises(ValueError, match="Not a valid PDF"):
            extract_markdown(txt)

    def test_encrypted_pdf(self, tmp_path: Path) -> None:
        """Raises ValueError for encrypted PDF."""
        pdf = tmp_path / "secret.pdf"
        _make_pdf(pdf, encrypt=True)

        from axm_bib.extract import extract_markdown

        with pytest.raises(ValueError, match="encrypted"):
            extract_markdown(pdf)

    def test_pdf_without_text(self, tmp_path: Path) -> None:
        """Returns a string (possibly whitespace-only) for a PDF with no text."""
        pdf = tmp_path / "empty_text.pdf"
        _make_pdf(pdf, text=None)

        from axm_bib.extract import extract_markdown

        result = extract_markdown(pdf)
        assert isinstance(result, str)


# ── TestExtractFigures ──────────────────────────────────────────────────────


class TestExtractFigures:
    """Tests for extract_figures()."""

    def test_extracts_images_as_png(self, tmp_path: Path) -> None:
        """Extracts images and saves as PNG files."""
        pdf = tmp_path / "test.pdf"
        _make_pdf(pdf, images=[(200, 150)])
        out = tmp_path / "output"

        from axm_bib.extract import extract_figures

        figures = extract_figures(pdf, out)
        assert len(figures) >= 1
        for fig in figures:
            assert (out / fig.filename).exists()

    def test_sequential_numbering(self, tmp_path: Path) -> None:
        """Figures numbered sequentially: fig_001, fig_002, ..."""
        pdf = tmp_path / "test.pdf"
        _make_pdf(pdf, images=[(200, 150), (300, 200)])
        out = tmp_path / "output"

        from axm_bib.extract import extract_figures

        figures = extract_figures(pdf, out)
        assert len(figures) >= 2
        assert figures[0].filename == "fig_001.png"
        assert figures[0].index == 1
        assert figures[1].filename == "fig_002.png"
        assert figures[1].index == 2

    def test_no_images_returns_empty_list(self, tmp_path: Path) -> None:
        """PDF with no images returns empty list."""
        pdf = tmp_path / "test.pdf"
        _make_pdf(pdf, images=None)
        out = tmp_path / "output"

        from axm_bib.extract import extract_figures

        figures = extract_figures(pdf, out)
        assert figures == []

    def test_filters_small_images(self, tmp_path: Path) -> None:
        """Images smaller than 50x50 are filtered out."""
        pdf = tmp_path / "test.pdf"
        _make_pdf(pdf, images=[(10, 10), (200, 150)])
        out = tmp_path / "output"

        from axm_bib.extract import extract_figures

        figures = extract_figures(pdf, out)
        assert len(figures) == 1
        assert figures[0].width == 200
        assert figures[0].height == 150

    def test_figure_info_metadata(self, tmp_path: Path) -> None:
        """FigureInfo contains correct metadata."""
        from axm_bib.extract import extract_figures
        from axm_bib.models import FigureInfo

        pdf = tmp_path / "test.pdf"
        _make_pdf(pdf, images=[(640, 480)])
        out = tmp_path / "output"

        figures = extract_figures(pdf, out)
        assert len(figures) >= 1
        fig = figures[0]
        assert isinstance(fig, FigureInfo)
        assert fig.index == 1
        assert fig.page == 0
        assert fig.width == 640
        assert fig.height == 480
        assert fig.xref > 0
        assert fig.filename == "fig_001.png"

    def test_file_not_found(self, tmp_path: Path) -> None:
        """Raises FileNotFoundError for missing PDF."""
        from axm_bib.extract import extract_figures

        with pytest.raises(FileNotFoundError):
            extract_figures(Path("/nonexistent.pdf"), tmp_path)

    def test_deduplicates_images(self, tmp_path: Path) -> None:
        """Same image inserted on multiple pages is deduplicated by xref."""
        import pymupdf

        pdf_path = tmp_path / "dedup.pdf"
        doc = pymupdf.open()
        pix = pymupdf.Pixmap(pymupdf.csRGB, pymupdf.IRect(0, 0, 100, 80), 1)
        pix.clear_with(100)  # fill with gray value
        img_bytes = pix.tobytes("png")

        for _ in range(2):
            page = doc.new_page()
            page.insert_image(pymupdf.Rect(50, 50, 150, 130), stream=img_bytes)

        doc.save(str(pdf_path))
        doc.close()

        from axm_bib.extract import extract_figures

        out = tmp_path / "output"
        figures = extract_figures(pdf_path, out)
        assert len(figures) == 1


# ── TestExtractPdf ──────────────────────────────────────────────────────────


class TestExtractPdf:
    """Tests for extract_pdf() — the orchestrator."""

    def test_creates_output_directory(self, tmp_path: Path) -> None:
        """Output directory is created automatically."""
        from axm_bib.extract import extract_pdf

        pdf = tmp_path / "paper.pdf"
        _make_pdf(pdf)
        out = tmp_path / "out"

        extract_pdf(pdf, out)
        assert out.is_dir()

    def test_default_output_dir(self, tmp_path: Path) -> None:
        """Default output_dir is {stem}/ next to PDF."""
        from axm_bib.extract import extract_pdf

        pdf = tmp_path / "paper.pdf"
        _make_pdf(pdf)

        result = extract_pdf(pdf)
        expected = tmp_path / "paper"
        assert result.output_dir == str(expected)

    def test_creates_content_md(self, tmp_path: Path) -> None:
        """content.md is created with markdown content."""
        from axm_bib.extract import extract_pdf

        pdf = tmp_path / "paper.pdf"
        _make_pdf(pdf, text="Research findings here")
        out = tmp_path / "out"

        result = extract_pdf(pdf, out)
        md_path = Path(result.markdown_path)
        assert md_path.exists()
        assert len(md_path.read_text()) > 0

    def test_creates_figures_subdir(self, tmp_path: Path) -> None:
        """figures/ subdirectory is created."""
        from axm_bib.extract import extract_pdf

        pdf = tmp_path / "paper.pdf"
        _make_pdf(pdf)
        out = tmp_path / "out"

        extract_pdf(pdf, out)
        assert (out / "figures").is_dir()

    def test_result_metadata(self, tmp_path: Path) -> None:
        """ExtractionResult has correct metadata."""
        from axm_bib.extract import extract_pdf
        from axm_bib.models import ExtractionResult

        pdf = tmp_path / "paper.pdf"
        _make_pdf(pdf, pages=3, images=[(200, 150)])
        out = tmp_path / "out"

        result = extract_pdf(pdf, out)
        assert isinstance(result, ExtractionResult)
        assert result.source_pdf == str(pdf)
        assert result.page_count == 3
        assert result.char_count > 0
        assert len(result.figures) >= 1

    def test_overwrites_existing(self, tmp_path: Path) -> None:
        """No error if output_dir already exists with old content."""
        from axm_bib.extract import extract_pdf

        pdf = tmp_path / "paper.pdf"
        _make_pdf(pdf, text="new content")
        out = tmp_path / "out"
        out.mkdir()
        (out / "content.md").write_text("old content")

        result = extract_pdf(pdf, out)
        new_content = Path(result.markdown_path).read_text()
        assert "old content" not in new_content
