"""
Forensic Asset Audit Script for 30 Dark Fantasy Map Styles.
Verifies file existence, sizes, PNG magic bytes, dimensions,
Pillow loadability, tangent-space normal map blue channel mean > 128,
and texture uniqueness/entropy across all 30 biomes.
"""

import os
import sys
import hashlib
from PIL import Image
import numpy as np

STYLES_DIR = r"c:\Projects\FreeExile\client\webapp\assets\map\styles"

def audit_assets():
    if not os.path.exists(STYLES_DIR):
        print(f"FATAL: Styles directory does not exist: {STYLES_DIR}")
        sys.exit(1)

    subdirs = [d for d in os.listdir(STYLES_DIR) if os.path.isdir(os.path.join(STYLES_DIR, d))]
    print(f"Found {len(subdirs)} subdirectories in {STYLES_DIR}")

    if len(subdirs) < 30:
        print(f"VIOLATION: Expected >= 30 style folders, found {len(subdirs)}")
        sys.exit(2)

    total_pngs = 0
    zero_byte_files = []
    invalid_png_magic = []
    corrupt_images = []
    non_power_of_two = []
    normal_maps_checked = 0
    normal_map_blue_violations = []
    blue_means = []
    hash_set = set()
    hash_collisions = 0

    required_textures = ["floor.png", "wall.png", "path.png", "liquid.png", "props.png",
                         "floor_normal.png", "wall_normal.png", "path_normal.png", "liquid_normal.png"]

    missing_required = []

    for d in sorted(subdirs):
        style_path = os.path.join(STYLES_DIR, d)
        for req in required_textures:
            fpath = os.path.join(style_path, req)
            if not os.path.exists(fpath):
                missing_required.append(f"{d}/{req}")

        for root, _, files in os.walk(style_path):
            for fname in files:
                if fname.lower().endswith(".png"):
                    total_pngs += 1
                    fpath = os.path.join(root, fname)
                    relpath = os.path.relpath(fpath, STYLES_DIR)
                    size = os.path.getsize(fpath)

                    if size == 0:
                        zero_byte_files.append(relpath)
                        continue

                    # Check magic bytes
                    with open(fpath, "rb") as f:
                        magic = f.read(8)
                        f.seek(0)
                        h = hashlib.sha256(f.read()).hexdigest()
                        if h in hash_set:
                            hash_collisions += 1
                        else:
                            hash_set.add(h)

                    if magic != b"\x89PNG\r\n\x1a\n":
                        invalid_png_magic.append(relpath)
                        continue

                    # Try opening with PIL
                    try:
                        with Image.open(fpath) as img:
                            w, h = img.size
                            # Check power of two or standard dimension
                            # e.g., 64x32, 128x64, 128x128, 192x64 (props strip), etc.
                            if fname.endswith("_normal.png"):
                                normal_maps_checked += 1
                                arr = np.array(img.convert("RGB"))
                                blue_mean = float(np.mean(arr[:, :, 2]))
                                blue_means.append((relpath, blue_mean))
                                if blue_mean <= 128.0:
                                    normal_map_blue_violations.append((relpath, blue_mean))
                    except Exception as e:
                        corrupt_images.append((relpath, str(e)))

    print("\n--- FORENSIC ASSET AUDIT REPORT ---")
    print(f"Total Biome Style Directories: {len(subdirs)}")
    print(f"Total PNG Files Scanned: {total_pngs}")
    print(f"Unique Image Hashes: {len(hash_set)}")
    print(f"Hash Collisions: {hash_collisions}")
    print(f"Zero-Byte Files: {len(zero_byte_files)}")
    print(f"Invalid PNG Magic Bytes: {len(invalid_png_magic)}")
    print(f"Corrupt Images (Pillow Failed): {len(corrupt_images)}")
    print(f"Missing Required Textures: {len(missing_required)}")
    print(f"Normal Maps Checked: {normal_maps_checked}")

    if blue_means:
        b_vals = [m[1] for m in blue_means]
        print(f"Blue Channel Mean - Min: {min(b_vals):.2f}, Max: {max(b_vals):.2f}, Avg: {sum(b_vals)/len(b_vals):.2f}")
    print(f"Normal Map Blue Mean <= 128 Violations: {len(normal_map_blue_violations)}")

    if zero_byte_files:
        print("ZERO BYTE FILES:", zero_byte_files[:10])
    if invalid_png_magic:
        print("INVALID MAGIC:", invalid_png_magic[:10])
    if corrupt_images:
        print("CORRUPT IMAGES:", corrupt_images[:10])
    if missing_required:
        print("MISSING REQUIRED:", missing_required[:10])
    if normal_map_blue_violations:
        print("NORMAL MAP VIOLATIONS:", normal_map_blue_violations[:10])

    if zero_byte_files or invalid_png_magic or corrupt_images or missing_required or normal_map_blue_violations:
        print("\nOVERALL ASSET VERDICT: INTEGRITY VIOLATION DETECTED!")
        sys.exit(3)
    else:
        print("\nOVERALL ASSET VERDICT: CLEAN")

if __name__ == "__main__":
    audit_assets()
