"""Tests for Atlas Graph Progression, Pathfinder & Full Node Completion Engine.
=============================================================================
Kiểm thử toàn diện:
- AtlasGraph: Khởi tạo, thêm cạnh, mở rộng biên (Frontier Progression), tính completion stats.
- EncounterMatcher: Nhận diện cơ chế (Breach, Delirium, Ritual, Citadels), tính điểm ưu tiên.
- AtlasPathfinder: Thuật toán Dijkstra, tìm đường ngắn nhất, tìm node biên gần nhất.
- AtlasProgressionPlanner: Các chiến lược Full Completion, Bonus Rush, Citadel Rush, Encounter Farm.
"""

import pytest
from src.assistant_tool.atlas.graph import (
    AtlasGraph,
    AtlasGraphNode,
    NodeProgressionState,
)
from src.assistant_tool.atlas.encounter_matcher import (
    EncounterMatcher,
    EncounterType,
)
from src.assistant_tool.atlas.pathfinder import (
    AtlasPathfinder,
)
from src.assistant_tool.atlas.progression_planner import (
    AtlasProgressionPlanner,
    ProgressionStrategy,
)
from src.assistant_tool.atlas_knowledge_base import AtlasNodeType


def build_test_atlas_graph() -> AtlasGraph:
    """
    Xây dựng cây đồ thị Atlas mẫu 7 nodes:
    T1: Beach (Gateway)
    T2: Deserted -> Dunes
    T3: Fortress -> Peninsula
    T4: Enigma Chamber (Quest +1 Point)
    T16: Iron Citadel (Citadel Guardian)
    """
    g = AtlasGraph()

    n_beach = AtlasGraphNode(
        node_id="node_beach",
        name="Beach",
        tier=1,
        progression_state=NodeProgressionState.BONUS_COMPLETED,
    )
    n_deserted = AtlasGraphNode(
        node_id="node_deserted",
        name="Deserted",
        tier=2,
        progression_state=NodeProgressionState.REACHABLE_FRONTIER,
        encounters=["Breach"],
    )
    n_dunes = AtlasGraphNode(
        node_id="node_dunes",
        name="Dunes",
        tier=2,
        progression_state=NodeProgressionState.LOCKED,
        encounters=["Ritual"],
    )
    n_fortress = AtlasGraphNode(
        node_id="node_fortress",
        name="Fortress",
        tier=3,
        progression_state=NodeProgressionState.LOCKED,
        encounters=["Delirium"],
        tower_coverage_count=1,
    )
    n_peninsula = AtlasGraphNode(
        node_id="node_peninsula",
        name="Peninsula",
        tier=3,
        progression_state=NodeProgressionState.LOCKED,
    )
    n_enigma = AtlasGraphNode(
        node_id="node_enigma",
        name="Enigma Chamber",
        tier=4,
        progression_state=NodeProgressionState.LOCKED,
        node_type=AtlasNodeType.QUEST_ENIGMA_CHAMBER,
        atlas_points_reward=1,
    )
    n_citadel = AtlasGraphNode(
        node_id="node_citadel",
        name="Iron Citadel",
        tier=16,
        progression_state=NodeProgressionState.LOCKED,
        node_type=AtlasNodeType.CITADEL_BOSS,
    )

    for n in [n_beach, n_deserted, n_dunes, n_fortress, n_peninsula, n_enigma, n_citadel]:
        g.add_node(n)

    # Cạnh nối đồ thị
    g.add_edge("node_beach", "node_deserted")
    g.add_edge("node_beach", "node_dunes")
    g.add_edge("node_deserted", "node_fortress")
    g.add_edge("node_dunes", "node_peninsula")
    g.add_edge("node_fortress", "node_enigma")
    g.add_edge("node_fortress", "node_citadel")

    return g


class TestAtlasGraphProgression:
    def test_graph_initialization_and_edges(self):
        g = build_test_atlas_graph()
        assert g.total_nodes == 7
        assert len(g.get_neighbors("node_beach")) == 2
        assert len(g.get_neighbors("node_fortress")) == 3

    def test_frontier_expansion_on_completion(self):
        g = build_test_atlas_graph()
        # Ban đầu: beach BONUS_COMPLETED, deserted REACHABLE_FRONTIER, fortress LOCKED
        assert g.get_node("node_fortress").progression_state == NodeProgressionState.LOCKED

        # Hoàn thành deserted -> fortress tự động chuyển sang REACHABLE_FRONTIER
        g.mark_node_completed("node_deserted", bonus=True)

        assert g.get_node("node_deserted").is_bonus_completed is True
        assert g.get_node("node_fortress").progression_state == NodeProgressionState.REACHABLE_FRONTIER

    def test_completion_stats_calculation(self):
        g = build_test_atlas_graph()
        stats = g.get_completion_stats()
        assert stats["total_nodes"] == 7
        assert stats["completed_count"] == 1
        assert stats["bonus_completed_count"] == 1
        assert stats["frontier_count"] == 1  # deserted
        assert stats["completion_rate"] == 14.3

        # Đánh dấu thêm node
        g.mark_node_completed("node_deserted", bonus=True)
        stats2 = g.get_completion_stats()
        assert stats2["completed_count"] == 2
        assert stats2["bonus_completed_count"] == 2
        assert stats2["completion_rate"] == 28.6


class TestEncounterMatcher:
    def test_identify_encounters(self):
        assert EncounterMatcher.identify_encounter("Breach Portal").encounter_type == EncounterType.BREACH
        assert EncounterMatcher.identify_encounter("Delirium Mirror").encounter_type == EncounterType.DELIRIUM
        assert EncounterMatcher.identify_encounter("Ritual Altar").encounter_type == EncounterType.RITUAL
        assert EncounterMatcher.identify_encounter("Expedition Logbook").encounter_type == EncounterType.EXPEDITION
        assert EncounterMatcher.identify_encounter("Count Geonor Iron Citadel").encounter_type == EncounterType.CITADEL_GUARDIAN
        assert EncounterMatcher.identify_encounter("Enigma Chamber").encounter_type == EncounterType.ATLAS_QUEST

    def test_calculate_score(self):
        score_plain = EncounterMatcher.calculate_score([])
        score_breach = EncounterMatcher.calculate_score(["Breach"], preferred_encounters=["Breach"])
        score_delirium_tower = EncounterMatcher.calculate_score(["Delirium"], tower_coverage=2)

        assert score_plain == 1.0
        assert score_breach > score_plain
        assert score_delirium_tower > score_plain


class TestAtlasPathfinder:
    def test_shortest_path_dijkstra(self):
        g = build_test_atlas_graph()
        pathfinder = AtlasPathfinder()

        res = pathfinder.find_shortest_path(g, "node_beach", "node_enigma")
        assert res is not None
        assert res.path_node_ids == ["node_beach", "node_deserted", "node_fortress", "node_enigma"]
        assert res.next_hop_node_id == "node_deserted"
        assert res.target_node_id == "node_enigma"

    def test_find_nearest_frontier(self):
        g = build_test_atlas_graph()
        pathfinder = AtlasPathfinder()

        # Beach tiếp giáp deserted (frontier)
        res = pathfinder.find_nearest_frontier(g, "node_beach")
        assert res is not None
        assert res.target_node_id == "node_deserted"


class TestAtlasProgressionPlanner:
    def test_full_completion_rush_strategy(self):
        g = build_test_atlas_graph()
        planner = AtlasProgressionPlanner(graph=g, strategy=ProgressionStrategy.FULL_COMPLETION_RUSH)

        dec = planner.select_next_node(current_node_id="node_beach", current_tier_cap=16)
        assert dec is not None
        # Ưu tiên dọn frontier node khả dụng (deserted)
        assert dec.target_node.node_id == "node_deserted"
        assert dec.strategy == ProgressionStrategy.FULL_COMPLETION_RUSH

    def test_bonus_points_rush_strategy(self):
        g = build_test_atlas_graph()
        planner = AtlasProgressionPlanner(graph=g, strategy=ProgressionStrategy.BONUS_POINTS_RUSH)

        dec = planner.select_next_node(current_node_id="node_beach", current_tier_cap=16)
        assert dec is not None
        # Hướng tới Enigma Chamber vì đây là Quest Node (+1 Điểm Atlas)
        assert dec.target_node.node_id == "node_enigma"
        assert dec.next_step_node.node_id == "node_deserted"  # Phải đi qua deserted trước
        assert dec.strategy == ProgressionStrategy.BONUS_POINTS_RUSH

    def test_citadel_rush_strategy(self):
        g = build_test_atlas_graph()
        planner = AtlasProgressionPlanner(graph=g, strategy=ProgressionStrategy.CITADEL_PINNACLE_RUSH)

        dec = planner.select_next_node(current_node_id="node_beach", current_tier_cap=16)
        assert dec is not None
        assert dec.target_node.node_id == "node_citadel"
        assert dec.next_step_node.node_id == "node_deserted"
        assert dec.strategy == ProgressionStrategy.CITADEL_PINNACLE_RUSH

    def test_encounter_farm_strategy(self):
        g = build_test_atlas_graph()
        # Đánh dấu hoàn thành beach & deserted để mở fortress (có Delirium)
        g.mark_node_completed("node_deserted", bonus=True)

        planner = AtlasProgressionPlanner(
            graph=g,
            strategy=ProgressionStrategy.SPECIAL_ENCOUNTER_FARM,
            preferred_encounters=["Delirium", "Breach"],
        )

        dec = planner.select_next_node(current_node_id="node_deserted", current_tier_cap=16)
        assert dec is not None
        assert dec.target_node.node_id == "node_fortress"
        assert dec.target_node.encounters == ["Delirium"]

    def test_inv_atlas_farm_node_rejects_burning_monolith(self):
        """Khóa chặt bất biến INV-ATLAS-FARM-NODE: Planner tuyệt đối từ chối The Burning Monolith."""
        g = build_test_atlas_graph()
        # Thêm Burning Monolith kết nối trực tiếp với Beach
        n_monolith = AtlasGraphNode(
            node_id="node_monolith",
            name="The Burning Monolith",
            tier=16,
            progression_state=NodeProgressionState.REACHABLE_FRONTIER,
            node_type=AtlasNodeType.BURNING_MONOLITH,
        )
        g.add_node(n_monolith)
        g.add_edge("node_beach", "node_monolith")

        # 1. Full completion rush phải bỏ qua Monolith dù là frontier trực tiếp
        planner_full = AtlasProgressionPlanner(graph=g, strategy=ProgressionStrategy.FULL_COMPLETION_RUSH)
        dec_full = planner_full.select_next_node(current_node_id="node_beach", current_tier_cap=16)
        assert dec_full is not None
        assert dec_full.target_node.node_id != "node_monolith"
        assert dec_full.target_node.name != "The Burning Monolith"

        # 2. Citadel rush phải bỏ qua Monolith
        planner_citadel = AtlasProgressionPlanner(graph=g, strategy=ProgressionStrategy.CITADEL_PINNACLE_RUSH)
        dec_citadel = planner_citadel.select_next_node(current_node_id="node_beach", current_tier_cap=16)
        assert dec_citadel is not None
        assert dec_citadel.target_node.name != "The Burning Monolith"

    def test_calculate_node_weight_monk_ci_tier15_order(self):
        """Thẩm định hàm calculate_node_weight: Deserted > Fortress > Mineshaft > Creek > Marsh > Dunes."""
        from src.assistant_tool.atlas.graph import calculate_node_weight

        n_deserted = AtlasGraphNode(node_id="d1", name="Deserted Map", tier=15)
        n_fortress = AtlasGraphNode(node_id="f1", name="Fortress Map", tier=15)
        n_mineshaft = AtlasGraphNode(node_id="m1", name="Mineshaft Map", tier=15)
        n_creek = AtlasGraphNode(node_id="c1", name="Creek Map", tier=15)
        n_marsh = AtlasGraphNode(node_id="m2", name="Marsh Map", tier=15)
        n_dunes = AtlasGraphNode(node_id="d2", name="Dunes Map", tier=15)
        n_monolith = AtlasGraphNode(node_id="bm", name="The Burning Monolith", tier=16, node_type=AtlasNodeType.BURNING_MONOLITH)
        n_maze = AtlasGraphNode(node_id="mz", name="Maze Map", tier=15)

        w_des = calculate_node_weight(n_deserted)
        w_fort = calculate_node_weight(n_fortress)
        w_mine = calculate_node_weight(n_mineshaft)
        w_crk = calculate_node_weight(n_creek)
        w_mrsh = calculate_node_weight(n_marsh)
        w_dune = calculate_node_weight(n_dunes)
        w_mono = calculate_node_weight(n_monolith)
        w_maze = calculate_node_weight(n_maze, ignored_maps=["Maze"])

        assert w_des > w_fort > w_mine > w_crk > w_mrsh > w_dune, "Thứ tự trọng số phải đúng chuẩn Monk CI layout mở!"
        assert w_mono == -1000.0, "The Burning Monolith phải có trọng số -1000.0 (Fail-Closed)!"
        assert w_maze == -500.0, "Ignored map Maze phải có trọng số -500.0!"

    def test_atlas_progression_planner_cli_helpers(self, capsys):
        """Kiểm tra các hàm điều phối của tools/atlas_progression_planner.py."""
        from tools.atlas_progression_planner import print_completion_stats, plan_next_step, simulate_multi_map
        from src.assistant_tool.atlas.progression_service import AtlasProgressionService

        service = AtlasProgressionService()
        print_completion_stats(service.atlas_graph)
        out = capsys.readouterr().out
        assert "TIẾN TRÌNH ATLAS POE2" in out

        planner = AtlasProgressionPlanner(graph=service.atlas_graph)
        dec = plan_next_step(planner)
        assert dec is not None
        assert dec.target_node.name != "The Burning Monolith"

        simulate_multi_map(service, num_maps=2)
        out_sim = capsys.readouterr().out
        assert "MÔ PHỎNG THUẬT TOÁN HEADLESS" in out_sim
        assert "Map 01" in out_sim


