"""Tests for pdf_tree_edit.demote_mcids_to_artifact — the leaked-artifact-
status counterpart to artifact_page_unmarked. Fixes the E2 re-tag
regression where source-/Artifact content (running headers/footers) came
back as real tagged content, invisible to veraPDF/fidelity gates because
neither checks reading order.

CI-light: pure pikepdf fixtures, no vendored ODL/java/verapdf.
"""
import os
import sys

import pikepdf

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

import pdf_tree_edit as pte  # noqa: E402
import reading_order as ro  # noqa: E402


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


def _tagged_pdf_with_header():
    """1-page tagged PDF: MCID0 'Body' (leaf /P), MCID1 'Running Header'
    (leaf /P) — the case where MCID1 should have stayed /Artifact."""
    pdf = pikepdf.new()
    page = pdf.add_blank_page(page_size=(612, 792))
    page.Contents = pdf.make_stream(
        b"/P <</MCID 0>> BDC BT (Body) Tj ET EMC\n"
        b"/P <</MCID 1>> BDC BT (Running Header) Tj ET EMC\n")
    page.StructParents = 0
    p0 = _struct_elem(pdf, S=pikepdf.Name.P, Pg=page.obj, K=0)
    p1 = _struct_elem(pdf, S=pikepdf.Name.P, Pg=page.obj, K=1)
    doc = _struct_elem(pdf, S=pikepdf.Name.Document, K=[p0, p1])
    pdf.Root.StructTreeRoot = pdf.make_indirect(pikepdf.Dictionary({
        "/Type": pikepdf.Name("/StructTreeRoot"),
        "/K": pikepdf.Array([doc]),
        "/ParentTree": pikepdf.Dictionary({"/Nums": pikepdf.Array([0, pikepdf.Array([p0, p1])])}),
        "/ParentTreeNextKey": 1,
    }))
    return pdf, page


def test_demoted_mcid_disappears_from_struct_order_text():
    pdf, page = _tagged_pdf_with_header()
    assert ro.struct_order_text(pdf) == ["Body", "Running Header"]
    demoted = pte.demote_mcids_to_artifact(pdf, {1: [1]})
    assert demoted == 1
    assert ro.struct_order_text(pdf) == ["Body"]


def test_content_still_renders_artifact_bmc_and_paint_survive():
    pdf, page = _tagged_pdf_with_header()
    pte.demote_mcids_to_artifact(pdf, {1: [1]})
    ops = list(pikepdf.parse_content_stream(page))
    artifact_bmc = any(
        str(i.operator) == "BMC" and str(i.operands[0]) == "/Artifact" for i in ops)
    assert artifact_bmc
    # the header's paint/text op survives the demotion, just outside a /Tag now
    tj_texts = [bytes(i.operands[0]) for i in ops if str(i.operator) == "Tj"]
    assert b"Running Header" in tj_texts
    assert b"Body" in tj_texts
    # the untouched body MCID's BDC is still present
    body_bdc = any(
        str(i.operator) == "BDC" and str(i.operands[0]) == "/P" for i in ops)
    assert body_bdc


def test_missing_mcid_is_noop_returns_zero():
    pdf, page = _tagged_pdf_with_header()
    before = page.Contents.read_bytes()
    demoted = pte.demote_mcids_to_artifact(pdf, {1: [99]})
    assert demoted == 0
    assert page.Contents.read_bytes() == before
    assert ro.struct_order_text(pdf) == ["Body", "Running Header"]


def test_missing_page_is_noop_returns_zero():
    pdf, page = _tagged_pdf_with_header()
    demoted = pte.demote_mcids_to_artifact(pdf, {2: [1]})  # page 2 doesn't exist
    assert demoted == 0
    assert ro.struct_order_text(pdf) == ["Body", "Running Header"]


def test_empty_targets_is_noop():
    pdf, page = _tagged_pdf_with_header()
    assert pte.demote_mcids_to_artifact(pdf, {}) == 0
    assert pte.demote_mcids_to_artifact(pdf, {1: []}) == 0
    assert ro.struct_order_text(pdf) == ["Body", "Running Header"]


def test_struct_elem_removed_from_parent_k():
    pdf, page = _tagged_pdf_with_header()
    doc = pdf.Root.StructTreeRoot.K[0]
    assert len(doc.K) == 2
    pte.demote_mcids_to_artifact(pdf, {1: [1]})
    assert len(doc.K) == 1
    assert str(doc.K[0].get("/S")) == "/P"
    assert int(doc.K[0].K) == 0  # only the Body /P remains


def test_parenttree_entry_nulled():
    pdf, page = _tagged_pdf_with_header()
    pte.demote_mcids_to_artifact(pdf, {1: [1]})
    arr = pdf.Root.StructTreeRoot.ParentTree.Nums[1]
    assert arr[1] is None  # nulled -> no struct elem owns MCID1 anymore
    assert arr[0] is not None  # MCID0 (Body) untouched


def test_string_page_key_from_json_coerced_to_int():
    pdf, page = _tagged_pdf_with_header()
    demoted = pte.demote_mcids_to_artifact(pdf, {"1": [1]})
    assert demoted == 1
    assert ro.struct_order_text(pdf) == ["Body"]
