"""Tier 4: Real-World Application Scenarios — 8-Step Human Player Journey.
Grounded against authentic server components (AccountRepository, CharacterService,
ZoneEngine, MovementAuthorityEngine, WsGatewayBridge, NpcEngine, ProceduralMapEngine).
"""
from __future__ import annotations

import math
import time
import uuid
import pytest

from server.auth.auth_service import AccountRepository
from server.auth.character_service import Character, CharacterClass, CharacterService
from server.auth.models import AccountStatus
from server.combat.combat_types import CombatantType
from server.combat.monster_scaling import MonsterScalingEngine
from server.common.math_utils import euclidean_distance_2d, is_within_radius_2d
from server.gateway.ws_gateway_bridge import (
    OPCODE_CAST_MARTIAL_SKILL,
    OPCODE_PACKET_ENVELOPE,
    OPCODE_PHANTOM_EVASION,
    WsGatewayBridge,
    decode_binary_frame,
    encode_binary_frame,
)
from server.proto import combat_pb2, network_pb2
from server.world.map_data_types import TileType
from server.world.movement_authority import MovementAuthorityEngine, PlayerCharacter
from server.world.npc_catalog import populate_canonical_npcs
from server.world.npc_engine import NpcEngine
from server.world.npc_types import NpcServiceType
from server.world.procedural_map_engine import ProceduralMapEngine
from server.world.zone_engine import ZoneEngine
from server.world.zone_types import ZoneType

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


class TestTier4Step1OnboardingAndLogin:
    """Step 1 (S1): Onboarding & Guest Login (< 50ms)."""

    def test_t4_s1_guest_login_latency_under_50ms(self) -> None:
        repo = AccountRepository()
        t0 = time.perf_counter()
        acc = repo.create_account(
            email=f"guest_{uuid.uuid4().hex[:8]}@freeexile.lan",
            password_hash="argon2id_guest_hash_simulated",
            username="ExileGuest",
            status=AccountStatus.ACTIVE,
            roles=["EXILE_PLAYER"],
        )
        elapsed_ms = (time.perf_counter() - t0) * 1000.0
        assert elapsed_ms < 50.0
        assert acc.status == AccountStatus.ACTIVE
        assert acc.account_id.startswith("acc_")

    def test_t4_s1_welcome_packet_handshake_state(self) -> None:
        bridge = WsGatewayBridge(host="127.0.0.1", port=8080)
        assert bridge.is_running is False
        env = network_pb2.PacketEnvelope(sequence_number=1, timestamp_ms=int(time.time() * 1000), nonce=b"nonce_s1")
        frame = encode_binary_frame(OPCODE_PACKET_ENVELOPE, env.SerializeToString())
        opcode, payload = decode_binary_frame(frame)
        assert opcode == OPCODE_PACKET_ENVELOPE
        assert network_pb2.PacketEnvelope.FromString(payload).sequence_number == 1


class TestTier4Step2CharacterCreation:
    """Step 2 (S2): Character Creation with [🎲 Tên Ngẫu Nhiên]."""

    def test_t4_s2_random_name_dice_button_selection(self) -> None:
        assert len(CANONICAL_MARTIAL_NAMES) == 12
        for name in CANONICAL_MARTIAL_NAMES:
            assert CharacterService.NAME_REGEX.match(name) is not None
            assert 3 <= len(name) <= 24

    def test_t4_s2_character_archetype_initial_stats(self) -> None:
        hero = Character(
            character_id=f"char_{uuid.uuid4().hex[:12]}",
            account_id="acc_guest_1",
            name=CANONICAL_MARTIAL_NAMES[0],
            class_type=CharacterClass.SWORD_MASTER,
            level=1, current_hp=100, max_hp=100, current_mana=50, max_mana=50,
            current_zone_id="zone_boundless_sanctuary", pos_x=0.0, pos_y=10.0,
        )
        assert hero.level == 1 and hero.current_hp == 100 and hero.max_hp == 100
        assert hero.current_zone_id == "zone_boundless_sanctuary"
        assert (hero.pos_x, hero.pos_y) == (0.0, 10.0)


class TestTier4Step3SanctuaryArrivalAndMovement:
    """Step 3 (S3): Sanctuary Arrival & Zero-Residual Stop."""

    def test_t4_s3_spawn_at_sanctuary_safe_coordinates(self, simulated_player: SimulatedCocosPlayer) -> None:
        ze = ZoneEngine()
        zone = ze.get_zone("zone_boundless_sanctuary")
        assert zone.zone_type == ZoneType.SANCTUARY
        assert ze.can_spawn_hostile_monsters("zone_boundless_sanctuary") is False
        simulated_player.wx, simulated_player.wy = 0.0, 10.0
        simulated_player.zone_id = zone.zone_id
        assert simulated_player.zone_id == "zone_boundless_sanctuary"
        assert (simulated_player.wx, simulated_player.wy) == (0.0, 10.0)

    def test_t4_s3_firm_kinematics_instant_stop_on_release(self, simulated_player: SimulatedCocosPlayer) -> None:
        mae = MovementAuthorityEngine()
        player = PlayerCharacter(entity_id=1001, x=0.0, y=10.0, move_speed=6.0)
        mae.register_player(player)
        ok, nx, ny = mae.process_move_input(1001, 1.0, 0.0, 0.016)
        assert ok and nx > 0.0

        bridge = WsGatewayBridge()
        halted, vx, vy = bridge.verify_zero_residual_momentum(1001, 1.0, 0.0)
        assert not halted and vx > 0.0
        # Release / deadzone (hypot(0.03, 0.04) == 0.05 <= 0.05) -> frame 0 instant stop
        halted_stop, vx_stop, vy_stop = bridge.verify_zero_residual_momentum(1001, 0.03, 0.04)
        assert halted_stop and vx_stop == 0.0 and vy_stop == 0.0
        simulated_player.apply_input(0.0, 0.0, dt=0.016)
        assert simulated_player.vx == 0.0 and simulated_player.vy == 0.0
        assert simulated_player.anim_state == "idle"


class TestTier4Step4NPCInteractions:
    """Step 4 (S4): 4 Encampment NPC Interactions & Service Modals."""

    def test_t4_s4_pathfinding_and_dialogue_with_all_4_npcs(self) -> None:
        npc_engine = NpcEngine()
        populate_canonical_npcs(npc_engine)
        sanctuary_npcs = npc_engine.get_npcs_in_zone("zone_boundless_sanctuary")
        expected_ids = {"npc_van_gioi_thuong_nhan", "npc_thiet_tuong_au_da_tu", "npc_chan_vo_truyen_thua", "npc_bach_hieu_sinh"}
        assert expected_ids.issubset({npc.npc_id for npc in sanctuary_npcs})

        for npc in sanctuary_npcs:
            if npc.npc_id in expected_ids:
                assert npc_engine.validate_interaction_distance(npc, npc.x + 1.0, npc.y)
                ok, _, node, _ = npc_engine.interact_with_npc(
                    player_id="player_j1", npc_id=npc.npc_id,
                    zone_id="zone_boundless_sanctuary", player_x=npc.x + 1.0, player_y=npc.y,
                )
                assert ok and node is not None
                npc_engine.close_dialogue_session("player_j1")

    def test_t4_s4_service_tab_transition_clean_execution(self) -> None:
        npc_engine = NpcEngine()
        populate_canonical_npcs(npc_engine)
        smith = npc_engine.get_npc("npc_thiet_tuong_au_da_tu")
        assert smith is not None and NpcServiceType.CRAFTING_FORGE in smith.supported_services
        ok, _, node, _ = npc_engine.interact_with_npc(
            player_id="player_j1", npc_id="npc_thiet_tuong_au_da_tu",
            zone_id="zone_boundless_sanctuary", player_x=smith.x, player_y=smith.y,
        )
        assert ok and node is not None
        forge_choices = [c for c in node.choices if c.trigger_service == NpcServiceType.CRAFTING_FORGE]
        assert len(forge_choices) == 1 and forge_choices[0].choice_id == "choice_open_forge"
        npc_engine.close_dialogue_session("player_j1")


class TestTier4Step5TargetDummyCombat:
    """Step 5 (S5): Combat Feel Testing at Target Dummy."""

    def test_t4_s5_target_dummy_qwe_combo_rotation(self) -> None:
        ze = ZoneEngine()
        dummy_ok, _ = ze.validate_monster_spawn("zone_boundless_sanctuary", is_dummy=True, tx=0, ty=0)
        assert dummy_ok is True

        bridge = WsGatewayBridge()
        skills = [
            combat_pb2.CastMartialSkillRequest(caster_entity_id=1001, skill_id=101, target_x=5.0, target_y=5.0),
            combat_pb2.CastMartialSkillRequest(caster_entity_id=1001, skill_id=102, target_x=5.0, target_y=5.0),
            combat_pb2.CastMartialSkillRequest(caster_entity_id=1001, skill_id=103, target_x=5.0, target_y=5.0),
        ]
        dummy_max_hp = 80000
        current_hp, total_dmg = dummy_max_hp, 0
        for skill in skills:
            frame = encode_binary_frame(OPCODE_CAST_MARTIAL_SKILL, skill.SerializeToString())
            op, _ = decode_binary_frame(frame)
            assert op == OPCODE_CAST_MARTIAL_SKILL
            dmg_evt = combat_pb2.CombatDamageEvent(
                source_entity_id=skill.caster_entity_id, target_entity_id=9999,
                raw_damage=180 + skill.skill_id, mitigated_damage=150 + skill.skill_id,
                element=combat_pb2.FiveElementsType.ELEMENT_PHYSICAL_KIM,
            )
            total_dmg += dmg_evt.mitigated_damage
            current_hp = max(1, current_hp - dmg_evt.mitigated_damage)

        assert total_dmg > 0 and current_hp == dummy_max_hp - total_dmg and current_hp > 1

    def test_t4_s5_iframe_dodge_cancels_attack_animation(self, simulated_player: SimulatedCocosPlayer) -> None:
        eva_req = combat_pb2.PhantomEvasionRequest(entity_id=1001, evasion_dir_x=1.0, evasion_dir_y=0.0)
        frame = encode_binary_frame(OPCODE_PHANTOM_EVASION, eva_req.SerializeToString())
        op, _ = decode_binary_frame(frame)
        assert op == OPCODE_PHANTOM_EVASION
        simulated_player.anim_state = "attack_slash"
        simulated_player.trigger_dodge()
        assert simulated_player.anim_state == "dodge" and simulated_player.is_iframe is True


class TestTier4Step6DungeonEntry:
    """Step 6 (S6): Dungeon Entry with Waypoint Safe Radius & Fog of War."""

    def test_t4_s6_dungeon_transition_and_tile_grid_load(self, simulated_player: SimulatedCocosPlayer) -> None:
        ze = ZoneEngine()
        dungeon_zone = ze.get_zone("zone_tang_kiem_nhai")
        assert dungeon_zone.zone_type in (ZoneType.OPEN_WORLD, ZoneType.DUNGEON_INSTANCE)
        assert ze.can_spawn_hostile_monsters("zone_tang_kiem_nhai") is True
        simulated_player.zone_id = dungeon_zone.zone_id
        simulated_player.wx, simulated_player.wy = 0.0, 0.0
        assert simulated_player.zone_id == "zone_tang_kiem_nhai"

    def test_t4_s6_waypoint_safe_radius_hostile_protection(self, simulated_player: SimulatedCocosPlayer) -> None:
        ze = ZoneEngine()
        zone = ze.get_zone("zone_boundless_sanctuary")
        assert len(zone.waypoints) > 0
        wp = next(iter(zone.waypoints.values()))
        simulated_player.wx, simulated_player.wy = wp.x + 2.0, wp.y + 2.0
        dist = euclidean_distance_2d(simulated_player.wx, simulated_player.wy, wp.x, wp.y)
        assert is_within_radius_2d(simulated_player.wx, simulated_player.wy, wp.x, wp.y, wp.safe_radius)
        assert dist <= wp.safe_radius


class TestTier4Step7BossGateUnlocking:
    """Step 7 (S7): 3-Pack Kill Boss Gate Unlocking."""

    def test_t4_s7_pack_kills_unlock_runic_boss_gate(self) -> None:
        ProceduralMapEngine.clear_zone_maps()
        map_data = ProceduralMapEngine.generate_wilderness_map("zone_tang_kiem_nhai", seed=101)
        ProceduralMapEngine.set_zone_map("zone_tang_kiem_nhai", map_data)
        assert map_data.boss_gate is not None
        gx, gy = map_data.boss_gate
        assert map_data.tiles[gy][gx].tile_type == TileType.BOSS_GATE
        assert not map_data.tiles[gy][gx].walkable and not map_data.boss_gate_breached
        assert ProceduralMapEngine().breach_boss_gate(map_data) is True
        assert map_data.boss_gate_breached and map_data.tiles[gy][gx].walkable is True

    def test_t4_s7_proximity_banner_lore_updates_to_unsealed(self) -> None:
        map_data = ProceduralMapEngine._active_zone_maps.get("zone_tang_kiem_nhai")
        assert map_data is not None and map_data.boss_gate_breached is True
        banner = "[PHONG ẤN ĐÃ GIẢI] Lối vào Huyết Đàn đã mở." if map_data.boss_gate_breached else "LOCKED"
        assert "PHONG ẤN ĐÃ GIẢI" in banner


class TestTier4Step8BossExecutionAndLoot:
    """Step 8 (S8): Strand Executioner Boss Kill & 40-Slot Backpack Loot."""

    def test_t4_s8_boss_execution_and_fountain_dispersion(self) -> None:
        scaling = MonsterScalingEngine()
        boss_def = scaling.create_monster_defense(level=18, rank=CombatantType.BOSS)
        assert boss_def.max_life > 15000 and boss_def.combatant_type == CombatantType.BOSS
        boss_def.current_life = max(0.0, boss_def.current_life - boss_def.max_life)
        assert boss_def.current_life == 0.0
        drops = ["blood_soul_orb", "nether_bone_nail", "blood_reaper_scythe"]
        assert len(drops) == 3 and "blood_reaper_scythe" in drops

    def test_t4_s8_smart_pickup_into_backpack_slot_0(self, simulated_player: SimulatedCocosPlayer) -> None:
        success, slot_idx = simulated_player.add_to_inventory({"id": "blood_reaper_scythe", "rarity": "MYTHIC"})
        assert success and slot_idx == 0
        assert simulated_player.inventory[0]["rarity"] == "MYTHIC"


class TestTier4ContinuousPlayerJourneySymphony:
    """Tier 4: Full Continuous 8-Step Symphony Execution."""

    def test_t4_continuous_8_step_journey_state_chaining(self, simulated_player: SimulatedCocosPlayer) -> None:
        # Step 1: Login
        repo = AccountRepository()
        acc = repo.create_account("symphony@freeexile.lan", "pw_hash", "ExileSym", AccountStatus.ACTIVE)
        assert acc.status == AccountStatus.ACTIVE

        # Step 2: Character Creation
        char_name = CANONICAL_MARTIAL_NAMES[1]
        assert CharacterService.NAME_REGEX.match(char_name) is not None
        char = Character(f"char_{acc.account_id}", acc.account_id, char_name, CharacterClass.SWORD_MASTER)
        assert char.level == 1

        # Step 3: Sanctuary spawn & zero-residual stop
        ze = ZoneEngine()
        sanctuary = ze.get_zone("zone_boundless_sanctuary")
        assert ze.can_spawn_hostile_monsters(sanctuary.zone_id) is False
        simulated_player.wx, simulated_player.wy = 0.0, 10.0
        bridge = WsGatewayBridge()
        halted, vx, vy = bridge.verify_zero_residual_momentum(1001, 0.0, 0.0)
        assert halted and vx == 0.0 and vy == 0.0
        simulated_player.apply_input(0.0, 0.0, 0.016)
        assert simulated_player.vx == 0.0

        # Step 4: Interact with Tracker NPC
        npc_engine = NpcEngine()
        populate_canonical_npcs(npc_engine)
        tracker = npc_engine.get_npc("npc_bach_hieu_sinh")
        assert tracker is not None
        assert npc_engine.validate_interaction_distance(tracker, tracker.x, tracker.y)

        # Step 5: Dummy combo & dodge
        simulated_player.trigger_dodge()
        assert simulated_player.is_iframe
        simulated_player.update_timers(0.26)
        assert not simulated_player.is_iframe

        # Step 6: Dungeon portal entry
        dungeon = ze.get_zone("zone_tang_kiem_nhai")
        assert ze.can_spawn_hostile_monsters(dungeon.zone_id)
        simulated_player.zone_id, simulated_player.wx, simulated_player.wy = dungeon.zone_id, 0.0, 0.0
        assert simulated_player.zone_id == "zone_tang_kiem_nhai"

        # Step 7: 3 packs eliminated -> Boss gate breaches
        map_data = ProceduralMapEngine.generate_wilderness_map(dungeon.zone_id, seed=888)
        assert ProceduralMapEngine().breach_boss_gate(map_data) and map_data.boss_gate_breached

        # Step 8: Boss execution & item loot
        boss_def = MonsterScalingEngine().create_monster_defense(level=18, rank=CombatantType.BOSS)
        assert boss_def.max_life > 15000
        boss_def.current_life = 0.0
        assert boss_def.current_life == 0.0
        success, slot_idx = simulated_player.add_to_inventory({"id": "boss_trophy", "rarity": "MYTHIC"})
        assert success and slot_idx == 0

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