#!/usr/bin/env python3
"""Verify the strong unchanged-topology candidate and its web deliverables."""

from __future__ import annotations

import hashlib
import json
from pathlib import Path

import numpy as np
from PIL import Image


ROOT = Path(__file__).resolve().parent
FINAL = ROOT / "final-strong"
VIEWS = ("top", "front", "side", "hero", "back-hero", "oblique")


def mask(path: Path) -> np.ndarray:
    # Workbench renders the object near black and the background near 59.
    return np.asarray(Image.open(path).convert("L"), dtype=np.uint8) < 30


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 main() -> None:
    metrics = json.loads(
        (ROOT / "cyclic-original-strong" / "cyclic-metrics.json").read_text()
    )
    geometry = metrics["variants"]["cycle-strong"]["metrics"]
    views: dict[str, dict[str, float | int]] = {}
    total_intersection = total_union = total_xor = 0
    for view in VIEWS:
        original = mask(ROOT / "masks" / "original" / f"original-{view}.png")
        strong = mask(
            ROOT / "cyclic-original-strong" / "masks" / f"strong-{view}.png"
        )
        intersection = int(np.count_nonzero(original & strong))
        union = int(np.count_nonzero(original | strong))
        xor = int(np.count_nonzero(original ^ strong))
        total_intersection += intersection
        total_union += union
        total_xor += xor
        views[view] = {
            "iou": intersection / max(union, 1),
            "changed_pixels": xor,
        }

    files = {}
    for path in sorted(FINAL.glob("*.glb")):
        metadata_path = path.with_suffix(".json")
        metadata = (
            json.loads(metadata_path.read_text()) if metadata_path.exists() else {}
        )
        files[path.name] = {
            "path": str(path),
            "bytes": path.stat().st_size,
            "sha256": sha256(path),
            "vertices": metadata.get("vertices"),
            "triangles": metadata.get("triangles"),
        }

    reimport = {
        name: json.loads((ROOT / "reimport-strong" / f"{name}.json").read_text())
        for name in ("web", "mobile")
    }
    aggregate_iou = total_intersection / max(total_union, 1)
    checks = {
        "master_topology_matches_original": metrics["vertices"] == 1015384
        and metrics["triangles"] == 1999660,
        "no_flipped_faces": geometry["flipped_faces"] == 0,
        "max_displacement_under_0_035_pct_diagonal": geometry[
            "displacement_max_pct_diagonal"
        ]
        < 0.035,
        "volume_change_under_0_001_pct": abs(geometry["volume_change_pct"]) < 0.001,
        "surface_area_change_under_0_05_pct": abs(
            geometry["surface_area_change_pct"]
        )
        < 0.05,
        "reflection_metric_improves_over_15_pct": geometry[
            "reflection_improvement_pct"
        ]
        > 15,
        "aggregate_silhouette_iou_over_0_999": aggregate_iou > 0.999,
        "front_silhouette_iou_over_0_9995": views["front"]["iou"] > 0.9995,
        "web_reimports_without_degenerate_faces": reimport["web"][
            "degenerate_triangles"
        ]
        == 0,
        "mobile_reimports_without_degenerate_faces": reimport["mobile"][
            "degenerate_triangles"
        ]
        == 0,
    }
    report = {
        "candidate": "cycle-strong",
        "method": "feature-aware guided normal filtering with trust-region projection",
        "geometry": geometry,
        "topology": metrics["topology"],
        "silhouette": {
            "aggregate_iou": aggregate_iou,
            "changed_pixels": total_xor,
            "views": views,
        },
        "files": files,
        "reimport": reimport,
        "checks": checks,
        "all_checks_pass": all(checks.values()),
    }
    output = FINAL / "verification.json"
    output.write_text(json.dumps(report, indent=2), encoding="utf-8")
    print(json.dumps(report, indent=2))
    if not report["all_checks_pass"]:
        raise SystemExit(2)


if __name__ == "__main__":
    main()
