"""Detailed verification of:
1. Size budgets for all 55 PNGs against their DRQ specs.
2. Exact Blue channel means of all 6 normal maps.
3. Content of the 2 emissive masks.
4. Content and compilation syntax check of the 3 Metal shaders.
5. Content and schemas of the 20 Atlas metadata JSON manifests.
"""
import os
import re
import json
from pathlib import Path
from PIL import Image
import numpy as np

PROJECT_ROOT = Path("c:/Projects/FreeExile")
APPROVED_DRQ_DIR = PROJECT_ROOT / "docs" / "design_requests" / "approved"
APPROVED_ASSETS_DIR = PROJECT_ROOT / "assets" / "approved"

DRQ_IDS = [
    "DRQ-20261001-CLI-LOADING-SCREEN-ART",
    "DRQ-20261001-CLI-LOGIN-PARALLAX-LAYERS",
    "DRQ-20261001-CLI-TOUCH-CONTROLS-PROMOTION",
    "DRQ-20261001-CLI-WORLD-BACKGROUND-LAYERS",
    "DRQ-20261001-ECO-BAZAAR-TRADE-PANELS",
    "DRQ-20261001-ECO-CRAFTING-ORBS-CATALOG",
    "DRQ-20261001-ECO-CURRENCY-BLOODSTONES",
    "DRQ-20261001-ECO-LOOT-DROP-PARTICLES",
    "DRQ-20261001-NPC-CUTSCENE-STORYBOARD",
    "DRQ-20261001-NPC-FERAL-ELDERS-AVATARS",
    "DRQ-20261001-NPC-QUEST-SCROLL-VARIANTS",
    "DRQ-20261001-SEC-ALERT-BADGES-QUARANTINE",
    "DRQ-20261001-SEC-SHIELD-ACTIVE-ANIMATION",
    "DRQ-20261001-SEC-UI-FORENSIC-WATERMARK",
    "DRQ-20261001-SRV-AGENT-ORB-ACTIVATION-VFX",
    "DRQ-20261001-SRV-AOI-SYNC-TELEMETRY-VISUAL",
    "DRQ-20261001-SRV-HARVEST-REPORT-OVERLAY",
    "DRQ-20261001-SYS-CRAFTING-STATION-UI",
    "DRQ-20261001-SYS-HIDEOUT-SAFEHAVEN-PROPS",
    "DRQ-20261001-SYS-MAP-DEVICE-ARENA",
    "DRQ-20261001-SYS-PASSIVE-TREE-NODES",
]

def parse_size_budget(budget_str: str) -> int:
    # e.g. "< 2MB", "< 150KB", "< 500KB", "< 1MB"
    m = re.search(r"<\s*(\d+(?:\.\d+)?)\s*(MB|KB)", budget_str, re.IGNORECASE)
    if not m:
        return 2 * 1024 * 1024 # default 2MB
    val = float(m.group(1))
    unit = m.group(2).upper()
    if unit == "MB":
        return int(val * 1024 * 1024)
    elif unit == "KB":
        return int(val * 1024)
    return int(val)

def main():
    print("=== Detailed Tech Art Specifications Audit ===")
    
    # Check normal maps
    print("\n--- 1. Normal Maps Audit (Expected: 6, Blue Mean >= 128) ---")
    normal_maps = list(APPROVED_ASSETS_DIR.glob("*/*_normal.png"))
    print(f"Found {len(normal_maps)} normal map files.")
    for nm in sorted(normal_maps):
        with Image.open(nm) as img:
            arr = np.array(img.convert("RGB"))
            b_mean = float(np.mean(arr[:, :, 2]))
            r_mean = float(np.mean(arr[:, :, 0]))
            g_mean = float(np.mean(arr[:, :, 1]))
            # Tangent space neutral normal is (128, 128, 255)
            # Check dimensions
            w, h = img.size
            print(f"  {nm.parent.name}/{nm.name}: {w}x{h}, RGB means: R={r_mean:.1f}, G={g_mean:.1f}, B={b_mean:.1f} (Pass: {b_mean >= 128.0})")
            assert b_mean >= 128.0, f"B mean below 128: {b_mean}"

    # Check emissive masks
    print("\n--- 2. Emissive Masks Audit (Expected: 2) ---")
    emissive_masks = list(APPROVED_ASSETS_DIR.glob("*/*_emissive.png"))
    print(f"Found {len(emissive_masks)} emissive mask files.")
    for em in sorted(emissive_masks):
        with Image.open(em) as img:
            w, h = img.size
            arr = np.array(img)
            non_zero = int(np.count_nonzero(arr))
            print(f"  {em.parent.name}/{em.name}: {w}x{h}, mode={img.mode}, non-zero pixels={non_zero}")

    # Check Metal shaders
    print("\n--- 3. Metal Shaders Audit (Expected: 3) ---")
    metal_shaders = list(APPROVED_ASSETS_DIR.glob("*/*.metal"))
    print(f"Found {len(metal_shaders)} metal shader files.")
    for ms in sorted(metal_shaders):
        content = ms.read_text(encoding="utf-8")
        lines = content.strip().splitlines()
        has_include = "#include <metal_stdlib>" in content
        has_kernel_or_vertex = any(kw in content for kw in ["kernel ", "vertex ", "fragment "])
        print(f"  {ms.parent.name}/{ms.name}: {len(lines)} lines, size={ms.stat().st_size} bytes, include={has_include}, shader_func={has_kernel_or_vertex}")
        assert has_include and has_kernel_or_vertex, f"Shader invalid: {ms}"

    # Check JSON manifests
    print("\n--- 4. JSON Manifests Audit (Expected: 20) ---")
    json_manifests = list(APPROVED_ASSETS_DIR.glob("*/*.json"))
    print(f"Found {len(json_manifests)} JSON manifests.")
    for jm in sorted(json_manifests):
        with open(jm, "r", encoding="utf-8") as f:
            data = json.load(f)
            # check basic keys
            keys = list(data.keys())
            print(f"  {jm.parent.name}/{jm.name}: valid JSON with keys {keys[:5]} (total {len(keys)} keys)")

    # Check Size budgets for all 55 PNGs against their DRQ
    print("\n--- 5. Size Budget Audit for All 55 PNGs ---")
    size_budget_violations = []
    png_count = 0
    for drq_id in DRQ_IDS:
        drq_file = APPROVED_DRQ_DIR / f"{drq_id}.md"
        content = drq_file.read_text(encoding="utf-8")
        m = re.search(r"Size Budget.*?: `?([^`\n]+)`?", content, re.IGNORECASE)
        budget_str = m.group(1).strip() if m else "< 2MB"
        budget_bytes = parse_size_budget(budget_str)

        drq_dir = APPROVED_ASSETS_DIR / drq_id
        for png in sorted(drq_dir.glob("*.png")):
            png_count += 1
            size = png.stat().st_size
            passed = size <= budget_bytes
            if not passed:
                size_budget_violations.append((png.name, size, budget_bytes, budget_str))
            # print if close or interesting
            # print(f"  {png.name}: {size} bytes vs budget {budget_bytes} ({budget_str}) -> {'PASS' if passed else 'FAIL'}")

    print(f"Audited {png_count} PNGs across {len(DRQ_IDS)} DRQs.")
    print(f"Size budget violations: {len(size_budget_violations)}")
    if size_budget_violations:
        for v in size_budget_violations:
            print(f"  VIOLATION: {v[0]}: {v[1]} bytes exceeds budget {v[2]} bytes ({v[3]})")
    assert len(size_budget_violations) == 0, f"Found size budget violations!"
    print("ALL 55 PNGs strictly meet size budgets!")

if __name__ == "__main__":
    main()
