"""Tests for the placeholder-alt-text gate added to the PDF remediation
pipeline: pdf_tree_edit.is_placeholder_alt / list_figures, and
remediate_pdf._check_figure_alt_placeholders. CI-light: pure pikepdf/fitz
fixtures, no vendored ODL/java/verapdf.

Motivation (see pdf_tree_edit.py's "Figure alt-text placeholder detection"
module comment): veraPDF clause 7.3 only checks that /Alt EXISTS, never
whether it's useful. A live remediation shipped a file where every figure's
/Alt was opendataloader-pdf's own default ("image 1", "image 2", ...) --
106/106 compliant, and conveying nothing to a screen reader. This gate
surfaces that defect as evidence (list_figures) and as a needs_manual flag
(remediate_pdf), without touching the veraPDF/fidelity gates themselves.
"""

import os
import sys
import tempfile
from pathlib import Path

import fitz  # PyMuPDF
import pikepdf
import pytest

sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "scripts"))

import pdf_tree_edit as pte  # noqa: E402
import remediate_pdf as rp  # noqa: E402


# ── is_placeholder_alt ───────────────────────────────────────────────────

class TestIsPlaceholderAlt:
    @pytest.mark.parametrize("alt", [
        "image 1", "Image", "figure 3", "IMG", "untitled", "", "   ",
        "image 12.", "Image-01", "IMAGE",
    ])
    def test_flags_generic_placeholders(self, alt):
        assert pte.is_placeholder_alt(alt) is True

    @pytest.mark.parametrize("alt", [
        "Image of a menu for Oren's Hummus with three columns",
        "Figure showing the water cycle",
        "A small red square accent graphic.",
        "Bar chart of quarterly revenue by region",
    ])
    def test_does_not_flag_real_descriptions(self, alt):
        assert pte.is_placeholder_alt(alt) is False


# ── Caption normalization / extraction ──────────────────────────────────

class TestNormalizeCaptionText:
    def test_dehyphenates_line_break_hyphen(self):
        text = ("Figure 8. A row of three giraffes, rang-\ning from a "
                 "small giraffe to a larger one, line up outside of a doorway.")
        out = pte._normalize_caption_text(text)
        assert "ranging" in out
        assert "rang-" not in out
        assert "\n" not in out

    def test_preserves_genuine_midline_hyphen(self):
        out = pte._normalize_caption_text("a well-known compound word")
        assert "well-known" in out

    def test_collapses_whitespace(self):
        out = pte._normalize_caption_text("Figure   1.\n\n  Some   text.")
        assert out == "Figure 1. Some text."


def _build_figure_pdf(path, page_texts, figure_pages, alts=None):
    """Build a PDF with one page per entry in page_texts (list of [(y, text),
    ...] placed via fitz.insert_text), then a StructTreeRoot with one
    /Figure per entry in figure_pages (0-based page index), in order,
    each optionally carrying /Alt from `alts` (list aligned to
    figure_pages, or None)."""
    doc = fitz.open()
    for texts in page_texts:
        page = doc.new_page(width=400, height=600)
        for y, text in texts:
            page.insert_text((50, y), text, fontsize=10)
    doc.save(str(path))
    doc.close()

    pdf = pikepdf.open(str(path), allow_overwriting_input=True)
    fig_elems = []
    for i, page_idx in enumerate(figure_pages):
        kw = {"/Type": pikepdf.Name.StructElem, "/S": pikepdf.Name("/Figure"),
              "/Pg": pdf.pages[page_idx].obj}
        if alts and alts[i] is not None:
            kw["/Alt"] = pikepdf.String(alts[i])
        fig_elems.append(pdf.make_indirect(pikepdf.Dictionary(kw)))
    doc_elem = pdf.make_indirect(pikepdf.Dictionary({
        "/Type": pikepdf.Name.StructElem, "/S": pikepdf.Name("/Document"),
        "/K": pikepdf.Array(fig_elems),
    }))
    pdf.Root.StructTreeRoot = pdf.make_indirect(pikepdf.Dictionary({
        "/Type": pikepdf.Name("/StructTreeRoot"), "/K": doc_elem,
    }))
    return pdf


class TestListFigures:
    def test_matches_one_to_one_caption_by_vertical_order(self, tmp_path):
        p = tmp_path / "cap.pdf"
        pdf = _build_figure_pdf(
            p,
            page_texts=[[
                (100, "Some body text above the figure."),
                (300, "Figure 1. A cheeseburger is held in a close-up shot."),
                (500, "Figure 2. A second figure caption here."),
            ]],
            figure_pages=[0, 0],
        )
        recs = pte.list_figures(pdf, path=str(p))
        assert recs[0]["caption_candidate"] == \
            "Figure 1. A cheeseburger is held in a close-up shot."
        assert recs[1]["caption_candidate"] == \
            "Figure 2. A second figure caption here."

    def test_ambiguous_count_mismatch_returns_empty(self, tmp_path):
        p = tmp_path / "cap.pdf"
        pdf = _build_figure_pdf(
            p,
            page_texts=[[(300, "Figure 1. Only one caption present.")]],
            figure_pages=[0, 0],  # two figures, one caption -> ambiguous
        )
        recs = pte.list_figures(pdf, path=str(p))
        assert recs[0]["caption_candidate"] == ""
        assert recs[1]["caption_candidate"] == ""

    def test_no_caption_on_page_returns_empty_not_misassociated(self, tmp_path):
        """A cover-thumbnail figure sharing a page with no printed caption
        must not inherit a caption meant for a figure on a later page."""
        p = tmp_path / "cap.pdf"
        pdf = _build_figure_pdf(
            p,
            page_texts=[
                [(100, "Cover page, no caption text at all.")],
                [(300, "Figure 1. This caption belongs to page 2.")],
            ],
            figure_pages=[0, 1],
        )
        recs = pte.list_figures(pdf, path=str(p))
        assert recs[0]["caption_candidate"] == ""
        assert recs[1]["caption_candidate"] == "Figure 1. This caption belongs to page 2."

    def test_no_path_returns_empty_captions_and_does_not_raise(self, tmp_path):
        p = tmp_path / "cap.pdf"
        pdf = _build_figure_pdf(
            p, page_texts=[[(300, "Figure 1. Some caption.")]], figure_pages=[0])
        recs = pte.list_figures(pdf)
        assert recs[0]["caption_candidate"] == ""

    def test_alt_and_is_placeholder_fields(self, tmp_path):
        p = tmp_path / "alts.pdf"
        pdf = _build_figure_pdf(
            p, page_texts=[[]], figure_pages=[0, 0],
            alts=["image 1", "A real, specific description of the chart."])
        recs = pte.list_figures(pdf, path=str(p))
        assert recs[0]["alt"] == "image 1"
        assert recs[0]["is_placeholder"] is True
        assert recs[1]["alt"] == "A real, specific description of the chart."
        assert recs[1]["is_placeholder"] is False

    def test_missing_alt_is_placeholder(self, tmp_path):
        p = tmp_path / "noalt.pdf"
        pdf = _build_figure_pdf(p, page_texts=[[]], figure_pages=[0])
        recs = pte.list_figures(pdf, path=str(p))
        assert recs[0]["alt"] == ""
        assert recs[0]["is_placeholder"] is True

    def test_index_and_page_fields(self, tmp_path):
        p = tmp_path / "idx.pdf"
        pdf = _build_figure_pdf(p, page_texts=[[], []], figure_pages=[0, 1])
        recs = pte.list_figures(pdf, path=str(p))
        assert [r["index"] for r in recs] == [0, 1]
        assert [r["page"] for r in recs] == [1, 2]


# ── remediate_pdf._check_figure_alt_placeholders ────────────────────────

class TestCheckFigureAltPlaceholders:
    def test_no_figures_no_manual_reason(self, tmp_path):
        p = tmp_path / "plain.pdf"
        pdf = pikepdf.new()
        pdf.add_blank_page(page_size=(200, 200))
        pdf.save(str(p))
        pdf.close()

        review, reason = rp._check_figure_alt_placeholders(p)
        assert review == {"total": 0, "placeholder": 0, "figures": []}
        assert reason is None

    def test_all_real_alt_no_manual_reason(self, tmp_path):
        p = tmp_path / "real.pdf"
        pdf = _build_figure_pdf(
            p, page_texts=[[]], figure_pages=[0, 0],
            alts=["A red square accent graphic.", "A bar chart of quarterly revenue."])
        pdf.save(str(p))
        pdf.close()

        review, reason = rp._check_figure_alt_placeholders(p)
        assert review["total"] == 2
        assert review["placeholder"] == 0
        assert review["figures"] == []
        assert reason is None

    def test_placeholder_alt_sets_manual_reason(self, tmp_path):
        p = tmp_path / "placeholder.pdf"
        pdf = _build_figure_pdf(
            p, page_texts=[[]], figure_pages=[0, 0], alts=["image 1", "image 2"])
        pdf.save(str(p))
        pdf.close()

        review, reason = rp._check_figure_alt_placeholders(p)
        assert review["total"] == 2
        assert review["placeholder"] == 2
        assert {f["alt"] for f in review["figures"]} == {"image 1", "image 2"}
        assert reason is not None
        assert "placeholder alt text" in reason
        assert "judgment.alt_text" in reason

    def test_mixed_real_and_placeholder_counts_only_placeholder(self, tmp_path):
        p = tmp_path / "mixed.pdf"
        pdf = _build_figure_pdf(
            p, page_texts=[[]], figure_pages=[0, 0, 0],
            alts=["image 1", "A real description here.", "untitled"])
        pdf.save(str(p))
        pdf.close()

        review, reason = rp._check_figure_alt_placeholders(p)
        assert review["total"] == 3
        assert review["placeholder"] == 2
        assert reason is not None
