"""
Unit Tests for Atlas Node Selector & Endgame Atlas Strategy (Doc 56 / Doc 65 / Doc 70)
======================================================================================
Kiểm thử toàn diện các bất biến kiến trúc:
- INV-ATLAS-FARM-NODE: Tuyệt đối loại trừ và cấm đi vào The Burning Monolith (Arbiter of Ash).
- Monk CI Tier 15 Optimization: Ưu tiên các map mở, an toàn (Deserted, Fortress, Mineshaft, Creek, Marsh).
- INV-ATLAS-01: Safe Hideout Execution (chỉ kích hoạt tại Hideout/Town, 0 quái vật).
- INV-ATLAS-02: Verified Node Target & Hover Confirmation (Viewport ROI & Occlusion Fail-Closed).
- Precursor Tower Integration: Nhận diện và cắm Precursor Tablet hợp lệ.
"""

import pytest
from src.assistant_tool.atlas_knowledge_base import (
    AtlasKnowledgeBase,
    AtlasNodeType,
    AtlasNodeProfile,
)
from src.assistant_tool.atlas_node_selector import (
    AtlasNode,
    PrecursorTower,
    AtlasPerceptionEngine,
    AtlasStrategyPlanner,
    AtlasStrategyConfig,
    AtlasNodeSelector,
    ATLAS_FLAG_REQUIRE_HOVER_VERIFY,
    ATLAS_FLAG_SOCKET_PRECURSOR_TOWER,
    ATLAS_FLAG_AUTO_PROCEED_TO_WAYSTONE,
)


class TestInvAtlasFarmNodeSafety:
    """Kiểm thử bất biến INV-ATLAS-FARM-NODE: Cấm tuyệt đối The Burning Monolith (Arbiter of Ash)."""

    def test_burning_monolith_classified_and_forbidden(self):
        # 1. Kiểm tra phân loại Node trong AtlasKnowledgeBase
        profile_bm = AtlasKnowledgeBase.classify_node("The Burning Monolith")
        assert profile_bm.node_type == AtlasNodeType.BURNING_MONOLITH
        assert profile_bm.accepts_waystone is False

        profile_ash = AtlasKnowledgeBase.classify_node("Arbiter of Ash")
        assert profile_ash.node_type == AtlasNodeType.BURNING_MONOLITH
        assert profile_ash.accepts_waystone is False

        # 2. Kiểm tra hàm thẩm định bất biến is_forbidden_for_waystone_farming
        assert AtlasKnowledgeBase.is_forbidden_for_waystone_farming("The Burning Monolith") is True
        assert AtlasKnowledgeBase.is_forbidden_for_waystone_farming("Burning Monolith") is True
        assert AtlasKnowledgeBase.is_forbidden_for_waystone_farming("Arbiter of Ash") is True
        assert AtlasKnowledgeBase.is_forbidden_for_waystone_farming("The Burning Monolith (Arbiter of Ash)") is True

    def test_planner_rejects_burning_monolith_even_with_highest_tier(self):
        planner = AtlasStrategyPlanner()

        candidates = [
            AtlasNode(name="The Burning Monolith", tier=16, screen_x=700.0, screen_y=500.0, is_reachable=True),
            AtlasNode(name="Arbiter of Ash Arena", tier=16, screen_x=720.0, screen_y=520.0, is_reachable=True),
            AtlasNode(name="Deserted Map", tier=15, screen_x=800.0, screen_y=600.0, is_reachable=True),
        ]

        best = planner.select_best_node(candidates)
        assert best is not None
        assert "Deserted" in best.name
        assert "Burning Monolith" not in best.name
        assert "Arbiter of Ash" not in best.name

    def test_planner_rejects_burning_monolith_as_citadel(self):
        """Kể cả khi bị gán cờ is_citadel hoặc auto_engage_citadels=True, Burning Monolith vẫn bị cấm."""
        cfg = AtlasStrategyConfig(auto_engage_citadels=True)
        planner = AtlasStrategyPlanner(config=cfg)

        candidates = [
            AtlasNode(name="The Burning Monolith", tier=16, screen_x=700.0, screen_y=500.0, is_reachable=True, is_citadel=True),
            AtlasNode(name="Iron Citadel", tier=16, screen_x=750.0, screen_y=550.0, is_reachable=True, is_citadel=True),
            AtlasNode(name="Deserted Map", tier=15, screen_x=800.0, screen_y=600.0, is_reachable=True),
        ]

        # The Burning Monolith bị cấm qua INV-ATLAS-FARM-NODE -> Iron Citadel được chọn nếu là Citadel hợp lệ
        best = planner.select_best_node(candidates)
        assert best is not None
        assert best.name != "The Burning Monolith"


class TestMonkCITier15Preferences:
    """Kiểm thử chiến lược tối ưu bản đồ mở cho Monk CI Tier 15 (Deserted, Fortress, Mineshaft, Creek, Marsh)."""

    def test_monk_ci_preferred_maps_order(self):
        planner = AtlasStrategyPlanner()

        # Kiểm tra danh sách preferred_maps mặc định chứa đủ các map an toàn (Crypt, Deserted, Fortress, Creek...)
        expected_safe_maps = ["Crypt", "Deserted", "Fortress", "Mineshaft", "Creek", "Marsh"]
        for m in expected_safe_maps:
            assert any(m.lower() in pref.lower() for pref in planner.config.preferred_maps)

        # Ứng viên chứa các map mở ở cùng Tier 15 -> Phải chọn theo thứ tự ưu tiên
        candidates = [
            AtlasNode(name="Marsh Map", tier=15, screen_x=600.0, screen_y=400.0),
            AtlasNode(name="Creek Map", tier=15, screen_x=650.0, screen_y=450.0),
            AtlasNode(name="Mineshaft Map", tier=15, screen_x=700.0, screen_y=500.0),
            AtlasNode(name="Fortress Map", tier=15, screen_x=750.0, screen_y=550.0),
            AtlasNode(name="Deserted Map", tier=15, screen_x=800.0, screen_y=600.0),
            AtlasNode(name="Crypt Map", tier=15, screen_x=850.0, screen_y=650.0),
        ]

        # Crypt đứng đầu preferred_maps -> Phải được chọn
        best_crypt = planner.select_best_node(candidates)
        assert best_crypt is not None
        assert "Crypt" in best_crypt.name

        # Bỏ Crypt -> Deserted đứng kế tiếp
        best = planner.select_best_node(candidates[:-1])
        assert best is not None
        assert "Deserted" in best.name

        # Bỏ Deserted -> Fortress đứng kế tiếp
        best2 = planner.select_best_node(candidates[:4])
        assert best2 is not None
        assert "Fortress" in best2.name

        # Bỏ Fortress -> Mineshaft đứng kế tiếp
        best3 = planner.select_best_node(candidates[:3])
        assert best3 is not None
        assert "Mineshaft" in best3.name

        # Bỏ Mineshaft -> Creek đứng kế tiếp
        best4 = planner.select_best_node(candidates[:2])
        assert best4 is not None
        assert "Creek" in best4.name

        # Bỏ Creek -> Marsh đứng kế tiếp
        best5 = planner.select_best_node(candidates[:1])
        assert best5 is not None
        assert "Marsh" in best5.name

    def test_tier_15_filtering(self):
        planner = AtlasStrategyPlanner()
        # candidate tier 13 (ngoài dải 14-16) vs tier 15
        candidates = [
            AtlasNode(name="Deserted Map (Low Tier)", tier=13, screen_x=700.0, screen_y=500.0),
            AtlasNode(name="Fortress Map (Tier 15)", tier=15, screen_x=800.0, screen_y=600.0),
        ]
        best = planner.select_best_node(candidates)
        assert best is not None
        assert best.name == "Fortress Map (Tier 15)"
        assert best.tier == 15


class TestAtlasCoreInvariants:
    """Kiểm thử các bất biến kiến trúc cơ sở (INV-ATLAS-01, INV-ATLAS-02)."""

    def test_inv_atlas_01_safe_hideout_execution(self):
        selector = AtlasNodeSelector()
        candidates = [
            AtlasNode(name="Deserted Map", tier=15, screen_x=700.0, screen_y=500.0)
        ]

        # Có quái vật -> Từ chối ngay
        res_danger = selector.plan_and_execute(
            candidate_nodes=candidates,
            area_name="Shoreline Hideout",
            monster_count=3,
        )
        assert res_danger["success"] is False
        assert "INV-ATLAS-01" in res_danger["error"]

        # Không phải Hideout/Town -> Từ chối ngay
        res_map = selector.plan_and_execute(
            candidate_nodes=candidates,
            area_name="Deserted",
            monster_count=0,
        )
        assert res_map["success"] is False
        assert "INV-ATLAS-01" in res_map["error"]

        # Hideout với 0 quái vật -> Thành công
        res_ok = selector.plan_and_execute(
            candidate_nodes=candidates,
            area_name="Shoreline Hideout",
            monster_count=0,
        )
        assert res_ok["success"] is True

    def test_inv_atlas_02_viewport_and_occlusion(self):
        selector = AtlasNodeSelector()

        # Node ngoài Viewport ROI (x=100, y=50) -> Fail-Closed
        out_node = [AtlasNode(name="Deserted Map", tier=15, screen_x=100.0, screen_y=50.0)]
        res_out = selector.plan_and_execute(candidate_nodes=out_node, area_name="Shoreline Hideout", monster_count=0)
        assert res_out["success"] is False
        assert "INV-ATLAS-02" in res_out["error"]

        # Node bị che bởi chat window khi chat mở -> Fail-Closed
        chat_node = [AtlasNode(name="Deserted Map", tier=15, screen_x=400.0, screen_y=700.0)]
        res_chat = selector.plan_and_execute(
            candidate_nodes=chat_node,
            area_name="Shoreline Hideout",
            monster_count=0,
            chat_open=True,
        )
        assert res_chat["success"] is False
        assert "INV-ATLAS-02" in res_chat["error"]

    def test_precursor_tower_socketing(self):
        selector = AtlasNodeSelector()
        candidates = [AtlasNode(name="Deserted Map", tier=15, screen_x=700.0, screen_y=500.0)]
        tower = PrecursorTower(screen_x=960.0, screen_y=380.0, empty_sockets=2)

        res = selector.plan_and_execute(
            candidate_nodes=candidates,
            area_name="Shoreline Hideout",
            monster_count=0,
            available_tower=tower,
        )
        assert res["success"] is True
        assert res["tower_socketed"] is True
