#!/usr/bin/env python3
"""Build a mixed four-page PDF and record extraction/OCR evidence."""

from __future__ import annotations

import hashlib
import json
import shutil
import subprocess
from pathlib import Path

from PIL import Image, ImageDraw, ImageFont
from pypdf import PdfReader, PdfWriter
from reportlab.lib.pagesizes import letter
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.pdfgen import canvas


ROOT = Path(__file__).resolve().parent
FONT_PATH = ROOT.parent / "fix-pdf-copy-paste-text" / "fonts" / "NotoSans-Regular.ttf"
PAGE_TEXT = {
    1: ["PAGE 1 — DIGITAL TEXT", "Batch AX-104 contains 27 folios.", "Keep section 4.2 with the appendix."],
    2: ["PAGE 2 — IMAGE ONLY", "Batch BX-205 contains 38 folios.", "Keep section 5.3 with the appendix."],
    3: ["PAGE 3 — EXISTING OCR", "Batch CX-306 contains 49 folios.", "Keep section 6.4 with the appendix."],
    4: ["PAGE 4 — MISLEADING OCR", "Batch DX-407 contains 58 folios.", "Keep section 7.5 with the appendix."],
}
WRONG_TEXT = [
    "PAGE 4 — MISLEADING OCR",
    "Batch DX-4O7 contains 53 folios.",
    "Keep section 7.8 with the appendix.",
]


def run(*args: str) -> subprocess.CompletedProcess[str]:
    return subprocess.run(args, check=True, text=True, capture_output=True)


def draw_page_image(lines: list[str], output: Path) -> None:
    image = Image.new("RGB", (2550, 3300), "white")
    draw = ImageDraw.Draw(image)
    title_font = ImageFont.truetype(str(FONT_PATH), 70)
    body_font = ImageFont.truetype(str(FONT_PATH), 58)
    draw.rectangle((180, 180, 2370, 3120), outline="#808080", width=6)
    draw.text((300, 420), lines[0], fill="black", font=title_font)
    draw.text((300, 720), lines[1], fill="black", font=body_font)
    draw.text((300, 860), lines[2], fill="black", font=body_font)
    draw.line((300, 1030, 2150, 1030), fill="#777777", width=5)
    image.save(output, quality=95, dpi=(300, 300))


def make_digital(output: Path) -> None:
    pdfmetrics.registerFont(TTFont("FixtureSans", str(FONT_PATH)))
    c = canvas.Canvas(str(output), pagesize=letter)
    c.setFont("FixtureSans", 18)
    c.drawString(72, 700, PAGE_TEXT[1][0])
    c.setFont("FixtureSans", 14)
    c.drawString(72, 640, PAGE_TEXT[1][1])
    c.drawString(72, 610, PAGE_TEXT[1][2])
    c.showPage()
    c.save()


def make_scan(image: Path, output: Path, hidden_lines: list[str] | None) -> None:
    c = canvas.Canvas(str(output), pagesize=letter)
    c.drawImage(str(image), 0, 0, width=letter[0], height=letter[1], preserveAspectRatio=True)
    if hidden_lines:
        text = c.beginText(72, 700)
        text.setFont("Helvetica", 14)
        text.setTextRenderMode(3)
        for line in hidden_lines:
            text.textLine(line)
        c.drawText(text)
    c.showPage()
    c.save()


def normalized(value: str) -> str:
    return " ".join(value.replace("\x0c", " ").split())


def sha256(path: Path) -> str:
    return hashlib.sha256(path.read_bytes()).hexdigest()


def main() -> None:
    for binary in ("pdftotext", "pdftoppm", "tesseract"):
        if not shutil.which(binary):
            raise SystemExit(f"Missing required command: {binary}")
    if not FONT_PATH.exists():
        raise SystemExit(f"Missing fixture font: {FONT_PATH}")

    work = ROOT / ".build"
    if work.exists():
        shutil.rmtree(work)
    work.mkdir()

    page_pdfs: list[Path] = []
    digital = work / "page-1.pdf"
    make_digital(digital)
    page_pdfs.append(digital)

    for page in (2, 3, 4):
        image = work / f"page-{page}.png"
        pdf = work / f"page-{page}.pdf"
        draw_page_image(PAGE_TEXT[page], image)
        hidden = None if page == 2 else PAGE_TEXT[page]
        if page == 4:
            hidden = WRONG_TEXT
        make_scan(image, pdf, hidden)
        page_pdfs.append(pdf)

    writer = PdfWriter()
    for pdf in page_pdfs:
        writer.append(PdfReader(str(pdf)))
    mixed_pdf = ROOT / "mixed-text-layer-test.pdf"
    with mixed_pdf.open("wb") as handle:
        writer.write(handle)

    evidence: list[dict[str, object]] = []
    for page in range(1, 5):
        extracted_path = work / f"page-{page}-extracted.txt"
        run("pdftotext", "-f", str(page), "-l", str(page), str(mixed_pdf), str(extracted_path))
        extracted = normalized(extracted_path.read_text(errors="replace"))

        prefix = work / f"page-{page}-render"
        run("pdftoppm", "-f", str(page), "-l", str(page), "-r", "300", "-png", "-singlefile", str(mixed_pdf), str(prefix))
        ocr_base = work / f"page-{page}-tesseract"
        run("tesseract", str(prefix.with_suffix(".png")), str(ocr_base), "--psm", "6", "-l", "eng")
        ocr = normalized(ocr_base.with_suffix(".txt").read_text(errors="replace"))

        evidence.append(
            {
                "page": page,
                "page_type": ["born-digital", "image-only", "existing-correct-ocr", "existing-wrong-ocr"][page - 1],
                "visible_reference": " ".join(PAGE_TEXT[page]),
                "pdftotext": extracted,
                "tesseract_from_render": ocr,
                "extraction_matches_visible_reference": extracted == " ".join(PAGE_TEXT[page]),
                "ocr_matches_visible_reference": ocr == " ".join(PAGE_TEXT[page]),
            }
        )

    results = {
        "generated_on": "2026-09-21",
        "fixture_sha256": sha256(mixed_pdf),
        "commands": {
            "pdftotext": run("pdftotext", "-v").stderr.splitlines()[0],
            "tesseract": run("tesseract", "--version").stdout.splitlines()[0],
        },
        "pages": evidence,
        "scope": "Controlled English fixture; not an OCR accuracy benchmark.",
    }
    (ROOT / "results.json").write_text(json.dumps(results, indent=2, ensure_ascii=False) + "\n")
    shutil.rmtree(work)


if __name__ == "__main__":
    main()
