"""Verifikace Babofkátoru na sadě testovacích modelů."""
from __future__ import annotations

import os
import sys

import numpy as np

sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
from babofkator import Params, run, export                     # noqa: E402
from babofkator.draft import (cone_dilate, cone_erode,          # noqa: E402
                              slope_from_draft, max_slope)
from babofkator.analysis import draft_angles, base_face_mask    # noqa: E402

EX = os.path.join(os.path.dirname(__file__), "..", "examples")
OUT = os.path.join(os.path.dirname(__file__), "..", "out")

FAIL = []


def ok(cond, msg):
    print(("  OK   " if cond else "  CHYBA") + "  " + msg)
    if not cond:
        FAIL.append(msg)


# --------------------------------------------------------------------------- #
def test_lipschitz_exactness():
    """Porovnání chamfer sweepingu s hrubou silou (exaktní kuželová obálka)."""
    print("\n[1] Přesnost Lipschitzovy obálky (chamfer vs. brute force)")
    rng = np.random.default_rng(0)
    n, res, s = 81, 0.5, 2.0
    h = np.zeros((n, n))
    for _ in range(8):
        j, i = rng.integers(5, n - 5, 2)
        h[j, i] = rng.uniform(2, 12)
    got = cone_dilate(h, res, s, rounds=3)

    yy, xx = np.mgrid[0:n, 0:n] * res
    exact = np.full((n, n), -np.inf)
    src = np.argwhere(h > 0)
    for j, i in src:
        d = np.hypot(xx - xx[j, i], yy - yy[j, i])
        exact = np.maximum(exact, h[j, i] - s * d)
    exact = np.maximum(exact, h)
    err = np.abs(got - exact).max()
    rel = err / max(exact.max(), 1e-9)
    print(f"       max. absolutní odchylka {err:.4f} mm ({rel*100:.2f} %)")
    ok(rel < 0.02, "chamfer obálka odpovídá exaktní do 2 %")

    er = cone_erode(h, res, s, rounds=3)
    ok(np.all(er <= h + 1e-9), "dolní obálka nikde nepřevyšuje původní pole")
    ok(max_slope(er, res) <= s * 1.02, "dolní obálka splňuje Lipschitzovu podmínku")


def test_models():
    names = ["kostka", "polokoule", "hrib", "rybina", "relief", "azteccoin"]
    for k, name in enumerate(names, start=2):
        path = os.path.join(EX, name + ".stl")
        if not os.path.exists(path):
            continue
        print(f"\n[{k}] Model {name}")
        p = Params(draft_deg=3.0, wall_mm=3.0, rim_mm=5.0)
        r = run(path, p)
        rep = r.report

        ach = rep["correction"]["achieved_min_draft_deg"]
        ok(ach >= p.draft_deg - 1e-6,
           f"dosažený minimální úkos {ach:.3f}° >= {p.draft_deg}°")

        for which in ("model", "mold"):
            c = rep["mesh_checks"][which]
            if c is None:
                continue
            ok(c["watertight"], f"{which}: vodotěsný (0 volných hran)")
            ok(c["n_nonmanifold_edges"] == 0,
               f"{which}: bez nemanifoldních hran")
            ok(c["consistent_orientation"],
               f"{which}: konzistentní orientace normál")
            ok(c["volume_mm3"] > 0, f"{which}: kladný objem "
                                    f"({c['volume_mm3']:.1f} mm³)")
            ok(c["n_degenerate_faces"] == 0, f"{which}: bez degenerovaných stěn")

        mw = rep["mold"]["min_wall_mm"]
        ok(mw >= p.wall_mm - 1e-6,
           f"minimální (svislá) tloušťka stěny formy {mw:.3f} mm")

        # kontrola úkosů přímo na vyexportovaném meshi modelu
        Vm, Fm = r.model_mesh
        dd = draft_angles(Vm, Fm)
        live = ~base_face_mask(Vm, Fm)
        worst = float(dd[live].min()) if live.any() else 90.0
        ok(worst >= p.draft_deg - 0.35,
           f"nejhorší úkos trojúhelníku korigovaného modelu {worst:.3f}°")

        # lem
        exp = rep["mold"]["domain_area_mm2"] - rep["mold"]["footprint_area_mm2"]
        ok(exp > 0, f"lem má nenulovou plochu ({exp:.1f} mm²)")

        print("       čas: " + ", ".join(f"{a}={b}s" for a, b
                                         in rep["timings_s"].items()))
        export(r, OUT, name)


def test_roundtrip():
    """Korigovaný model se znovu proežene analýzou — nesmí mít podkosy."""
    print("\n[9] Zpětná kontrola vyexportovaných STL")
    from babofkator.stlio import read_stl
    from babofkator.analysis import analyze
    from babofkator.raster import make_grid, height_field
    for name in ("hrib", "rybina", "azteccoin"):
        pth = os.path.join(OUT, f"{name}_korigovany.stl")
        if not os.path.exists(pth):
            continue
        V, F = read_stl(pth)
        an = analyze(V, F, 3.0)
        ok(an["n_undercut_faces"] == 0,
           f"{name}: vyexportovaný model nemá žádnou stěnu se záporným úkosem")
        ok(an["min_draft_deg"] >= 3.0 - 1e-3,
           f"{name}: nejmenší úkos {an['min_draft_deg']:.3f}°")
        # objem pod horní obálkou se musí rovnat objemu tělesa
        # (to platí právě tehdy, když je model výškovým polem = odformovatelný)
        from babofkator.raster import mesh_volume
        g = make_grid(V.min(0), V.max(0), max((V[:, 0].max() - V[:, 0].min())
                                              / 500.0, 0.02), 1.0)
        hf = height_field(V, F, g)
        v_env = float(hf["h"].sum()) * g.cell_area
        v_mesh = abs(mesh_volume(V, F))
        rel = abs(v_env - v_mesh) / max(v_mesh, 1e-9)
        ok(rel < 0.01, f"{name}: objem pod horní obálkou = objem tělesa "
                       f"(odchylka {rel*100:.3f} %) → těleso je výškové pole")
        multi = int((hf["hits"] > 2).sum())
        ok(multi <= 0.001 * hf["hits"].size,
           f"{name}: bez vícevrstevnatých oblastí (nalezeno {multi} px)")


def test_modes():
    print("\n[7] Režimy korekce na modelu 'rybina'")
    path = os.path.join(EX, "rybina.stl")
    for mode in ("add", "cut", "balanced"):
        r = run(path, Params(mode=mode))
        c = r.report["correction"]
        chk = r.report["mesh_checks"]["mold"]
        print(f"       {mode:9s} přidáno {c['material_added_mm3']:8.1f} mm³, "
              f"ubráno {c['material_removed_mm3']:8.1f} mm³, "
              f"úkos {c['achieved_min_draft_deg']:.2f}°")
        ok(c["achieved_min_draft_deg"] >= 3.0 - 1e-6,
           f"režim {mode}: úkos splněn")
        ok(chk["watertight"] and chk["n_nonmanifold_edges"] == 0,
           f"režim {mode}: forma je vodotěsná a manifoldní")


def test_params():
    print("\n[8] Nestandardní parametry (úkos 7°, stěna 2 mm, lem 10 mm, plochý hřbet)")
    r = run(os.path.join(EX, "relief.stl"),
            Params(draft_deg=7.0, wall_mm=2.0, rim_mm=10.0, back="flat"))
    rep = r.report
    ok(rep["correction"]["achieved_min_draft_deg"] >= 7.0 - 1e-6,
       f"úkos {rep['correction']['achieved_min_draft_deg']:.2f}° >= 7°")
    ok(rep["mold"]["min_wall_mm"] >= 2.0 - 1e-6,
       f"tloušťka stěny {rep['mold']['min_wall_mm']:.2f} mm")
    chk = rep["mesh_checks"]["mold"]
    ok(chk["watertight"] and chk["n_nonmanifold_edges"] == 0,
       "forma vodotěsná a manifoldní")


if __name__ == "__main__":
    test_lipschitz_exactness()
    test_models()
    test_modes()
    test_params()
    test_roundtrip()
    print("\n" + "=" * 60)
    if FAIL:
        print(f"NEÚSPĚŠNÝCH KONTROL: {len(FAIL)}")
        for f in FAIL:
            print("  - " + f)
        sys.exit(1)
    print("Všechny kontroly prošly.")
