"""
Shared constants, tables, fixtures, and validators for FreeExile VFX & Pipeline E2E tests.
"""

from __future__ import annotations

import json
from pathlib import Path
from typing import Any, Dict, List

from PIL import Image
import pytest

REPO_ROOT = Path(__file__).resolve().parent.parent.parent

COCOS_VFX_DIR = REPO_ROOT / "client" / "cocos" / "assets" / "resources" / "vfx"
WEBAPP_VFX_DIR = REPO_ROOT / "client" / "webapp" / "assets" / "vfx"
APPROVED_VFX_DIR = REPO_ROOT / "assets" / "approved" / "vfx"

COCOS_MANIFEST_PATH = COCOS_VFX_DIR / "savage_primal_skills_vfx_atlas.json"
COCOS_ALBEDO_PATH = COCOS_VFX_DIR / "savage_primal_skills_vfx_atlas.png"
COCOS_NORMAL_PATH = COCOS_VFX_DIR / "savage_primal_skills_vfx_atlas_normal.png"
COCOS_ASTC_PATH = COCOS_VFX_DIR / "savage_primal_skills_vfx_atlas.astc"

COCOS_MONSTERS_DIR = REPO_ROOT / "client" / "cocos" / "assets" / "resources" / "monsters" / "archetypes"
COCOS_CHARACTERS_DIR = REPO_ROOT / "client" / "cocos" / "assets" / "resources" / "characters"

CANONICAL_ACTIVE_SKILLS: List[Dict[str, Any]] = [
    {"id": 1001, "key": "cuu_tieu_loi_kiem", "aliases": ["lightning_sword", "skill_1001", "cuu_tieu_loi_kiem"]},
    {"id": 1002, "key": "thanh_phong_van_kiem_vu", "aliases": ["wind_sword", "skill_1002", "thanh_phong_van_kiem_vu"]},
    {"id": 1003, "key": "u_minh_huyet_doc_cham", "aliases": ["toxic_needle", "skill_1003", "u_minh_huyet_doc_cham"]},
    {"id": 1004, "key": "thien_ma_boc_doc_kinh", "aliases": ["poison_nova", "skill_1004", "thien_ma_boc_doc_kinh"]},
    {"id": 1005, "key": "bang_phach_han_quang_tran", "aliases": ["glacial_seal", "skill_1005", "bang_phach_han_quang_tran", "han_bang_liet_phach_thuong"]},
    {"id": 1006, "key": "bang_tien_xuyen_tam", "aliases": ["frost_arrow", "skill_1006", "bang_tien_xuyen_tam", "huyen_minh_han_bang_chuong"]},
    {"id": 1007, "key": "liet_hoa_toan_phong_tram", "aliases": ["fire_whirlwind_slash", "skill_1007", "liet_hoa_toan_phong_tram"]},
    {"id": 1008, "key": "liet_diem_boc_khi_tram", "aliases": ["magma_cleave", "skill_1008", "liet_diem_boc_khi_tram", "cuu_u_liet_diem_chuong"]},
    {"id": 1009, "key": "kim_cang_truy_dia_chan", "aliases": ["ground_slam_crater", "skill_1009", "kim_cang_truy_dia_chan"]},
    {"id": 1010, "key": "kim_cang_bat_hoai_the", "aliases": ["unyielding_guard", "skill_1010", "kim_cang_bat_hoai_the"]},
    {"id": 1011, "key": "huyet_ma_khap_huyet_kinh", "aliases": ["blood_drain_aoe", "skill_1011", "huyet_ma_khap_huyet_kinh"]},
    {"id": 1012, "key": "man_cot_trieu_hon_do", "aliases": ["bone_totem_summon", "skill_1012", "man_cot_trieu_hon_do"]},
]

CANONICAL_MOBILITY_SKILL: Dict[str, Any] = {
    "id": 1099,
    "key": "huyen_anh_bo",
    "aliases": ["ghost_trail_evasion", "skill_1099", "huyen_anh_bo", "dodge_evasion"],
}

CANONICAL_SUPPORT_SIGILS: List[Dict[str, Any]] = [
    {"id": 2001, "key": "phan_quang_da_hoa_an", "aliases": ["multi_projectile_split", "sigil_2001", "phan_quang_da_hoa_an"]},
    {"id": 2002, "key": "boc_pha_khuech_dai_an", "aliases": ["aoe_expansion", "sigil_2002", "boc_pha_khuech_dai_an"]},
    {"id": 2004, "key": "huyet_khi_quy_tong_an", "aliases": ["blood_magic", "sigil_2004", "huyet_khi_quy_tong_an"]},
    {"id": 2005, "key": "loi_dinh_xau_chuoi_an", "aliases": ["chain_lightning_arc", "sigil_2005", "loi_dinh_xau_chuoi_an"]},
    {"id": 2006, "key": "cuc_lanh_an", "aliases": ["freeze_affliction", "sigil_2006", "cuc_lanh_an", "sigil_2003", "cuc_han_boc_kinh_an", "damage_boost"]},
]

CANONICAL_MONSTER_GENERA = [
    "mob_risen_skeleton", "mob_feral_hellhound", "mob_blood_crawler",
    "mob_bramble_treant", "mob_corrupted_raptor", "mob_flesh_abomination",
    "mob_ironhide_behemoth", "mob_primal_cannibal", "mob_shadow_wraith", "mob_tomb_lord"
]

CANONICAL_EXILE_CLASSES = [
    "char_sword_master", "char_sword_maiden", "char_feral_berserker",
    "char_wild_archer", "char_glacial_lancer", "char_shadow_assassin"
]

CANONICAL_DIRECTIONS = ["S", "SW", "W", "NW", "N", "NE", "E", "SE"]


def match_clip_identifier(clip_name: str, definition: Dict[str, Any]) -> bool:
    """Matches a manifest clip key against skill/sigil id and aliases."""
    c_lower = clip_name.lower()
    if str(definition["id"]) in c_lower:
        return True
    if definition["key"].lower() in c_lower:
        return True
    for alias in definition.get("aliases", []):
        if alias.lower() in c_lower:
            return True
    return False


def validate_manifest_uv_bounds(manifest_path: Path) -> List[str]:
    """Validates UV coordinates and pixel boundaries for all frames in a manifest."""
    if not manifest_path.exists():
        return [f"File missing: {manifest_path}"]
    try:
        with open(manifest_path, "r", encoding="utf-8") as f:
            data = json.load(f)
    except Exception as e:
        return [f"Invalid JSON in {manifest_path}: {e}"]

    tex_w = float(data.get("textureWidth", data.get("texture_width", 2048)))
    tex_h = float(data.get("textureHeight", data.get("texture_height", 2048)))
    frame_w = float(data.get("frameWidth", data.get("frame_width", 160)))
    frame_h = float(data.get("frameHeight", data.get("frame_height", 160)))
    cols = int(data.get("cols", max(1, int(tex_w // frame_w))))

    clips = data.get("clips", {})
    if not clips:
        return [f"{manifest_path.name}: No clips defined in manifest"]

    errors: List[str] = []
    global_idx = 0

    for clip_name, clip_data in clips.items():
        frames = clip_data.get("frames", [])
        if not frames:
            errors.append(f"{manifest_path.name} -> {clip_name}: No frames")
            continue

        for local_idx, f_desc in enumerate(frames):
            if isinstance(f_desc, dict) and "uv" in f_desc:
                u0, v0, u1, v1 = f_desc["uv"]
            elif isinstance(f_desc, dict) and "x" in f_desc:
                x, y = f_desc["x"], f_desc["y"]
                w, h = f_desc.get("w", frame_w), f_desc.get("h", frame_h)
                u0, v0 = x / tex_w, y / tex_h
                u1, v1 = (x + w) / tex_w, (y + h) / tex_h
            else:
                col = (global_idx + local_idx) % cols
                row = (global_idx + local_idx) // cols
                u0 = (col * frame_w) / tex_w
                v0 = (row * frame_h) / tex_h
                u1 = ((col + 1) * frame_w) / tex_w
                v1 = ((row + 1) * frame_h) / tex_h

            if not (0.0 <= u0 <= 1.0 and 0.0 <= v0 <= 1.0 and 0.0 <= u1 <= 1.0 and 0.0 <= v1 <= 1.0):
                errors.append(
                    f"{manifest_path.name} -> {clip_name}[{local_idx}]: UV out of bounds [0.0, 1.0]: "
                    f"({u0:.4f}, {v0:.4f}, {u1:.4f}, {v1:.4f}) [atlas: {tex_w:.0f}x{tex_h:.0f}]"
                )
            if u1 <= u0 or v1 <= v0:
                errors.append(f"{manifest_path.name} -> {clip_name}[{local_idx}]: Degenerate UV bounds")

        global_idx += len(frames)
    return errors


@pytest.fixture(scope="module")
def loaded_manifest() -> Dict[str, Any]:
    """Loads Cocos VFX manifest JSON."""
    assert COCOS_MANIFEST_PATH.exists(), f"Cocos VFX manifest not found at {COCOS_MANIFEST_PATH}"
    with open(COCOS_MANIFEST_PATH, "r", encoding="utf-8") as f:
        data = json.load(f)
    assert isinstance(data, dict), "Manifest JSON must be a dictionary"
    return data


@pytest.fixture(scope="module")
def loaded_albedo_image() -> Image.Image:
    """Loads Cocos Albedo Diffuse Atlas PNG."""
    assert COCOS_ALBEDO_PATH.exists(), f"Albedo atlas texture not found at {COCOS_ALBEDO_PATH}"
    img = Image.open(COCOS_ALBEDO_PATH)
    assert img.mode in ("RGBA", "RGB"), f"Albedo texture must be RGBA or RGB, got {img.mode}"
    return img


@pytest.fixture(scope="module")
def loaded_normal_image() -> Image.Image:
    """Loads Cocos Tangent Normal Atlas PNG."""
    assert COCOS_NORMAL_PATH.exists(), f"Normal atlas texture not found at {COCOS_NORMAL_PATH}"
    img = Image.open(COCOS_NORMAL_PATH)
    assert img.mode in ("RGBA", "RGB"), f"Normal map texture must be RGBA or RGB, got {img.mode}"
    return img
