"""Tests for remediate_pdf._preserve_artifact_status — the E2 re-tag
artifact-status-preservation pass (fixes the leaked-running-header
regression: taczak.pdf/cohn.pdf came back veraPDF-compliant and pixel-
identical, but a screen reader announced the running header on every page).

Detection is a PAGE-DISTRIBUTION test, not a raw occurrence count (see the
ARTIFACT_LEAK_MIN_PAGES comment in remediate_pdf.py). A raw occurrence
count (an earlier version of this pass) also demoted real body text on
taczak.pdf/cohn.pdf: bold bulleted-list lead-in words ("What" in "• What is
the purpose", "• What is the genre", "• What is the audience") got split
into their own MCID by opendataloader-pdf and repeated >=3 times WITHIN ONE
PAGE, which satisfied a raw >=3-occurrences floor. A running header instead
appears on MANY pages, AT MOST ONCE per page — that's the discriminator:
>=3 DISTINCT pages, never more than once on any single page.

CI-light: pure pikepdf fixtures, no vendored ODL/java/verapdf — exercises
_preserve_artifact_status directly rather than the full remediate_pdf()
pipeline (which needs the vendored ODL/JRE).
"""
import os
import sys

import pikepdf

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

import reading_order as ro  # noqa: E402
import remediate_pdf  # noqa: E402


def _struct_elem(pdf, **kw):
    return pdf.make_indirect(pikepdf.Dictionary(Type=pikepdf.Name.StructElem, **kw))


def _build_tagged_pdf(texts):
    """1-page tagged PDF: one leaf /P struct elem per text, MCID == index,
    in document order — same shape reading_order.py sees from a real
    ODL-tagged page."""
    return _build_multi_page_pdf([texts])


def _build_multi_page_pdf(pages_texts):
    """Tagged PDF with one page per list in pages_texts; each page holds
    one leaf /P struct elem per text (MCID == index within that page), all
    under one /Document node in document order — same shape reading_order.py
    sees from a real multi-page ODL-tagged document."""
    pdf = pikepdf.new()
    all_elems, nums = [], []
    for pno, texts in enumerate(pages_texts):
        page = pdf.add_blank_page(page_size=(612, 792))
        content = b"".join(
            f"/P <</MCID {i}>> BDC BT ({t}) Tj ET EMC\n".encode("latin-1")
            for i, t in enumerate(texts))
        page.Contents = pdf.make_stream(content)
        elems = [_struct_elem(pdf, S=pikepdf.Name.P, Pg=page.obj, K=i) for i in range(len(texts))]
        page.StructParents = pno
        nums.extend([pno, pikepdf.Array(elems)])
        all_elems.extend(elems)
    doc = _struct_elem(pdf, S=pikepdf.Name.Document, K=all_elems)
    pdf.Root.StructTreeRoot = pdf.make_indirect(pikepdf.Dictionary({
        "/Type": pikepdf.Name("/StructTreeRoot"),
        "/K": pikepdf.Array([doc]),
        "/ParentTree": pikepdf.Dictionary({"/Nums": pikepdf.Array(nums)}),
        "/ParentTreeNextKey": len(pages_texts),
    }))
    return pdf


# 55 unique runs >= ARTIFACT_CHECK_MIN_SOURCE_RUNS (50) — a source that was
# already meaningfully tagged, matching taczak.pdf/cohn.pdf's real shape.
_BODY = [f"Body paragraph number {i}" for i in range(55)]


def _make_source(tmp_path, texts):
    pdf = _build_tagged_pdf(texts)
    path = tmp_path / "source.pdf"
    pdf.save(path)
    pdf.close()
    return path


def test_guard_skips_pass_when_source_has_too_few_runs(tmp_path):
    """The most important test, unchanged by the page-distribution rewrite.
    carroll/giles/rafoth-style source: only 28 struct-order runs
    (essentially untagged). Every one of the hundreds of NEW runs the
    re-tag legitimately adds would look "leaked" by the page-distribution
    test alone — the <50-run guard must skip the whole pass so body text is
    never demoted to /Artifact."""
    source_path = _make_source(tmp_path, _BODY[:10])  # 10 < 50 -> guard fires
    pdf = _build_multi_page_pdf([
        _BODY[:10],
        ["RUNNING HEADER", "page 2 body"],
        ["RUNNING HEADER", "page 3 body"],
        ["RUNNING HEADER", "page 4 body"],
    ])
    notes = []
    result = remediate_pdf._preserve_artifact_status(pdf, source_path, notes)
    assert result.get("checked") is False
    assert ro.struct_order_text(pdf).count("RUNNING HEADER") == 3  # untouched
    pdf.close()


def test_running_header_once_per_page_across_3_pages_is_demoted(tmp_path):
    """The actual running-header shape: one occurrence per page, spread
    across many pages."""
    source_path = _make_source(tmp_path, _BODY)
    pdf = _build_multi_page_pdf([
        _BODY,
        ["RUNNING HEADER", "page 2 body text here"],
        ["RUNNING HEADER", "page 3 body text here"],
        ["RUNNING HEADER", "page 4 body text here"],
    ])
    notes = []
    result = remediate_pdf._preserve_artifact_status(pdf, source_path, notes)
    assert result["checked"] is True
    assert result["demoted_sequences"] == 3
    out_text = ro.struct_order_text(pdf)
    assert "RUNNING HEADER" not in out_text
    assert out_text.count("Body paragraph number 0") == 1  # body text untouched
    assert "page 2 body text here" in out_text  # other page content untouched
    pdf.close()


def test_bullet_lead_in_word_repeated_5x_on_one_page_not_demoted(tmp_path):
    """The false-positive this rewrite fixes: a bold bulleted-list lead-in
    word ("What") repeated 5 times on a SINGLE page clears any raw
    occurrence floor but must NOT be demoted — demoting it would make a
    screen reader read "is the purpose" instead of "What is the purpose"."""
    source_path = _make_source(tmp_path, _BODY)
    pdf = _build_multi_page_pdf([
        _BODY,
        ["What", "is the purpose", "What", "is the genre", "What", "is the audience",
         "What", "is different", "What", "is next"],  # 5x "What", all on page 2
    ])
    notes = []
    result = remediate_pdf._preserve_artifact_status(pdf, source_path, notes)
    assert result["demoted_sequences"] == 0
    out_text = ro.struct_order_text(pdf)
    assert out_text.count("What") == 5  # untouched — still real body text
    assert "is the purpose" in out_text


def test_run_twice_on_one_page_and_once_on_another_not_demoted(tmp_path):
    """Violates the once-per-page rule even though it spans 2 pages."""
    source_path = _make_source(tmp_path, _BODY)
    pdf = _build_multi_page_pdf([
        _BODY,
        ["DUPTEXT", "other", "DUPTEXT"],  # page 2: twice
        ["DUPTEXT", "other2"],            # page 3: once
    ])
    notes = []
    result = remediate_pdf._preserve_artifact_status(pdf, source_path, notes)
    assert result["demoted_sequences"] == 0
    assert ro.struct_order_text(pdf).count("DUPTEXT") == 3  # untouched
    pdf.close()


def test_run_on_exactly_2_distinct_pages_not_demoted(tmp_path):
    source_path = _make_source(tmp_path, _BODY)
    pdf = _build_multi_page_pdf([
        _BODY,
        ["TWOPAGE", "x"],
        ["TWOPAGE", "y"],
    ])
    notes = []
    result = remediate_pdf._preserve_artifact_status(pdf, source_path, notes)
    assert result["demoted_sequences"] == 0
    assert ro.struct_order_text(pdf).count("TWOPAGE") == 2  # untouched
    pdf.close()


def test_repeated_long_paragraph_not_demoted(tmp_path):
    source_path = _make_source(tmp_path, _BODY)
    long_text = "L" * 90  # > ARTIFACT_LEAK_MAX_LEN (80)
    pdf = _build_multi_page_pdf([
        _BODY,
        [long_text, "x"],
        [long_text, "y"],
        [long_text, "z"],
    ])
    notes = []
    result = remediate_pdf._preserve_artifact_status(pdf, source_path, notes)
    assert result["demoted_sequences"] == 0
    assert ro.struct_order_text(pdf).count(long_text) == 3  # untouched
    pdf.close()


def test_text_present_in_source_is_never_flagged_even_if_repeated(tmp_path):
    """A heading/label the source legitimately tagged and repeats (e.g. a
    real recurring section title, once per page) is not a leak — only text
    ABSENT from the source's own struct order counts."""
    source_texts = _BODY + ["Section Recap"] * 3
    source_path = _make_source(tmp_path, source_texts)
    pdf = _build_multi_page_pdf([
        _BODY,
        ["Section Recap", "x"],
        ["Section Recap", "y"],
        ["Section Recap", "z"],
    ])
    notes = []
    result = remediate_pdf._preserve_artifact_status(pdf, source_path, notes)
    assert result["demoted_sequences"] == 0
    assert ro.struct_order_text(pdf).count("Section Recap") == 3
    pdf.close()
