"""Create four original diagnostic PDFs; deliberately injected faults, not an OCR benchmark.

Run in this directory after installing the versions listed in README.md and Poppler.
Outputs replace the generated PDF/TXT/JSON/preview files in this directory.
The unmodified Noto Sans font must be present under fonts/ with its OFL license.
"""

from io import BytesIO
from pathlib import Path
import hashlib
import importlib.metadata
import json
import platform
import subprocess

from PIL import Image
from pypdf import PdfReader, PdfWriter
from pypdf.generic import DecodedStreamObject, NameObject
from reportlab.lib.utils import ImageReader
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.pdfgen import canvas

ROOT = Path(__file__).resolve().parent
PREVIEW = ROOT / "preview"
PREVIEW.mkdir(exist_ok=True)
FONT = ROOT / "fonts" / "NotoSans-Regular.ttf"
FONT_SHA256 = "b85c38ecea8a7cfb39c24e395a4007474fa5a4fc864f6ee33309eb4948d232d5"
assert hashlib.sha256(FONT.read_bytes()).hexdigest() == FONT_SHA256
assert (ROOT / "fonts" / "OFL.txt").is_file()
pdfmetrics.registerFont(TTFont("Fixture", str(FONT)))
LINES = [
    "A small reading-file experiment",
    "The archive contains 18 notebooks.",
    "Item B-204 arrived on 6 May.",
    "Retain the original page as the reference.",
]
WRONG = [
    "A small reading-file experiment",
    "The archive contains 13 notebooks.",
    "Item B-2O4 arrived on 8 May.",
    "Retain the original page as the reference.",
]


def make_page(lines, invisible=False, background=None):
    output = BytesIO()
    page = canvas.Canvas(output, pagesize=(612, 792), invariant=1, pageCompression=0)
    page.setTitle("BookTranslator original PDF text-layer diagnostic fixture")
    if background:
        page.drawImage(ImageReader(str(background)), 0, 0, 612, 792)
    block = page.beginText(54, 714)
    block.setFont("Fixture", 16)
    block.setLeading(30)
    if invisible:
        block.setTextRenderMode(3)
    for line in lines:
        block.textLine(line)
    page.drawText(block)
    page.showPage()
    page.save()
    return output.getvalue()


def render(pdf, target):
    subprocess.run(
        ["pdftoppm", "-singlefile", "-r", "144", "-png", str(pdf), str(target)],
        check=True, capture_output=True,
    )


control = ROOT / "correct-digital-text.pdf"
control.write_bytes(make_page(LINES))
render(control, PREVIEW / "reference")
writer = PdfWriter()
writer.clone_document_from_reader(PdfReader(control))
changed = 0
for indirect in writer.pages[0]["/Resources"]["/Font"].values():
    font = indirect.get_object()
    if "/ToUnicode" not in font:
        continue
    original = font["/ToUnicode"].get_object().get_data()
    # Deliberately change extraction lookup, not the embedded font or visible glyphs.
    replacement = original.replace(b"<0038>", b"<0033>").replace(b"<0061>", b"<0078>")
    assert replacement != original
    stream = DecodedStreamObject()
    stream.set_data(replacement)
    font[NameObject("/ToUnicode")] = writer._add_object(stream)
    changed += 1
assert changed == 1
with (ROOT / "wrong-unicode-map.pdf").open("wb") as output:
    writer.write(output)

(ROOT / "wrong-ocr-text.pdf").write_bytes(
    make_page(WRONG, invisible=True, background=PREVIEW / "reference.png")
)
(ROOT / "image-only.pdf").write_bytes(make_page([], background=PREVIEW / "reference.png"))

results = []
for name in ["correct-digital-text", "wrong-unicode-map", "wrong-ocr-text", "image-only"]:
    pdf = ROOT / f"{name}.pdf"
    render(pdf, PREVIEW / name)
    poppler = subprocess.run(
        ["pdftotext", "-layout", str(pdf), "-"], check=True, text=True, capture_output=True
    ).stdout.strip()
    pypdf_text = "\n".join(page.extract_text() or "" for page in PdfReader(pdf).pages).strip()
    (ROOT / f"{name}.txt").write_text(poppler + "\n", encoding="utf-8")
    image = Image.open(PREVIEW / f"{name}.png").convert("RGB")
    results.append({
        "file": pdf.name,
        "pdf_sha256": hashlib.sha256(pdf.read_bytes()).hexdigest(),
        "poppler_text": poppler,
        "pypdf_text": pypdf_text,
        "pixel_sha256": hashlib.sha256(image.tobytes()).hexdigest(),
        "image_size": list(image.size),
    })

for extractor in ["poppler_text", "pypdf_text"]:
    assert results[0][extractor].splitlines() == LINES
    assert results[1][extractor] == "\n".join(LINES).replace("8", "3").replace("a", "x")
    assert results[2][extractor].splitlines() == WRONG
    assert results[3][extractor] == ""
assert results[0]["pixel_sha256"] == results[1]["pixel_sha256"]
assert results[2]["pixel_sha256"] == results[3]["pixel_sha256"]

version = subprocess.run(["pdftotext", "-v"], capture_output=True, text=True, check=True)
summary = {
    "test_date": "2026-09-10",
    "environment": {
        "platform": platform.system(), "python": platform.python_version(),
        **{name: importlib.metadata.version(name) for name in ["reportlab", "pypdf", "Pillow"]},
        "poppler": (version.stdout + version.stderr).splitlines()[0],
    },
    "scope": "Four constructed diagnostic examples. No OCR engine, repair application, or BookTranslator upload was tested.",
    "font_sha256": FONT_SHA256,
    "font_source_commit": "ffebf8c1ee449e544955a7e813c54f9b73848eac",
    "visible_expected_lines": LINES,
    "results": results,
    "pixel_identity_pairs": [
        ["correct-digital-text.pdf", "wrong-unicode-map.pdf"],
        ["wrong-ocr-text.pdf", "image-only.pdf"],
    ],
    "cross_pair_pixel_identity": results[0]["pixel_sha256"] == results[2]["pixel_sha256"],
    "excluded_fixtures": [],
    "retry_policy": "Stop on any failed assertion; do not select a favorable subset.",
    "limitations": [
        "Pairwise pixel equality is specific to Poppler rendering at 144 dpi; it is not an assertion across every viewer.",
        "Font-map and hidden-text errors were injected deliberately, not produced by OCR.",
        "No OCR accuracy, repair effectiveness, multilingual, or complex-layout benchmark.",
        "The authoring agent knows each injected fault; evaluation is not blinded.",
    ],
}
(ROOT / "results.json").write_text(json.dumps(summary, indent=2) + "\n", encoding="utf-8")
print(json.dumps(summary, indent=2))
