#!/usr/bin/env python3
"""Validate generated character asset manifest and PNG files."""

from __future__ import annotations

import json
import struct
import sys
from pathlib import Path


ROOT = Path(__file__).resolve().parents[1]
DATA = ROOT / "data"
PNG_SIGNATURE = b"\x89PNG\r\n\x1a\n"


def load_json(path: Path) -> dict:
    with path.open("r", encoding="utf-8") as handle:
        return json.load(handle)


def fail(message: str) -> None:
    print(f"ERROR: {message}", file=sys.stderr)
    raise SystemExit(1)


def png_dimensions(path: Path) -> tuple[int, int]:
    with path.open("rb") as handle:
        signature = handle.read(8)
        if signature != PNG_SIGNATURE:
            fail(f"{path} is not a PNG file")

        length_bytes = handle.read(4)
        chunk_type = handle.read(4)
        if len(length_bytes) != 4 or chunk_type != b"IHDR":
            fail(f"{path} is missing a PNG IHDR chunk")

        length = struct.unpack(">I", length_bytes)[0]
        if length < 8:
            fail(f"{path} has an invalid PNG IHDR chunk")

        width, height = struct.unpack(">II", handle.read(8))
        return width, height


def main() -> None:
    characters_doc = load_json(DATA / "characters.json")
    assets_doc = load_json(DATA / "character_assets.json")

    character_ids = [character["id"] for character in characters_doc["characters"]]
    character_id_set = set(character_ids)
    asset_entries = assets_doc["assets"]
    asset_ids = [entry["character_id"] for entry in asset_entries]

    if set(asset_ids) != character_id_set:
        missing = sorted(character_id_set.difference(asset_ids))
        extra = sorted(set(asset_ids).difference(character_id_set))
        fail(f"asset manifest mismatch; missing={missing}, extra={extra}")

    if len(asset_ids) != len(set(asset_ids)):
        fail("asset manifest contains duplicate character ids")

    minimum = assets_doc["minimum_dimensions"]
    min_width = minimum["width"]
    min_height = minimum["height"]
    accepted = 0

    reference = ROOT / assets_doc["style"]["reference"]
    if not reference.exists():
        fail(f"style reference is missing: {reference}")
    png_dimensions(reference)

    for entry in asset_entries:
        status = entry["status"]
        if status not in {"accepted", "needs_revision", "pending", "rejected"}:
            fail(f"{entry['character_id']} has invalid status {status}")

        if status not in {"accepted", "needs_revision"}:
            continue

        if status == "accepted":
            accepted += 1

        if "path" not in entry:
            fail(f"{entry['character_id']} has status {status} but no path")

        path = ROOT / entry["path"]
        if not path.exists():
            fail(f"{entry['character_id']} asset is missing: {path}")

        width, height = png_dimensions(path)
        if width < min_width or height < min_height:
            fail(
                f"{entry['character_id']} asset is too small: "
                f"{width}x{height}, minimum {min_width}x{min_height}"
            )

    revision_count = sum(
        1 for entry in asset_entries if entry["status"] == "needs_revision"
    )
    print(
        "Validated character asset manifest: "
        f"{accepted}/{len(asset_entries)} accepted, "
        f"{revision_count} needs revision."
    )


if __name__ == "__main__":
    main()
