"""
Tier 1: Feature Coverage — UI HUD Orbs, Minimap, 40-Slot Backpack, Char Creation & i18n.
Covers Features 18, 19, 20, 21 from PROJECT.md Feature Inventory:
- Feature 18: Dual Liquid Orbs & Minimap
- Feature 19: 40-Slot Backpack Inventory
- Feature 20: Character Creation & Randomizer
- Feature 21: Reactive 9-Language i18n
"""

from __future__ import annotations

import math
from typing import Any, Dict, List, Optional, Tuple
import pytest

from tests.e2e_cocos.conftest import (
    BACKPACK_CAPACITY,
    CANONICAL_CLASSES,
    CANONICAL_MARTIAL_NAMES,
    SUPPORTED_LOCALES,
    SimulatedCocosPlayer,
)


class TestTier1DualLiquidOrbsAndMinimap:
    """Feature 18: Dual Liquid Orbs & Minimap."""

    def test_t1_f18_sinusoidal_liquid_wave_equation(self) -> None:
        # y_wave(x) = y_fill + sin(x * 0.05 + t * 3.0) * 4.5 + cos(x * 0.03 - t * 2.0) * 2.5
        y_fill = 50.0
        t = 1.0
        x = 20.0
        wave_offset = math.sin(x * 0.05 + t * 3.0) * 4.5 + math.cos(x * 0.03 - t * 2.0) * 2.5
        y_wave = y_fill + wave_offset
        assert abs(wave_offset) <= 7.0  # Combined amplitude bounds 4.5 + 2.5
        assert 43.0 <= y_wave <= 57.0

    def test_t1_f18_low_hp_alert_vignette_threshold_25_percent(self) -> None:
        max_hp = 100.0
        hp_safe = 30.0
        hp_danger = 20.0
        is_safe_alert = (hp_safe / max_hp) < 0.25
        is_danger_alert = (hp_danger / max_hp) < 0.25
        assert is_safe_alert is False
        assert is_danger_alert is True

    def test_t1_f18_minimap_viewport_dimensions_120x80(self) -> None:
        minimap_w, minimap_h = 120, 80
        assert minimap_w == 120
        assert minimap_h == 80

    def test_t1_f18_minimap_radar_blip_color_mappings(self) -> None:
        blip_colors = {
            "player": "#ffffff",
            "waypoint": "#10b981",  # Emerald green
            "boss_gate": "#ef4444",  # Crimson
            "poi": "#f59e0b",        # Amber
        }
        assert blip_colors["player"] == "#ffffff"
        assert blip_colors["boss_gate"] == "#ef4444"

    def test_t1_f18_fog_of_war_three_states_and_transitions(self) -> None:
        fog_states = {"UNEXPLORED": 0, "EXPLORED_FOGGED": 1, "VISIBLE": 2}
        tile_fog = fog_states["UNEXPLORED"]
        # Player enters within 8 tiles radius -> VISIBLE
        tile_fog = fog_states["VISIBLE"]
        assert tile_fog == 2
        # Player moves away -> transitions to EXPLORED_FOGGED
        tile_fog = fog_states["EXPLORED_FOGGED"]
        assert tile_fog == 1

    def test_t1_f18_fog_reveal_circular_radius_8_tiles(self) -> None:
        reveal_radius = 8.0
        player_x, player_y = 10, 10
        tile_near = (12, 14)  # dist = sqrt(4 + 16) = 4.47 <= 8.0
        tile_far = (18, 18)   # dist = sqrt(64 + 64) = 11.31 > 8.0
        assert math.hypot(tile_near[0] - player_x, tile_near[1] - player_y) <= reveal_radius
        assert math.hypot(tile_far[0] - player_x, tile_far[1] - player_y) > reveal_radius


class TestTier1BackpackInventory40Slots:
    """Feature 19: 40-Slot Backpack Inventory."""

    def test_t1_f19_exact_40_slot_capacity(self, simulated_player: SimulatedCocosPlayer) -> None:
        assert len(simulated_player.inventory) == BACKPACK_CAPACITY

    def test_t1_f19_item_insertion_first_available_slot(self, simulated_player: SimulatedCocosPlayer) -> None:
        item = {"id": "bloodstone_t1", "name": "Huyết Hồn Thạch", "rarity": "MAGIC"}
        success, slot_idx = simulated_player.add_to_inventory(item)
        assert success is True
        assert slot_idx == 0
        assert simulated_player.inventory[0]["id"] == "bloodstone_t1"

    def test_t1_f19_item_insertion_fills_consecutive_slots(self, simulated_player: SimulatedCocosPlayer) -> None:
        for i in range(5):
            success, slot_idx = simulated_player.add_to_inventory({"id": f"item_{i}"})
            assert success is True
            assert slot_idx == i
        assert simulated_player.inventory[4]["id"] == "item_4"
        assert simulated_player.inventory[5] is None

    def test_t1_f19_backpack_overflow_rejection_at_capacity(self, simulated_player: SimulatedCocosPlayer) -> None:
        # Fill all 40 slots
        for i in range(BACKPACK_CAPACITY):
            simulated_player.add_to_inventory({"id": f"item_{i}"})
        # Try to add 41st item
        overflow_item = {"id": "overflow_item"}
        success, slot_idx = simulated_player.add_to_inventory(overflow_item)
        assert success is False
        assert slot_idx is None

    def test_t1_f19_rarity_border_classes(self) -> None:
        rarity_border_classes = {
            "COMMON": "border-gray-500",
            "MAGIC": "border-blue-500",
            "RARE": "border-amber-500",
            "MYTHIC": "border-red-600",
        }
        assert rarity_border_classes["RARE"] == "border-amber-500"
        assert rarity_border_classes["MYTHIC"] == "border-red-600"

    def test_t1_f19_item_grid_sizes_dimension_support(self) -> None:
        item_sizes = {
            "bloodstone": (1, 1),
            "helmet": (2, 3),
            "two_handed_scythe": (2, 4),
        }
        assert item_sizes["bloodstone"] == (1, 1)
        assert item_sizes["two_handed_scythe"] == (2, 4)


class TestTier1CharacterCreationAndRandomizer:
    """Feature 20: Character Creation & Randomizer."""

    def test_t1_f20_six_canonical_martial_classes(self) -> None:
        assert len(CANONICAL_CLASSES) == 6
        assert "char_sword_master" in CANONICAL_CLASSES
        assert "char_shadow_assassin" in CANONICAL_CLASSES

    def test_t1_f20_twelve_canonical_vietnamese_names(self) -> None:
        assert len(CANONICAL_MARTIAL_NAMES) == 12
        assert "Độc Cô Bại" in CANONICAL_MARTIAL_NAMES
        assert "Huyết Vô Ảnh" in CANONICAL_MARTIAL_NAMES
        assert "Sát Cốt Ma" in CANONICAL_MARTIAL_NAMES

    def test_t1_f20_random_name_dice_pool_selection(self) -> None:
        import random
        rng = random.Random(42)
        rolled_name = rng.choice(CANONICAL_MARTIAL_NAMES)
        assert rolled_name in CANONICAL_MARTIAL_NAMES

    def test_t1_f20_name_length_validation_bounds(self) -> None:
        def validate_name(name: str) -> bool:
            return 3 <= len(name) <= 24

        assert not validate_name("AB")        # Underflow < 3
        assert validate_name("Độc Cô Bại")     # Valid
        assert validate_name("A" * 24)        # Upper bound
        assert not validate_name("A" * 25)    # Overflow > 24

    def test_t1_f20_duplicate_name_rejection(self) -> None:
        existing_names = {"Độc Cô Bại", "Huyết Vô Ảnh"}
        new_name_dupe = "Độc Cô Bại"
        new_name_ok = "Mạc Vấn Thiên"
        assert new_name_dupe in existing_names  # Rejected
        assert new_name_ok not in existing_names  # Allowed

    def test_t1_f20_default_archetype_fallback(self) -> None:
        requested_class = None
        selected_class = requested_class or CANONICAL_CLASSES[0]
        assert selected_class == "char_sword_master"


class TestTier1ReactiveLocalizationI18n:
    """Feature 21: Reactive 9-Language i18n."""

    def test_t1_f21_nine_canonical_languages_supported(self) -> None:
        assert len(SUPPORTED_LOCALES) == 9
        for loc in ["vi", "en", "zh", "ja", "ko", "th", "de", "ru", "es"]:
            assert loc in SUPPORTED_LOCALES

    def test_t1_f21_in_place_atomic_re_render_zero_reload(self) -> None:
        reloads_count = 0
        current_locale = "vi"

        def set_locale(new_locale: str) -> None:
            nonlocal current_locale, reloads_count
            current_locale = new_locale
            # Zero reload mandate: reloads_count remains 0

        set_locale("en")
        assert current_locale == "en"
        assert reloads_count == 0

    def test_t1_f21_translation_dictionary_key_parity(self) -> None:
        translations = {
            "vi": {"play_game": "Vào Game", "bag_full": "Túi đồ đã đầy!"},
            "en": {"play_game": "Play Game", "bag_full": "Bag is full!"},
            "zh": {"play_game": "进入游戏", "bag_full": "背包已满！"},
        }
        for lang, dict_keys in translations.items():
            assert "play_game" in dict_keys
            assert "bag_full" in dict_keys

    def test_t1_f21_fallback_to_english_on_missing_key(self) -> None:
        locale_data = {
            "en": {"rare_key": "Rare Value"},
            "ja": {},
        }
        key = "rare_key"
        val = locale_data["ja"].get(key, locale_data["en"].get(key))
        assert val == "Rare Value"

    def test_t1_f21_event_subscription_observer_notification(self) -> None:
        subscribers_notified = []

        def on_locale_change(new_lang: str) -> None:
            subscribers_notified.append(new_lang)

        # Notify subscribers
        on_locale_change("de")
        assert subscribers_notified == ["de"]

    def test_t1_f21_microcopy_length_constraints(self) -> None:
        # Microcopy rule: buttons <= 2 words, <= 12 characters
        button_labels = ["Vào Game", "Khởi Sinh", "Play", "Start"]
        for label in button_labels:
            words = label.split()
            assert len(words) <= 2
            assert len(label) <= 12
