"""
Unit Test Suite for FreeExile Map, Dungeon Instance, and Secret Chamber Engine.
Validates:
1. Zone Registry & Metadata (Sanctuary, Open World, Dungeons, Secret Chambers).
2. Waypoint Network, Discovery, and Fast Travel Authority.
3. Portal Traversal with Level and Quest Prerequisite Gates.
4. Dungeon Instance Lifecycle (Layer Progression, Time Limits, Spatial Isolation).
5. Secret Chamber Spawning, Event-Driven Discovery, and Auto-Collapse Mechanics.
6. Deep Closed-Loop Integration with QuestEngine & Combat Subsystems.
"""

import time
import pytest
from typing import Set

from server.world.zone_engine import (
    ZoneEngine,
    ZoneType,
    ZoneEnvironment,
    ZoneDefinition,
    Waypoint,
    ZonePortal,
    PlayerLocation,
    DungeonTemplate,
    InstanceSession,
    SecretChamberTemplate,
    SecretChamberSession,
)
from server.world.quest_engine import (
    QuestEngine,
    QuestStatus,
    HiddenTriggerType,
    create_default_quest_engine,
)


@pytest.fixture
def zone_engine() -> ZoneEngine:
    return ZoneEngine()


@pytest.fixture
def quest_engine() -> QuestEngine:
    return create_default_quest_engine()


class TestMapAndZoneRegistry:
    def test_all_canonical_zones_registered(self, zone_engine: ZoneEngine):
        """Verifies that all 10 canonical open world / sanctuary zones and templates exist."""
        # 1. Sanctuary
        assert "zone_boundless_sanctuary" in zone_engine.zones
        sanctuary = zone_engine.get_zone("zone_boundless_sanctuary")
        assert sanctuary.zone_type == ZoneType.SANCTUARY
        assert sanctuary.environment == ZoneEnvironment.NORMAL
        assert sanctuary.min_level == 1

        # 2. Open World Exploration Zones
        expected_zones = [
            ("zone_tang_kiem_nhai", 1, ZoneEnvironment.NORMAL),
            ("zone_ancient_sword_barrow", 8, ZoneEnvironment.MIASMA_TOXIC),
            ("zone_boundless_sandstorm", 20, ZoneEnvironment.SANDSTORM),
            ("zone_blood_scale_ruins", 30, ZoneEnvironment.VOID_CORRUPTION),
            ("zone_five_elements_altar", 40, ZoneEnvironment.NORMAL),
            ("zone_abyssal_ice_pond", 50, ZoneEnvironment.FREEZING_CHILL),
            ("zone_infinite_blood_rift", 60, ZoneEnvironment.FREEZING_CHILL),
            ("zone_purgatory_lava_cavern", 70, ZoneEnvironment.SCORCHING_HEAT),
            ("zone_boundless_celestial_palace", 85, ZoneEnvironment.VOID_CORRUPTION),
        ]

        for zone_id, min_lvl, env in expected_zones:
            assert zone_id in zone_engine.zones
            z = zone_engine.get_zone(zone_id)
            assert z.zone_type == ZoneType.OPEN_WORLD
            assert z.min_level == min_lvl
            assert z.environment == env

    def test_dungeon_templates_registered(self, zone_engine: ZoneEngine):
        """Verifies canonical dungeon instance templates (Phong Ma Cổ Trận, Bí Cảnh Hư Không, Thí Luyện Điện)."""
        assert "inst_phong_ma_co_tran" in zone_engine.dungeon_templates
        phong_ma = zone_engine.get_dungeon_template("inst_phong_ma_co_tran")
        assert phong_ma.max_layers == 50
        assert phong_ma.min_level == 30
        assert phong_ma.required_quest_id == "quest_norm_05_khai_mo_van_gioi_cac"

        assert "inst_bi_canh_hu_khong" in zone_engine.dungeon_templates
        bi_canh = zone_engine.get_dungeon_template("inst_bi_canh_hu_khong")
        assert bi_canh.max_layers == 100
        assert bi_canh.min_level == 60

        assert "inst_van_gioi_thi_luyen" in zone_engine.dungeon_templates

    def test_secret_chamber_templates_registered(self, zone_engine: ZoneEngine):
        """Verifies canonical cryptic secret chamber templates matching the 4 hidden quests."""
        expected_chambers = [
            ("secret_huyen_anh_to_su", "zone_blood_scale_ruins", "loc_secret_phantom_shrine"),
            ("secret_tan_bi_than_tuong", "zone_boundless_sandstorm", "action_decipher_ancient_stele"),
            ("secret_ngu_hanh_tran_nhan", "zone_five_elements_altar", "loc_five_elements_altar"),
            ("secret_tang_kiem_kiem_chung", "zone_tang_kiem_nhai", "action_cleanse_rusted_blade"),
        ]

        for chamber_id, parent_zone, target_key in expected_chambers:
            assert chamber_id in zone_engine.secret_chamber_templates
            tmpl = zone_engine.get_secret_chamber_template(chamber_id)
            assert tmpl.parent_zone_id == parent_zone
            assert tmpl.target_objective_id == target_key
            assert tmpl.lifespan_sec > 0


class TestPlayerNavigationAndWaypoints:
    def test_player_spawn_and_bounds_check(self, zone_engine: ZoneEngine):
        """Tests spawning in sanctuary and boundary-checked position updates."""
        player_id = "player_test_01"
        loc = zone_engine.spawn_player(player_id, "zone_boundless_sanctuary")
        assert loc.zone_id == "zone_boundless_sanctuary"
        assert loc.x == 0.0
        assert loc.y == 0.0

        # Valid movement within bounds
        success, err = zone_engine.update_player_position(player_id, 100.0, 150.0)
        assert success
        assert err == ""
        current_loc = zone_engine.get_player_location(player_id)
        assert current_loc.x == 100.0
        assert current_loc.y == 150.0

        # Out-of-bounds movement rejected
        zone = zone_engine.get_zone("zone_boundless_sanctuary")
        huge_x = zone.bounds_width + 1000.0
        success, err = zone_engine.update_player_position(player_id, huge_x, 0.0)
        assert not success
        assert "Vượt quá ranh giới" in err

    def test_waypoint_discovery_and_teleportation(self, zone_engine: ZoneEngine):
        """Tests discovering waypoints upon proximity and authoritative fast travel."""
        player_id = "player_test_02"
        zone_engine.spawn_player(player_id, "zone_tang_kiem_nhai")

        # Sanctuary waypoint is unlocked by default
        unlocked = zone_engine.get_unlocked_waypoints(player_id)
        assert "wp_sanctuary_central" in unlocked

        # Tang Kiem Nhai summit waypoint is initially locked
        assert "wp_tang_kiem_cliff" not in unlocked

        # Move close to wp_tang_kiem_cliff (x=150, y=150)
        discovered = zone_engine.check_waypoint_proximity(player_id, 151.0, 150.5)
        assert "wp_tang_kiem_cliff" in discovered
        assert "wp_tang_kiem_cliff" in zone_engine.get_unlocked_waypoints(player_id)

        # Teleport to discovered waypoint
        success, msg = zone_engine.teleport_via_waypoint(player_id, "wp_sanctuary_central")
        assert success
        current_loc = zone_engine.get_player_location(player_id)
        assert current_loc.zone_id == "zone_boundless_sanctuary"

        # Attempt teleport to undiscovered waypoint fails
        success, msg = zone_engine.teleport_via_waypoint(player_id, "wp_celestial_peak")
        assert not success
        assert "Chưa kích hoạt" in msg


class TestPortalTraversalAndLevelGating:
    def test_portal_traversal_success(self, zone_engine: ZoneEngine):
        """Tests crossing a zone portal when level requirements are satisfied."""
        player_id = "player_hero"
        zone_engine.spawn_player(player_id, "zone_boundless_sanctuary")

        # Move to portal leading to Tang Kiem Nhai
        portal_id = "portal_sanctuary_to_tang_kiem"
        success, new_zone, msg = zone_engine.traverse_portal(player_id, portal_id, player_level=5)
        assert success
        assert new_zone == "zone_tang_kiem_nhai"
        assert zone_engine.get_player_location(player_id).zone_id == "zone_tang_kiem_nhai"

    def test_portal_traversal_level_gate_rejection(self, zone_engine: ZoneEngine):
        """Tests that a low-level player cannot traverse into high-tier danger zones."""
        player_id = "player_newbie"
        zone_engine.spawn_player(player_id, "zone_purgatory_lava_cavern")

        # Portal to Celestial Palace requires level 85
        portal_id = "portal_lava_to_celestial"
        success, new_zone, msg = zone_engine.traverse_portal(player_id, portal_id, player_level=20)
        assert not success
        assert new_zone == ""
        assert "Chưa đạt cấp độ" in msg


class TestDungeonInstanceLifecycle:
    def test_create_and_progress_dungeon_instance(self, zone_engine: ZoneEngine):
        """Tests full lifecycle of Phong Ma Cổ Trận (Creation, layer advances, party members, completion)."""
        leader_id = "player_leader"
        members = ["player_member_1", "player_member_2"]

        # Create instance
        success, session, msg = zone_engine.create_dungeon_instance(
            template_id="inst_phong_ma_co_tran",
            leader_id=leader_id,
            members=members,
            leader_level=35,
            completed_quests={"quest_norm_05_khai_mo_van_gioi_cac"}
        )
        assert success
        assert session is not None
        assert session.current_layer == 1
        assert session.max_layers == 50
        assert session.lives_remaining == 3
        assert len(session.all_players) == 3

        instance_id = session.instance_id

        # Verify all players are placed in instance zone with isolation
        for p in session.all_players:
            loc = zone_engine.get_player_location(p)
            assert loc.zone_id == session.instance_zone_id
            assert loc.instance_id == instance_id

        # Advance layers
        cleared_layer, is_done = zone_engine.advance_instance_layer(instance_id)
        assert cleared_layer == 2
        assert not is_done

        # Simulate reaching final boss layer (layer 50) and beating it
        session.current_layer = 50
        cleared_layer, is_done = zone_engine.advance_instance_layer(instance_id)
        assert is_done
        assert session.is_completed

        # Close instance and evacuate players to sanctuary
        zone_engine.close_instance(instance_id)
        assert instance_id not in zone_engine.active_instances
        for p in session.all_players:
            loc = zone_engine.get_player_location(p)
            assert loc.zone_id == "zone_boundless_sanctuary"
            assert loc.instance_id is None

    def test_dungeon_entry_rejection_without_prerequisite(self, zone_engine: ZoneEngine):
        """Tests instance creation failure when level or quest prerequisite is missing."""
        leader_id = "player_underleveled"
        success, session, msg = zone_engine.create_dungeon_instance(
            template_id="inst_phong_ma_co_tran",
            leader_id=leader_id,
            members=[],
            leader_level=15, # Required: 30
            completed_quests=set()
        )
        assert not success
        assert session is None
        assert "Chưa đạt cấp độ" in msg


class TestSecretChamberMechanics:
    def test_secret_chamber_spawn_enter_and_auto_collapse(self, zone_engine: ZoneEngine):
        """Tests spawning a secret chamber portal, entering it, and timer auto-collapse."""
        player_id = "player_fated"
        zone_engine.spawn_player(player_id, "zone_blood_scale_ruins")

        # Spawn secret chamber: Mật Điện Huyễn Ảnh Tông
        success, chamber, msg = zone_engine.spawn_secret_chamber(
            template_id="secret_huyen_anh_to_su",
            player_id=player_id,
            parent_zone_id="zone_blood_scale_ruins",
            portal_x=120.0,
            portal_y=85.0,
            lifespan_sec=600 # 10 mins
        )
        assert success
        assert chamber is not None
        assert chamber.portal_zone_id == "zone_blood_scale_ruins"
        assert chamber.is_active

        # Player steps into the secret chamber portal
        enter_ok, enter_msg = zone_engine.enter_secret_chamber(player_id, chamber.chamber_id)
        assert enter_ok
        loc = zone_engine.get_player_location(player_id)
        assert loc.zone_id == chamber.chamber_zone_id
        assert loc.instance_id == chamber.chamber_id

        # Mark secret chamber completed (e.g. ancient stele deciphered)
        cleared_ok, clear_msg = zone_engine.complete_secret_chamber(player_id, chamber.chamber_id)
        assert cleared_ok
        assert chamber.is_cleared

        # Force collapse (simulate timer expiration)
        zone_engine.collapse_secret_chamber(chamber.chamber_id)
        assert chamber.chamber_id not in zone_engine.active_secret_chambers

        # Player returned to parent zone portal coordinates safely
        return_loc = zone_engine.get_player_location(player_id)
        assert return_loc.zone_id == "zone_blood_scale_ruins"
        assert return_loc.instance_id is None
        assert return_loc.x == 120.0
        assert return_loc.y == 85.0


class TestQuestAndCombatClosedLoopIntegration:
    def test_zone_discovery_updates_quest_engine(
        self, zone_engine: ZoneEngine, quest_engine: QuestEngine
    ):
        """Tests that moving to a secret location or zone updates quest discovery objectives."""
        player_id = "player_integrator"
        quest_engine.initialize_player(player_id, player_level=15)

        # Trigger hidden quest 01: Huyễn Ảnh Tông
        triggered = quest_engine.trigger_hidden_quest(
            player_id=player_id,
            trigger_type=HiddenTriggerType.EVASION_NEAR_DEATH,
            context={"zone": "zone_blood_scale_ruins", "hp_percent": 0.08},
        )
        assert triggered == "quest_hidden_01_huyen_anh_to_su"

        # Spawn secret chamber via zone engine
        ok, chamber, _ = zone_engine.spawn_secret_chamber(
            template_id="secret_huyen_anh_to_su",
            player_id=player_id,
            parent_zone_id="zone_blood_scale_ruins",
            portal_x=100.0,
            portal_y=100.0
        )
        assert ok
        assert chamber is not None

        # Enter chamber
        zone_engine.enter_secret_chamber(player_id, chamber.chamber_id)

        # Interacting with secret shrine triggers discovery event on quest engine
        completed_quests = quest_engine.record_discovery(player_id, "loc_secret_phantom_shrine")
        assert "quest_hidden_01_huyen_anh_to_su" in completed_quests

        # Verify quest is now COMPLETED
        state = quest_engine.get_player_quest(player_id, "quest_hidden_01_huyen_anh_to_su")
        assert state is not None
        assert state.status == QuestStatus.COMPLETED

        # Claim reward
        claim_ok, reward, _ = quest_engine.claim_quest_reward(player_id, "quest_hidden_01_huyen_anh_to_su")
        assert claim_ok
        assert reward is not None
        assert reward.title == "Huyễn Ảnh Vô Song"
        assert reward.meridian_points == 5
