"""Tests for scripts/lo_export.py.

CI-light: artifact_postfix (pure pikepdf, no soffice needed) + error paths.
Heavy (remediation_tools marker): real soffice export + veraPDF gate — needs
vendored LibreOffice + java + veraPDF.
"""

import os
import sys

import pikepdf
import pytest

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

import lo_export
import setup_remediation
import verapdf_gate

REMEDIATION_TOOLS_READY = bool(setup_remediation.get_java() and setup_remediation.get_verapdf())
LO_READY = bool(setup_remediation.get_libreoffice())
heavy = pytest.mark.remediation_tools


def _page_with_content(content_bytes):
    pdf = pikepdf.new()
    pdf.add_blank_page(page_size=(200, 200))
    page = pdf.pages[0]
    page.Contents = pdf.make_stream(content_bytes)
    return pdf


class TestArtifactPostfix:
    def test_wraps_unmarked_depth0_paint_run(self, tmp_path):
        """The Impress double-background bug: an unmarked depth-0 fill gets
        wrapped in /Artifact BMC..EMC; already-marked content is untouched."""
        content = (
            b"/Artifact <</Type /Pagination>> BDC\n"
            b"1 0 0 rg 0 0 200 200 re f\n"
            b"EMC\n"
            b"0 1 0 rg 0 0 200 200 re f\n"  # unmarked depth-0 paint
            b"/P <</MCID 0>> BDC\n"
            b"0 0 1 rg 50 50 50 50 re f\n"
            b"EMC\n"
        )
        pdf = _page_with_content(content)
        path = tmp_path / "in.pdf"
        pdf.save(str(path))
        pdf.close()

        lo_export.artifact_postfix(path)

        result = pikepdf.open(str(path))
        instrs = pikepdf.parse_content_stream(result.pages[0])
        ops = [str(i.operator) for i in instrs]
        # the unmarked fill's BMC/rg/re/f/EMC run is now bracketed by an
        # /Artifact BMC..EMC pair inserted right before it
        artifact_idx = next(
            i for i, instr in enumerate(instrs)
            if str(instr.operator) == "BMC" and [str(o) for o in instr.operands] == ["/Artifact"]
        )
        assert ops[artifact_idx + 1] == "rg"
        assert ops[artifact_idx - 1] == "EMC"  # closes the prior marked-content block first
        # balanced overall
        depth = 0
        for op in ops:
            if op in ("BMC", "BDC"):
                depth += 1
            elif op == "EMC":
                depth -= 1
        assert depth == 0

    def test_does_not_alter_rendered_pixels(self, tmp_path):
        content = (
            b"/Artifact <</Type /Pagination>> BDC\n1 0 0 rg 0 0 200 200 re f\nEMC\n"
            b"0 1 0 rg 0 0 200 200 re f\n"
        )
        pdf = _page_with_content(content)
        path = tmp_path / "in.pdf"
        pdf.save(str(path))
        pdf.close()

        import fidelity_gate

        before = tmp_path / "before.pdf"
        import shutil
        shutil.copy(path, before)
        lo_export.artifact_postfix(path)

        assert fidelity_gate.pixel_diff(str(before), str(path))["identical"] is True

    def test_already_marked_content_untouched(self, tmp_path):
        """A page with no unmarked depth-0 paint at all is a no-op — no
        spurious /Artifact wrapping when there's nothing to fix."""
        content = b"/P <</MCID 0>> BDC\n0 0 0 rg 0 0 10 10 re f\nEMC\n"
        pdf = _page_with_content(content)
        path = tmp_path / "in.pdf"
        pdf.save(str(path))
        pdf.close()

        lo_export.artifact_postfix(path)

        result = pikepdf.open(str(path))
        instrs = pikepdf.parse_content_stream(result.pages[0])
        artifacts = [i for i in instrs if [str(o) for o in i.operands] == ["/Artifact"]]
        assert artifacts == []

    def test_non_paint_unmarked_run_not_wrapped(self, tmp_path):
        """A depth-0 unmarked run with no paint operator (e.g. just state
        setup) is left exactly as-is — only paint-containing runs get
        wrapped."""
        content = b"q 1 0 0 1 0 0 cm Q\n"
        pdf = _page_with_content(content)
        path = tmp_path / "in.pdf"
        pdf.save(str(path))
        pdf.close()

        lo_export.artifact_postfix(path)

        result = pikepdf.open(str(path))
        instrs = pikepdf.parse_content_stream(result.pages[0])
        assert [str(i.operator) for i in instrs] == ["q", "cm", "Q"]


class TestGetSoffice:
    def test_returns_none_when_absent(self, monkeypatch, tmp_path):
        monkeypatch.setattr(setup_remediation, "VENDOR", tmp_path / "vendor")
        assert lo_export.get_soffice() is None


class TestSofficeConvertErrors:
    def test_raises_when_soffice_missing(self, monkeypatch, tmp_path):
        monkeypatch.setattr(lo_export, "get_soffice", lambda: None)
        with pytest.raises(RuntimeError, match="not installed"):
            lo_export.export_pdf(tmp_path / "x.docx", tmp_path / "x.pdf")

    def test_raises_on_unsupported_extension(self, monkeypatch, tmp_path):
        monkeypatch.setattr(lo_export, "get_soffice", lambda: tmp_path / "fake-soffice")
        with pytest.raises(RuntimeError, match="unsupported extension"):
            lo_export.export_pdf(tmp_path / "x.txt", tmp_path / "x.pdf")


@heavy
@pytest.mark.skipif(not (LO_READY and REMEDIATION_TOOLS_READY),
                     reason="vendored LibreOffice/java/veraPDF not installed")
class TestExportLive:
    def test_ac11_good_pptx_export_compliant_after_postfix(self, tmp_path):
        """A well-formed deck (real title placeholders, real alt text, real
        first_row) exports via Impress and passes veraPDF UA1 after the
        artifact postfix fixes the double-background-paint bug."""
        import gen_office_fixtures as gf

        src = gf.build_good_pptx(tmp_path / "good.pptx")
        out = tmp_path / "good.pdf"

        result_path = lo_export.export_pdf(src, out)

        vera = verapdf_gate.run_verapdf(str(result_path))
        assert vera["compliant"] is True
        assert vera["failed_rules"] == 0
        assert vera["matterhorn_human_checkpoints"] == 47

    def test_docx_export_produces_pdf(self, tmp_path):
        import gen_office_fixtures as gf

        src = gf.build_bad_docx(tmp_path / "bad.docx")
        out = tmp_path / "bad.pdf"

        result_path = lo_export.export_pdf(src, out)
        assert result_path.exists()

    def test_render_pdf_plain_no_artifact_postfix_applied(self, tmp_path):
        """render_pdf_plain skips artifact_postfix -- appearance checks don't
        need (and shouldn't pay for) the compliance-path fixup."""
        import gen_office_fixtures as gf

        src = gf.build_bad_pptx(tmp_path / "bad.pptx")
        out = tmp_path / "bad_plain.pdf"

        result_path = lo_export.render_pdf_plain(src, out)
        assert result_path.exists()
