"""
FreeExile Monster & Character Scaffolding Invariants & Cross-Platform Parity E2E Test Suite.
Verifies deep structural completeness, Apple Metal configs, Song Binh pairs,
strict normalized UV bounds [0.0, 1.0], and Cocos/WebApp manifest parity.
"""

from __future__ import annotations

import json
from pathlib import Path
import pytest

from tests.e2e_cocos.vfx_test_helpers import (
    REPO_ROOT,
    COCOS_MONSTERS_DIR,
    COCOS_CHARACTERS_DIR,
    COCOS_MANIFEST_PATH,
    WEBAPP_VFX_DIR,
    CANONICAL_MONSTER_GENERA,
    CANONICAL_EXILE_CLASSES,
    CANONICAL_DIRECTIONS,
    validate_manifest_uv_bounds,
)


class TestScaffoldingStructureAndConfig:
    """Verifies filesystem completeness and pipeline configuration invariants."""

    def test_scaffolding_generator_scripts_exist(self) -> None:
        """Verifies generator script exists and has valid size."""
        script_path = REPO_ROOT / "tools" / "asset_pipeline" / "monster_character_pipeline_scaffold.py"
        assert script_path.exists(), f"Scaffolding script missing at {script_path}"
        assert script_path.stat().st_size >= 1024, "Scaffolding script too small or empty"

    def test_monster_archetypes_directory_and_manifest_completeness(self) -> None:
        """Verifies all 10 monster archetypes exist with manifest and pipeline config."""
        assert COCOS_MONSTERS_DIR.exists(), f"Monsters directory missing: {COCOS_MONSTERS_DIR}"
        for genus in CANONICAL_MONSTER_GENERA:
            g_dir = COCOS_MONSTERS_DIR / genus
            assert g_dir.is_dir(), f"Monster archetype directory missing: {g_dir}"
            manifest_file = g_dir / f"{genus}_anim_manifest.json"
            assert manifest_file.exists(), f"Monster manifest missing: {manifest_file}"
            pipeline_file = g_dir / "pipeline_config.json"
            assert pipeline_file.exists(), f"Monster pipeline config missing: {pipeline_file}"

    def test_exile_characters_directory_and_manifest_completeness(self) -> None:
        """Verifies all 6 exile characters exist with manifest and Song Binh config."""
        assert COCOS_CHARACTERS_DIR.exists(), f"Characters directory missing: {COCOS_CHARACTERS_DIR}"
        for class_id in CANONICAL_EXILE_CLASSES:
            c_dir = COCOS_CHARACTERS_DIR / class_id
            assert c_dir.is_dir(), f"Character class directory missing: {c_dir}"
            manifest_file = c_dir / f"{class_id}_anim_manifest.json"
            assert manifest_file.exists(), f"Character manifest missing: {manifest_file}"
            song_binh_file = c_dir / "song_binh_config.json"
            assert song_binh_file.exists(), f"Song Binh config missing: {song_binh_file}"

    def test_monster_pipeline_configs_apple_metal_conformance(self) -> None:
        """Verifies monster pipeline configs declare Apple Metal ASTC 4x4 format."""
        for genus in CANONICAL_MONSTER_GENERA:
            cfg_path = COCOS_MONSTERS_DIR / genus / "pipeline_config.json"
            with open(cfg_path, "r", encoding="utf-8") as f:
                cfg = json.load(f)
            assert cfg.get("hardware_compression") == "ASTC_4x4"
            assert cfg.get("apple_metal_pixel_format") == "MTLPixelFormatASTC_4x4_sRGB"
            assert cfg.get("target_resolution") == [2048, 8192]

    def test_character_song_binh_configs_weapon_swap_conformance(self) -> None:
        """Verifies character Song Binh configs define primary and secondary weapons."""
        for class_id in CANONICAL_EXILE_CLASSES:
            sb_path = COCOS_CHARACTERS_DIR / class_id / "song_binh_config.json"
            with open(sb_path, "r", encoding="utf-8") as f:
                sb = json.load(f)
            assert "primary" in sb and "secondary" in sb
            assert "weapon_type" in sb["primary"] and "weapon_type" in sb["secondary"]


class TestScaffoldingManifestUvInvariants:
    """Verifies mathematical UV boundedness [0.0, 1.0] and kinematics across all manifests."""

    def test_monster_manifests_all_frames_uv_strictly_bounded(self) -> None:
        """Verifies that 100% of monster animation frames satisfy 0.0 <= u, v <= 1.0."""
        for genus in CANONICAL_MONSTER_GENERA:
            manifest_path = COCOS_MONSTERS_DIR / genus / f"{genus}_anim_manifest.json"
            errors = validate_manifest_uv_bounds(manifest_path)
            assert not errors, f"Monster {genus} UV errors:\n" + "\n".join(errors[:5])

    def test_character_manifests_all_frames_uv_strictly_bounded(self) -> None:
        """Verifies that 100% of character animation frames satisfy 0.0 <= u, v <= 1.0."""
        for class_id in CANONICAL_EXILE_CLASSES:
            manifest_path = COCOS_CHARACTERS_DIR / class_id / f"{class_id}_anim_manifest.json"
            errors = validate_manifest_uv_bounds(manifest_path)
            assert not errors, f"Character {class_id} UV errors:\n" + "\n".join(errors[:5])

    def test_scaffolding_frame_pivots_and_directions_integrity(self) -> None:
        """Verifies bottom-center pivot [0.5, 0.90] and 8 canonical directions."""
        for genus in CANONICAL_MONSTER_GENERA:
            manifest_path = COCOS_MONSTERS_DIR / genus / f"{genus}_anim_manifest.json"
            with open(manifest_path, "r", encoding="utf-8") as f:
                data = json.load(f)
            assert data.get("defaultPivot") == [0.5, 0.90]
            assert set(data.get("directions", [])) == set(CANONICAL_DIRECTIONS)

        for class_id in CANONICAL_EXILE_CLASSES:
            manifest_path = COCOS_CHARACTERS_DIR / class_id / f"{class_id}_anim_manifest.json"
            with open(manifest_path, "r", encoding="utf-8") as f:
                data = json.load(f)
            assert data.get("defaultPivot") == [0.5, 0.90]
            assert set(data.get("directions", [])) == set(CANONICAL_DIRECTIONS)


class TestCrossPlatformManifestParity:
    """Verifies Cocos Creator vs WebApp Client manifest parity."""

    def test_cocos_and_webapp_manifest_cross_platform_parity(self) -> None:
        """Validates clip parity between Cocos and WebApp manifest if WebApp manifest exists."""
        webapp_manifest_path = WEBAPP_VFX_DIR / "savage_primal_skills_vfx_atlas.json"
        if not webapp_manifest_path.exists():
            pytest.skip(f"WebApp manifest not found at {webapp_manifest_path}; skipping parity check.")

        with open(COCOS_MANIFEST_PATH, "r", encoding="utf-8") as f:
            cocos_data = json.load(f)
        with open(webapp_manifest_path, "r", encoding="utf-8") as f:
            webapp_data = json.load(f)

        cocos_clips = set(cocos_data.get("clips", {}).keys())
        webapp_clips = set(webapp_data.get("clips", {}).keys())
        assert cocos_clips == webapp_clips, (
            f"Clips mismatch between Cocos and WebApp manifest. "
            f"In Cocos only: {cocos_clips - webapp_clips}, In WebApp only: {webapp_clips - cocos_clips}"
        )
