"""
AutoPOE2 - Atlas Node Selector & Precursor Tower Orchestrator (Doc 56)
======================================================================
Tier 2 (Cold Path 1-10Hz Python Companion):
- AtlasPerceptionEngine: Nhận diện vùng giao diện Atlas, Map Nodes, Bonus Stars và Tháp Dẫn Đường.
- AtlasStrategyPlanner: Hoạch định chiến lược chọn Node theo whitelist và nạp Precursor Tablet.
- AtlasNodeSelector: Điều phối và gửi lệnh SELECT_ATLAS_NODE (OpCode 9) qua SPSC Ring Buffer.

Tuân thủ bất biến kiến trúc:
- INV-ATLAS-01: Safe Hideout Execution (chỉ kích hoạt tại Hideout/Town, không có quái vật).
- INV-ATLAS-02: Verified Node Target & Hover Confirmation (kiểm tra hover delay).
- INV-ATLAS-03: Zero Hot-Path Blocking (< 8.33ms, toàn bộ nhận diện chạy tại Tier 2).
- INV-ATLAS-04: Zero Wishful Thinking (xác minh thực nghiệm).
"""

import math
import os
import time
from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional, Tuple

try:
    import tomllib  # Python 3.11+
except ImportError:
    try:
        import tomli as tomllib  # type: ignore[import-not-found]
    except ImportError:
        tomllib = None  # Fallback to basic parser if needed

from src.assistant_tool.core_shm_bridge import CoreShmBridge, MacroOpCode
from src.assistant_tool.atlas_knowledge_base import AtlasKnowledgeBase, AtlasNodeType, AtlasNodeProfile


# Bitmask flags cho targetZ khi gửi SELECT_ATLAS_NODE (khớp protocol.hpp)
ATLAS_FLAG_REQUIRE_HOVER_VERIFY = 0x01
ATLAS_FLAG_SOCKET_PRECURSOR_TOWER = 0x02
ATLAS_FLAG_AUTO_PROCEED_TO_WAYSTONE = 0x04


@dataclass
class AtlasNode:
    name: str
    tier: int
    screen_x: float
    screen_y: float
    bonus_completed: bool = False
    is_reachable: bool = True
    is_citadel: bool = False
    node_id: int = 0
    node_type: AtlasNodeType = AtlasNodeType.REGULAR_WAYSTONE_MAP
    is_quest_node: bool = False


@dataclass
class PrecursorTower:
    screen_x: float
    screen_y: float
    empty_sockets: int = 1
    max_sockets: int = 3
    tower_id: int = 0


@dataclass
class AtlasStrategyConfig:
    mode: str = "farm_whitelist"  # "farm_whitelist" | "bonus_rush" | "atlas_quest_rush" | "highest_tier"
    min_tier: int = 14
    max_tier: int = 16
    prioritize_uncompleted_bonus: bool = True
    auto_engage_citadels: bool = True
    auto_engage_quests: bool = True
    preferred_maps: List[str] = field(default_factory=lambda: [
        "Crypt", "Deserted", "Fortress", "Stronghold", "Mineshaft", "Creek", "Marsh", "Sandswept Marsh", "Riverbank"
    ])
    ignored_maps: List[str] = field(default_factory=lambda: [
        "The Burning Monolith", "Arbiter of Ash", "Vaal Pyramid", "Overgrown Ruin", "Maze"
    ])
    enable_precursor_towers: bool = True
    tablet_inventory_row: int = 1
    max_tablets_per_tower: int = 3
    preferred_tablet_types: List[str] = field(default_factory=lambda: [
        "Delirium", "Breach", "Ritual", "Expedition", "Precursor"
    ])


class AtlasPerceptionEngine:
    """
    Khối nhận diện thị giác giao diện Atlas (Cold Path Tier 2).
    Xác định viewport, phân tích icon Map Nodes và Tháp Dẫn Đường Precursor Towers.
    """

    def __init__(self, ref_width: float = 1920.0, ref_height: float = 1080.0):
        self.ref_width = ref_width
        self.ref_height = ref_height
        # Viewport ROI tham chiếu 1080p
        self.roi_x = 350.0
        self.roi_y = 120.0
        self.roi_w = 1220.0
        self.roi_h = 840.0

    def scale_point(self, x: float, y: float, screen_w: float, screen_h: float) -> Tuple[float, float]:
        sx = screen_w / self.ref_width if screen_w > 0 else 1.0
        sy = screen_h / self.ref_height if screen_h > 0 else 1.0
        return round(x * sx, 1), round(y * sy, 1)

    def is_in_viewport(self, x: float, y: float, screen_w: float = 1920.0, screen_h: float = 1080.0) -> bool:
        rx, ry = self.scale_point(self.roi_x, self.roi_y, screen_w, screen_h)
        rw, rh = self.scale_point(self.roi_w, self.roi_h, screen_w, screen_h)
        return (rx <= x <= rx + rw) and (ry <= y <= ry + rh)

    def is_occluded(
        self,
        x: float,
        y: float,
        screen_w: float = 1920.0,
        screen_h: float = 1080.0,
        chat_open: bool = False,
    ) -> bool:
        """
        Kiểm tra điểm (x, y) có bị che khuất bởi các thành phần giao diện cố định không (Doc 56 INV-ATLAS-02).
        - Buff bar trên cùng: y < 110 (1080p)
        - Action bar / Vitals dưới cùng: y > 940 (1080p)
        - Minimap / Quest tracker bên phải: x > 1580 và y < 400 (1080p)
        - Chat window bên trái (nếu chat_open=True hoặc vùng chatbox): x < 480 và y > 500 (1080p)
        - Extreme left panel: x < 300 (1080p)
        """
        sx = screen_w / self.ref_width if screen_w > 0 else 1.0
        sy = screen_h / self.ref_height if screen_h > 0 else 1.0

        if y < 110.0 * sy:
            return True
        if y > 940.0 * sy:
            return True
        if x > 1580.0 * sx and y < 400.0 * sy:
            return True
        if chat_open and (x < 480.0 * sx and y > 500.0 * sy):
            return True
        if x < 300.0 * sx:
            return True

        return False

    def parse_nodes_from_metadata(self, raw_nodes: List[Dict[str, Any]]) -> List[AtlasNode]:
        """Chuyển đổi danh sách node thô từ perception / RAM / config thành danh sách AtlasNode."""
        nodes = []
        for n in raw_nodes:
            name = n.get("name", "Unknown Map")
            profile = AtlasKnowledgeBase.classify_node(name)
            node = AtlasNode(
                name=name,
                tier=int(n.get("tier", 1)),
                screen_x=float(n.get("x", 960.0)),
                screen_y=float(n.get("y", 540.0)),
                bonus_completed=bool(n.get("bonus_completed", False)),
                is_reachable=bool(n.get("is_reachable", True)),
                is_citadel=bool(n.get("is_citadel", False)),
                node_id=int(n.get("node_id", 0)),
                node_type=profile.node_type,
                is_quest_node=profile.is_quest_node,
            )
            nodes.append(node)
        return nodes


class AtlasStrategyPlanner:
    """
    Khối hoạch định chiến lược chọn Map Node tối ưu (Doc 56 Section 3.1.B).
    Lựa chọn node dựa trên cấu hình atlas_policy.toml.
    """

    def __init__(self, config: Optional[AtlasStrategyConfig] = None):
        self.config = config or AtlasStrategyConfig()

    @classmethod
    def load_from_toml(cls, toml_path: str) -> "AtlasStrategyPlanner":
        """Nạp cấu hình từ tệp TOML."""
        cfg = AtlasStrategyConfig()
        if os.path.exists(toml_path):
            try:
                if tomllib:
                    with open(toml_path, "rb") as f:
                        data = tomllib.load(f)
                else:
                    data = {}
                strategy = data.get("strategy", {})
                cfg.mode = strategy.get("mode", cfg.mode)
                cfg.min_tier = int(strategy.get("min_tier", cfg.min_tier))
                cfg.max_tier = int(strategy.get("max_tier", cfg.max_tier))
                cfg.prioritize_uncompleted_bonus = bool(
                    strategy.get("prioritize_uncompleted_bonus", cfg.prioritize_uncompleted_bonus)
                )
                cfg.auto_engage_citadels = bool(
                    strategy.get("auto_engage_citadels", cfg.auto_engage_citadels)
                )

                whitelist = data.get("whitelist", {})
                if "preferred_maps" in whitelist:
                    cfg.preferred_maps = list(whitelist["preferred_maps"])

                blacklist = data.get("blacklist", {})
                if "ignored_maps" in blacklist:
                    cfg.ignored_maps = list(blacklist["ignored_maps"])

                precursor = data.get("precursor_towers", {})
                cfg.enable_precursor_towers = bool(
                    precursor.get("enable_precursor_towers", cfg.enable_precursor_towers)
                )
                cfg.tablet_inventory_row = int(
                    precursor.get("tablet_inventory_row", cfg.tablet_inventory_row)
                )
                cfg.max_tablets_per_tower = int(
                    precursor.get("max_tablets_per_tower", cfg.max_tablets_per_tower)
                )
                if "preferred_tablet_types" in precursor:
                    cfg.preferred_tablet_types = list(precursor["preferred_tablet_types"])
            except Exception:
                pass
        return cls(config=cfg)

    def select_best_node(
        self,
        candidate_nodes: List[AtlasNode],
        perception_engine: Optional[AtlasPerceptionEngine] = None,
        screen_w: float = 1920.0,
        screen_h: float = 1080.0,
        chat_open: bool = False,
    ) -> Optional[AtlasNode]:
        """
        Lựa chọn Map Node tối ưu nhất trong số các ứng viên hợp lệ (Doc 56 Section 3.1.B).
        Tuân thủ whitelist, min/max tier, blacklist, và chiến lược bonus rush / farm whitelist.
        Lọc bỏ các Node nằm ngoài Viewport hoặc bị che khuất (INV-ATLAS-02).
        """
        def _is_node_visually_valid(n: AtlasNode) -> bool:
            if perception_engine is None:
                return True
            if not perception_engine.is_in_viewport(n.screen_x, n.screen_y, screen_w, screen_h):
                return False
            if perception_engine.is_occluded(n.screen_x, n.screen_y, screen_w, screen_h, chat_open=chat_open):
                return False
            return True

        # 0. Chế độ Atlas Quest Rush: Ưu tiên tối thượng cho các Node Nhiệm Vụ nhận Điểm Atlas
        if self.config.mode == "atlas_quest_rush" or self.config.auto_engage_quests:
            quest_nodes = [
                n for n in candidate_nodes
                if n.is_reachable
                and AtlasKnowledgeBase.is_quest_node(n.name)
                and _is_node_visually_valid(n)
            ]
            if quest_nodes:
                return quest_nodes[0]

        # 1. Nếu cho phép tự động đánh Citadel
        if self.config.auto_engage_citadels:
            citadels = [
                n for n in candidate_nodes
                if n.is_reachable
                and (n.is_citadel or "citadel" in n.name.lower())
                and not AtlasKnowledgeBase.is_forbidden_for_waystone_farming(n.name)
                and _is_node_visually_valid(n)
            ]
            if citadels:
                return citadels[0]

        valid_nodes = [
            n for n in candidate_nodes
            if n.is_reachable
            and self.config.min_tier <= n.tier <= self.config.max_tier
            and not any(ignored.lower() in n.name.lower() for ignored in self.config.ignored_maps)
            and not AtlasKnowledgeBase.is_forbidden_for_waystone_farming(n.name)
            and _is_node_visually_valid(n)
        ]

        if not valid_nodes:
            # Fallback: Nếu không có node trong dải tier, chấp nhận node reachable bất kỳ không nằm trong blacklist
            valid_nodes = [
                n for n in candidate_nodes
                if n.is_reachable
                and not any(ignored.lower() in n.name.lower() for ignored in self.config.ignored_maps)
                and not AtlasKnowledgeBase.is_forbidden_for_waystone_farming(n.name)
                and _is_node_visually_valid(n)
            ]
            if not valid_nodes:
                return None

        # 2. Chế độ bonus_rush: Ưu tiên node chưa lấy điểm Bonus
        if self.config.mode == "bonus_rush" or self.config.prioritize_uncompleted_bonus:
            uncompleted = [n for n in valid_nodes if not n.bonus_completed]
            if uncompleted:
                # Ưu tiên map nằm trong whitelist trước
                for pref in self.config.preferred_maps:
                    for n in uncompleted:
                        if pref.lower() in n.name.lower():
                            return n
                # Nếu không có map whitelist chưa lấy bonus, lấy node chưa lấy bonus tier cao nhất
                uncompleted.sort(key=lambda x: x.tier, reverse=True)
                return uncompleted[0]

        # 3. Chế độ farm_whitelist: Ưu tiên map theo thứ tự danh sách preferred_maps
        for pref in self.config.preferred_maps:
            matching = [n for n in valid_nodes if pref.lower() in n.name.lower()]
            if matching:
                # Sắp xếp theo tier cao nhất
                matching.sort(key=lambda x: x.tier, reverse=True)
                return matching[0]

        # 4. Fallback: Lấy node tier cao nhất
        valid_nodes.sort(key=lambda x: x.tier, reverse=True)
        return valid_nodes[0]


class AtlasNodeSelector:
    """
    Bộ điều phối cấp cao kết nối Perception, Strategy và IPC Core (Doc 56).
    """

    def __init__(
        self,
        shm_bridge: Optional[CoreShmBridge] = None,
        config_path: str = "data/maps/atlas_policy.toml",
    ):
        self.bridge = shm_bridge
        self.perception = AtlasPerceptionEngine()
        self.planner = AtlasStrategyPlanner.load_from_toml(config_path)

    def is_safe_to_operate(self, area_name: str, monster_count: int = 0) -> bool:
        """
        Kiểm tra bất biến INV-ATLAS-01: Safe Hideout Execution.
        Chỉ cho phép tương tác Atlas khi ở Hideout / Town và không có quái vật.
        """
        if monster_count > 0:
            return False
        lower = area_name.lower()
        return ("hideout" in lower) or ("town" in lower) or ("encampment" in lower)

    def select_node(
        self,
        node: AtlasNode,
        area_name: str = "Shoreline Hideout",
        monster_count: int = 0,
        screen_w: float = 2560.0,
        screen_h: float = 1440.0,
        chat_open: bool = False,
    ) -> Dict[str, Any]:
        """
        Chọn trực tiếp một AtlasNode cụ thể và gửi lệnh SELECT_ATLAS_NODE (Opcode 9) sang C++ Core.
        Tuân thủ INV-ATLAS-01 (Safe Hideout) và INV-ATLAS-02 (Viewport & Occlusion Fail-Closed).
        """
        return self.plan_and_execute(
            candidate_nodes=[node],
            area_name=area_name,
            monster_count=monster_count,
            screen_w=screen_w,
            screen_h=screen_h,
            chat_open=chat_open,
        )

    def plan_and_execute(
        self,
        candidate_nodes: List[AtlasNode],
        area_name: str = "Shoreline Hideout",
        monster_count: int = 0,
        available_tower: Optional[PrecursorTower] = None,
        screen_w: float = 1920.0,
        screen_h: float = 1080.0,
        chat_open: bool = False,
    ) -> Dict[str, Any]:
        """
        Thực thi luồng chọn Node và gửi lệnh IPC xuống C++ Core.
        Tuân thủ nghiêm ngặt INV-ATLAS-01 (Safe Hideout) và INV-ATLAS-02 (Verified Viewport & Occlusion Fail-Closed).
        """
        result = {
            "success": False,
            "selected_node": None,
            "tower_socketed": False,
            "error": "",
        }

        # 1. Kiểm tra bất biến INV-ATLAS-01: An toàn tại Hideout/Town
        if not self.is_safe_to_operate(area_name, monster_count):
            result["error"] = f"Vi phạm INV-ATLAS-01: Khu vực '{area_name}' không an toàn hoặc có {monster_count} quái vật."
            return result

        # 2. Chọn Node tối ưu qua Planner có kiểm tra Viewport & Occlusion (INV-ATLAS-02)
        target_node = self.planner.select_best_node(
            candidate_nodes,
            perception_engine=self.perception,
            screen_w=screen_w,
            screen_h=screen_h,
            chat_open=chat_open,
        )
        if not target_node:
            result["error"] = "Vi phạm INV-ATLAS-02: Không tìm thấy Map Node nào hợp lệ trong Viewport (hoặc toàn bộ bị che khuất/ngoài khung nhìn)."
            return result

        # 2b. Khóa kiểm tra bất biến INV-ATLAS-02 hai tầng (Fail-Closed)
        if not self.perception.is_in_viewport(target_node.screen_x, target_node.screen_y, screen_w, screen_h):
            result["error"] = f"Vi phạm INV-ATLAS-02: Node '{target_node.name}' ({target_node.screen_x}, {target_node.screen_y}) nằm ngoài Atlas Viewport."
            return result

        if self.perception.is_occluded(target_node.screen_x, target_node.screen_y, screen_w, screen_h, chat_open=chat_open):
            result["error"] = f"Vi phạm INV-ATLAS-02: Node '{target_node.name}' ({target_node.screen_x}, {target_node.screen_y}) bị che khuất bởi giao diện (Occluded)."
            return result

        result["selected_node"] = target_node

        # 3. Tính toán cờ targetZ bitmask
        flags = ATLAS_FLAG_REQUIRE_HOVER_VERIFY | ATLAS_FLAG_AUTO_PROCEED_TO_WAYSTONE
        if available_tower and self.planner.config.enable_precursor_towers and available_tower.empty_sockets > 0:
            if self.perception.is_in_viewport(available_tower.screen_x, available_tower.screen_y, screen_w, screen_h):
                flags |= ATLAS_FLAG_SOCKET_PRECURSOR_TOWER
                result["tower_socketed"] = True

        # 4. Gửi lệnh IPC sang Core nếu bridge có kết nối
        if self.bridge and self.bridge.is_connected():
            sent = self.bridge.send_select_atlas_node(
                screen_x=target_node.screen_x,
                screen_y=target_node.screen_y,
                flags=flags,
                node_id=target_node.node_id,
                timeout_ms=5000,
            )
            result["success"] = sent
            if not sent:
                result["error"] = "Lỗi gửi lệnh SELECT_ATLAS_NODE qua SHM IPC."
        else:
            # Chế độ headless / offline verification (khi Core chưa chạy hoặc test)
            result["success"] = True
        return result

    def get_active_atlas_node_from_ram(
        self,
        fallback_map_name: Optional[str] = None,
        screen_w: float = 2560.0,
        screen_h: float = 1440.0,
    ) -> Optional[Dict[str, Any]]:
        """
        Truy vấn thông tin Node Atlas đang sáng / mở / hoạt động từ bộ nhớ RAM hoặc IPC Telemetry (Doc 56 / Doc 65).
        Cung cấp vị trí / tên map để hỗ trợ điều hướng chuột hoặc kéo (hold LMB) world map.
        """
        active_area = ""
        area_code = ""
        monsters_alive = 0
        is_online = False

        try:
            if self.bridge:
                snap = self.bridge.read_telemetry_snapshot()
                if snap and snap.is_online:
                    is_online = True
                    monsters_alive = getattr(snap, "monsters_alive_total", 0)
                    if snap.area_name and "hideout" not in snap.area_name.lower() and "town" not in snap.area_name.lower():
                        active_area = snap.area_name
                    area_code = getattr(snap, "area_code", "")
        except Exception:
            pass

        if not active_area and fallback_map_name:
            if "hideout" not in fallback_map_name.lower() and "town" not in fallback_map_name.lower():
                active_area = fallback_map_name

        if not active_area:
            return None

        profile = AtlasKnowledgeBase.classify_node(active_area)

        # Bảng tọa độ Atlas tham chiếu chuẩn hóa (2560x1440) cho các map phổ biến
        KNOWN_NODE_COORDS = {
            "deserted": (1281.0, 546.0),
            "fortress": (1420.0, 570.0),
            "creek": (1050.0, 780.0),
            "the burning monolith": (1043.0, 810.0),
            "monolith": (1043.0, 810.0),
            "the well of souls": (1250.0, 480.0),
            "heart of the well": (1250.0, 480.0),
            "origin tower": (1280.0, 500.0),
            "precursor reactor": (1280.0, 500.0),
            "enigma chamber": (1350.0, 420.0),
            "corrupted nexus": (1180.0, 620.0),
            "riverbank": (960.0, 720.0),
            "sandswept marsh": (1150.0, 680.0),
        }

        sx = screen_w / 2560.0 if screen_w > 0 else 1.0
        sy = screen_h / 1440.0 if screen_h > 0 else 1.0

        suggested_pt = None
        lower_name = active_area.lower()
        for k, (ref_x, ref_y) in KNOWN_NODE_COORDS.items():
            if k in lower_name:
                suggested_pt = (int(round(ref_x * sx)), int(round(ref_y * sy)))
                break

        return {
            "area_name": active_area,
            "canonical_name": profile.canonical_name,
            "area_code": area_code,
            "is_online": is_online,
            "is_opened": True,
            "monsters_alive": monsters_alive,
            "node_type": profile.node_type.value,
            "accepts_waystone": profile.accepts_waystone,
            "is_quest_node": profile.is_quest_node,
            "suggested_screen_pos": suggested_pt,
        }
