"""Static and visual validation for the TDC v01-RC2 OMR bundle."""

from __future__ import annotations

import hashlib
import json
import subprocess
import tempfile
from pathlib import Path
from zipfile import ZipFile

from PIL import Image, ImageDraw


SCRIPT_DIR = Path(__file__).resolve().parent
if (SCRIPT_DIR / "manifest.json").is_file():
    # Portable copy included at the root of the delivered bundle.
    BUNDLE = SCRIPT_DIR
    OVERLAYS = Path.cwd() / "validation_overlays"
else:
    # Source-tree execution during bundle construction.
    BUNDLE = SCRIPT_DIR / "output/omr/TDC_Form_Family_v01_RC2_OMR"
    OVERLAYS = SCRIPT_DIR / "tmp/omr/TDC_Form_Family_v01_RC2_overlays"


def sha256(path: Path) -> str:
    digest = hashlib.sha256()
    with path.open("rb") as stream:
        for chunk in iter(lambda: stream.read(1024 * 1024), b""):
            digest.update(chunk)
    return digest.hexdigest()


def bubble_rectangles(template: dict) -> list[tuple[int, int, int, int]]:
    default_w, default_h = template["bubbleDimensions"]
    rectangles = []
    for block in template["fieldBlocks"].values():
        width, height = block.get("bubbleDimensions", [default_w, default_h])
        x0, y0 = block["origin"]
        values = block.get("bubbleValues")
        if values is None:
            field_types = {
                "QTYPE_INT": list("0123456789"),
                "QTYPE_MCQ4": list("ABCD"),
                "QTYPE_MCQ5": list("ABCDE"),
            }
            values = field_types[block["fieldType"]]
        vertical = block.get("direction", "vertical") == "vertical"
        for field_index, _field in enumerate(block["fieldLabels"]):
            for value_index, _value in enumerate(values):
                x = x0 + (field_index * block["labelsGap"] if vertical else value_index * block["bubblesGap"])
                y = y0 + (value_index * block["bubblesGap"] if vertical else field_index * block["labelsGap"])
                rectangles.append((round(x), round(y), round(x + width), round(y + height)))
    return rectangles


def render_marker_crop(pdf: Path) -> Image.Image:
    with tempfile.TemporaryDirectory() as td:
        prefix = Path(td) / "page"
        subprocess.run(
            ["pdftoppm", "-f", "1", "-singlefile", "-r", "200", "-png", str(pdf), str(prefix)],
            check=True,
            stdout=subprocess.DEVNULL,
            stderr=subprocess.DEVNULL,
        )
        image = Image.open(prefix.with_suffix(".png")).convert("RGB")
        scale_x = image.width / 612.0
        scale_y = image.height / 792.0
        crop = image.crop((39 * scale_x, 39 * scale_y, 573 * scale_x, 753 * scale_y))
        return crop.resize((1700, 2200), Image.Resampling.LANCZOS)


def validate() -> None:
    manifest = json.loads((BUNDLE / "manifest.json").read_text())
    assert manifest["layout_count"] == 6
    assert manifest["form_count"] == 8

    checksum_lines = (BUNDLE / "SHA256SUMS.txt").read_text().splitlines()
    for line in checksum_lines:
        expected, relative = line.split("  ", 1)
        actual = sha256(BUNDLE / relative)
        assert actual == expected, f"Checksum mismatch: {relative}"

    archive_name = "TDC_Form_Family_v01_RC2_OMR_Wrapper_Bundle_v03.zip"
    archive_candidates = [BUNDLE / archive_name, BUNDLE.parent / archive_name]
    archive = next((candidate for candidate in archive_candidates if candidate.is_file()), None)
    if archive:
        with ZipFile(archive) as zf:
            assert zf.testzip() is None
            assert all(not Path(name).is_absolute() and ".." not in Path(name).parts for name in zf.namelist())

    expected_questions = {
        "40-ABCD": (40, 4),
        "40-ABCDE": (40, 5),
        "40-ABCDEF": (40, 6),
        "41-ABCD": (41, 4),
        "50-ABCD": (50, 4),
        "50-ABCDE": (50, 5),
    }

    OVERLAYS.mkdir(parents=True, exist_ok=True)
    for layout, (question_count, choice_count) in expected_questions.items():
        template_path = BUNDLE / "layouts" / layout / "template.json"
        template = json.loads(template_path.read_text())
        assert template["pageDimensions"] == [1700, 2200]
        assert template["outputColumns"] == ["DriverIDRows"] + [f"q{i}" for i in range(1, question_count + 1)]
        rectangles = bubble_rectangles(template)
        assert len(rectangles) == 30 + question_count * choice_count
        assert all(0 <= x0 < x1 < 1700 and 0 <= y0 < y1 < 2200 for x0, y0, x1, y1 in rectangles)

        form = next(item for item in manifest["forms"] if item["layout"] == layout)
        pdf = BUNDLE / "source_pdfs" / form["pdf"]
        overlay = render_marker_crop(pdf)
        draw = ImageDraw.Draw(overlay)
        for rect in rectangles:
            draw.rectangle(rect, outline=(255, 0, 0), width=3)
        overlay.save(OVERLAYS / f"{layout}.png")

    archive_result = "ZIP, " if archive else ""
    print(f"PASS: checksums, {archive_result}six layouts, field counts, bounds, and overlay renders")


if __name__ == "__main__":
    validate()
