"""Independent QC pass over everything in ../out."""
import os, sys, json, zipfile, glob
import xml.etree.ElementTree as ET
import numpy as np, trimesh
sys.path.insert(0,'.')
import tagkit as tk

OUT = os.path.abspath('../out')
idx = json.load(open(os.path.join(OUT,'tags.json')))
fails, warns = [], []

def chk(cond, msg, hard=True):
    if not cond:
        (fails if hard else warns).append(msg)

print(f"checking {len(idx)} tags\n")
for e in idx:
    d = os.path.join(OUT, e['dir']); sl = e['slug']
    for f in e['files']:
        chk(os.path.exists(os.path.join(d,f)), f"{sl}: missing {f}")

    A = trimesh.load(os.path.join(d, f'{sl}-front-half.stl'))
    Bm = trimesh.load(os.path.join(d, f'{sl}-back-half.stl'))
    for nm, m in (('front',A), ('back',Bm)):
        chk(m.is_watertight, f"{sl}: {nm} half not watertight")
        chk(abs(m.bounds[1][2]-tk.P.half_t) < 1e-3, f"{sl}: {nm} half thickness {m.bounds[1][2]:.3f}")
        chk(m.bounds[1][0]-m.bounds[0][0] <= tk.P.size+1e-3, f"{sl}: {nm} half too wide")

    # do the halves mate?  flip B about the vertical axis and compare footprints
    Bf = Bm.copy(); Bf.apply_transform(trimesh.transformations.reflection_matrix([0,0,0],[1,0,0]))
    sa = tk.outline('left'); sb = tk.outline('right')
    holeA = sa[2]; holeB = sb[2]
    chk(abs(holeA[0] + holeB[0]) < 1e-6 and abs(holeA[1]-holeB[1]) < 1e-6,
        f"{sl}: keychain holes do not mirror ({holeA} vs {holeB})")
    chk(abs(A.volume - Bm.volume) < 1e-6, f"{sl}: halves differ in volume {A.volume:.4f}/{Bm.volume:.4f}")

    chk(not os.path.exists(os.path.join(d, f'{sl}-1-piece.stl')),
        f"{sl}: one-piece STL should not be shipped", hard=False)

    for f in (f'{sl}-2-piece.3mf', f'{sl}-1-piece.3mf'):
        z = zipfile.ZipFile(os.path.join(d,f))
        for n in ['3D/3dmodel.model','Metadata/model_settings.config','[Content_Types].xml','_rels/.rels']:
            chk(n in z.namelist(), f"{sl}/{f}: missing {n}")
            try: ET.fromstring(z.read(n))
            except Exception as ex: fails.append(f"{sl}/{f}: bad XML in {n}: {ex}")
        ms = ET.fromstring(z.read('Metadata/model_settings.config'))
        mdl = ET.fromstring(z.read('3D/3dmodel.model'))
        ns = {'c':'http://schemas.microsoft.com/3dmanufacturing/core/2015/02'}
        objs = {o.get('id') for o in mdl.findall('.//c:object', ns)}
        built = [it.get('objectid') for it in mdl.findall('.//c:item', ns)]
        chk(all(b in objs for b in built), f"{sl}/{f}: build item references a missing object")
        for o in ms.findall('object'):
            chk(o.get('id') in objs, f"{sl}/{f}: model_settings object {o.get('id')} not in model")
            exts = {p.find("metadata[@key='extruder']").get('value') for p in o.findall('part')}
            chk(exts == {'1','2'}, f"{sl}/{f}: object {o.get('id')} extruders = {exts}")
            comp_ids = {c.get('objectid') for c in
                        mdl.find(f".//c:object[@id='{o.get('id')}']", ns).findall('.//c:component', ns)}
            part_ids = {p.get('id') for p in o.findall('part')}
            chk(comp_ids == part_ids, f"{sl}/{f}: part ids {part_ids} != component ids {comp_ids}")
    chk(os.path.exists(os.path.join(OUT,'previews',f'{sl}.png')), f"{sl}: no preview")

# glue keys must never break into the recessed artwork
for e in idx[:8]:
    art = tk.build_artwork(e['name'], e['number'])
    from shapely.geometry import Point
    for x,y in tk.P.key_positions:
        c = Point(x,y).buffer(tk.P.key_d/2)
        depth_left = tk.P.base_t - tk.P.key_depth
        chk(depth_left > 0.3, f"glue-key floor only {depth_left:.2f}mm")
        _,hole,_ = tk.outline('left'); _,hole2,_ = tk.outline('right')
        chk(not c.intersects(hole) and not c.intersects(hole2),
            f"{e['slug']}: glue key at ({x},{y}) hits the keychain hole")

print(f"FAILURES: {len(fails)}")
for f in fails[:25]: print("  ✗", f)
print(f"WARNINGS: {len(warns)}")
for w in warns[:10]: print("  !", w)
