"""AutoPOE2 - Atlas Progression Planner & Full Unlock Engine (Doc 56 / Doc 65 / Doc 69).
========================================================================================
Bộ hoạch định chiến lược cấp cao cho tiến trình mở khóa toàn bộ cây bản đồ Atlas (Full Node Progression):
- Đạt 100% tỷ lệ hoàn thành bản đồ (Atlas Completion Rate) và lấy tối đa Điểm Nội Tại Atlas.
- Lựa chọn linh hoạt giữa 4 chiến lược:
  1. FULL_COMPLETION_RUSH: Duyệt đồ thị theo vết dầu loang mở sạch 115+ nodes.
  2. BONUS_POINTS_RUSH: Săn lùng toàn bộ các node nhiệm vụ Questlines (+1 Point) và Unique Maps (+2 Points).
  3. SPECIAL_ENCOUNTER_FARM: Khai thác tối đa các node có Breach, Delirium, Ritual, Expedition.
  4. CITADEL_PINNACLE_RUSH: Tối ưu hóa đường đi thẳng tới 3 Thành Lũy Citadels (Geonor, Jamanra, Doryani).
- Tích hợp phủ sóng Tháp Tiền Thân (Precursor Towers) để nạp Tablets tối ưu hóa lợi nhuận.

Tuân thủ nghiêm ngặt:
- Rule 1: Cold Path Tier 2 (Python 3.11).
- Rule 4: Module hóa, trần file < 500 dòng.
- Rule 8: Bất biến kiến trúc INV-ATLAS-FARM-NODE & INV-ATLAS-QUEST-PROG.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Dict, List, Optional, Set, Tuple

from src.assistant_tool.atlas.graph import AtlasGraph, AtlasGraphNode, NodeProgressionState, calculate_node_weight
from src.assistant_tool.atlas.pathfinder import AtlasPathfinder, AtlasPathResult
from src.assistant_tool.atlas.encounter_matcher import EncounterMatcher, EncounterType
from src.assistant_tool.atlas_knowledge_base import AtlasKnowledgeBase, AtlasNodeType


class ProgressionStrategy(str, Enum):
    """Chiến lược tiến trình mở khóa cây bản đồ Atlas."""
    FULL_COMPLETION_RUSH = "FULL_COMPLETION_RUSH"       # Mở khóa toàn bộ 100% nodes
    BONUS_POINTS_RUSH = "BONUS_POINTS_RUSH"             # Săn điểm nội tại Atlas (+1/+2 points)
    SPECIAL_ENCOUNTER_FARM = "SPECIAL_ENCOUNTER_FARM"   # Ưu tiên các cụm Breach / Delirium / Ritual
    CITADEL_PINNACLE_RUSH = "CITADEL_PINNACLE_RUSH"     # Hướng thẳng tới các Boss Citadels


@dataclass
class ProgressionDecision:
    """Quyết định điều phối điểm nút tiếp theo của Atlas."""
    target_node: AtlasGraphNode
    next_step_node: AtlasGraphNode
    route_node_ids: List[str]
    strategy: ProgressionStrategy
    estimated_cost: float
    reason: str
    recommended_tablets: List[str] = field(default_factory=list)

    @property
    def requires_hop(self) -> bool:
        """True nếu cần đi qua node trung gian để tới target_node."""
        return self.target_node.node_id != self.next_step_node.node_id


class AtlasProgressionPlanner:
    """Bộ điều phối và hoạch định tiến trình Atlas toàn diện."""

    def __init__(
        self,
        graph: Optional[AtlasGraph] = None,
        strategy: ProgressionStrategy = ProgressionStrategy.FULL_COMPLETION_RUSH,
        preferred_encounters: Optional[List[str]] = None,
    ):
        self.graph = graph or AtlasGraph()
        self.strategy = strategy
        self.preferred_encounters = preferred_encounters or ["Delirium", "Breach", "Ritual", "Expedition"]
        self.pathfinder = AtlasPathfinder()

    def select_next_node(
        self,
        current_node_id: Optional[str] = None,
        current_tier_cap: int = 16,
    ) -> Optional[ProgressionDecision]:
        """
        Lựa chọn Map Node tối ưu tiếp theo dựa trên đồ thị và chiến lược đang kích hoạt.
        """
        if self.graph.total_nodes == 0:
            return None

        # 1. Nếu chưa có current_node_id, lấy node Gateway Tier 1 hoặc node completed bất kỳ
        if not current_node_id or not self.graph.get_node(current_node_id):
            completed = self.graph.get_completed_nodes()
            if completed:
                current_node_id = completed[0].node_id
            else:
                tier1_nodes = self.graph.get_nodes_by_tier(1)
                if tier1_nodes:
                    current_node_id = tier1_nodes[0].node_id
                else:
                    frontiers = self.graph.get_frontier_nodes()
                    if frontiers:
                        current_node_id = frontiers[0].node_id
                    else:
                        return None

        # 2. Phân nhánh xử lý theo chiến lược:
        if self.strategy == ProgressionStrategy.BONUS_POINTS_RUSH:
            return self._plan_bonus_points_rush(current_node_id, current_tier_cap)
        elif self.strategy == ProgressionStrategy.CITADEL_PINNACLE_RUSH:
            return self._plan_citadel_rush(current_node_id, current_tier_cap)
        elif self.strategy == ProgressionStrategy.SPECIAL_ENCOUNTER_FARM:
            return self._plan_encounter_farm(current_node_id, current_tier_cap)
        else:
            return self._plan_full_completion_rush(current_node_id, current_tier_cap)

    def _plan_bonus_points_rush(
        self, current_id: str, tier_cap: int
    ) -> Optional[ProgressionDecision]:
        """Ưu tiên tối thượng cho Quest Nodes (+1 Point) và Unique Maps (+2 Points).
        
        Khóa chặt bất biến INV-ATLAS-FARM-NODE: Tuyệt đối từ chối The Burning Monolith (Arbiter of Ash).
        """
        uncompleted = self.graph.get_uncompleted_nodes()
        quest_and_uniques = [
            n for n in uncompleted
            if (n.node_type in (
                AtlasNodeType.QUEST_ENIGMA_CHAMBER,
                AtlasNodeType.QUEST_CORRUPTED_NEXUS,
                AtlasNodeType.QUEST_GREAT_BEAST,
                AtlasNodeType.QUEST_WITHERED_WILLOW,
                AtlasNodeType.QUEST_KINGSMARCH_RUINS,
                AtlasNodeType.UNIQUE_MAP,
            ) or n.atlas_points_reward > 1)
            and n.tier <= tier_cap
            and n.node_type != AtlasNodeType.BURNING_MONOLITH
            and "BURNING MONOLITH" not in n.name.upper()
            and "ARBITER OF ASH" not in n.name.upper()
        ]

        if quest_and_uniques:
            # Tìm node có lộ trình ngắn nhất
            best_decision = self._find_best_path_to_candidates(current_id, quest_and_uniques)
            if best_decision:
                best_decision.reason = f"Bonus Points Rush: Hướng tới {best_decision.target_node.name} (+{best_decision.target_node.atlas_points_reward} Điểm Atlas)"
                return best_decision

        # Fallback sang full completion
        return self._plan_full_completion_rush(current_id, tier_cap)

    def _plan_citadel_rush(
        self, current_id: str, tier_cap: int
    ) -> Optional[ProgressionDecision]:
        """Hướng thẳng tới các Thành Lũy Boss Citadel (Tier 15-16).
        
        Khóa chặt bất biến INV-ATLAS-FARM-NODE: Tuyệt đối từ chối The Burning Monolith (Arbiter of Ash).
        """
        uncompleted = self.graph.get_uncompleted_nodes()
        citadels = [
            n for n in uncompleted
            if (n.node_type == AtlasNodeType.CITADEL_BOSS or "citadel" in n.name.lower())
            and n.node_type != AtlasNodeType.BURNING_MONOLITH
            and "BURNING MONOLITH" not in n.name.upper()
            and "ARBITER OF ASH" not in n.name.upper()
        ]

        if citadels:
            best_decision = self._find_best_path_to_candidates(current_id, citadels)
            if best_decision:
                best_decision.reason = f"Citadel Rush: Di chuyển tới Thành Lũy {best_decision.target_node.name}"
                return best_decision

        return self._plan_full_completion_rush(current_id, tier_cap)

    def _plan_encounter_farm(
        self, current_id: str, tier_cap: int
    ) -> Optional[ProgressionDecision]:
        """Ưu tiên các map có cơ chế Breach, Delirium, Ritual, Expedition."""
        frontiers = [
            n for n in self.graph.get_frontier_nodes()
            if n.tier <= tier_cap
            and n.node_type != AtlasNodeType.BURNING_MONOLITH
            and not AtlasKnowledgeBase.is_forbidden_for_waystone_farming(n.name)
            and "BURNING MONOLITH" not in n.name.upper()
            and "ARBITER OF ASH" not in n.name.upper()
        ]
        if not frontiers:
            frontiers = [
                n for n in self.graph.get_uncompleted_nodes()
                if n.tier <= tier_cap
                and n.node_type != AtlasNodeType.BURNING_MONOLITH
                and not AtlasKnowledgeBase.is_forbidden_for_waystone_farming(n.name)
                and "BURNING MONOLITH" not in n.name.upper()
                and "ARBITER OF ASH" not in n.name.upper()
            ]

        if not frontiers:
            return None

        # Chấm điểm cơ chế
        scored_nodes = []
        for n in frontiers:
            score = EncounterMatcher.calculate_score(n.encounters, n.tower_coverage_count, self.preferred_encounters)
            scored_nodes.append((score, n))

        scored_nodes.sort(key=lambda x: x[0], reverse=True)
        top_candidates = [n for _, n in scored_nodes[:5]]

        best_decision = self._find_best_path_to_candidates(current_id, top_candidates)
        if best_decision:
            best_decision.reason = f"Encounter Farm: Chọn {best_decision.target_node.name} (Mechanics: {', '.join(best_decision.target_node.encounters)})"
            best_decision.recommended_tablets = list(self.preferred_encounters[:2])
            return best_decision

        return self._plan_full_completion_rush(current_id, tier_cap)

    def _plan_full_completion_rush(
        self, current_id: str, tier_cap: int
    ) -> Optional[ProgressionDecision]:
        """Thuật toán duyệt biên mở rộng (Frontier Progression) hoàn thành 100% full nodes.
        
        Áp dụng calculate_node_weight() ưu tiên map mở an toàn cho Monk CI Tier 15.
        """
        frontiers = [
            n for n in self.graph.get_frontier_nodes()
            if n.tier <= tier_cap
            and n.node_type != AtlasNodeType.BURNING_MONOLITH
            and not AtlasKnowledgeBase.is_forbidden_for_waystone_farming(n.name)
            and "BURNING MONOLITH" not in n.name.upper()
            and "ARBITER OF ASH" not in n.name.upper()
        ]

        if frontiers:
            # Ưu tiên các node biên có trọng số cao nhất (Monk CI: Deserted > Fortress > Mineshaft > Creek > Marsh > Dunes)
            frontiers.sort(
                key=lambda n: (
                    -calculate_node_weight(n),
                    n.tier,
                    -EncounterMatcher.calculate_score(n.encounters, n.tower_coverage_count),
                )
            )
            best_decision = self._find_best_path_to_candidates(current_id, frontiers[:6])
            if best_decision:
                best_decision.reason = f"Full Completion Rush: Mở khóa node biên mới '{best_decision.target_node.name}' (Tier {best_decision.target_node.tier})"
                return best_decision

        # Nếu không còn frontier trực tiếp trong tier_cap, kiểm tra các node COMPLETED nhưng chưa lấy BONUS
        need_bonus = [
            n for n in self.graph.get_completed_nodes()
            if not n.is_bonus_completed
            and n.tier <= tier_cap
            and n.node_type != AtlasNodeType.BURNING_MONOLITH
            and not AtlasKnowledgeBase.is_forbidden_for_waystone_farming(n.name)
            and "BURNING MONOLITH" not in n.name.upper()
            and "ARBITER OF ASH" not in n.name.upper()
        ]
        if need_bonus:
            need_bonus.sort(key=lambda n: -calculate_node_weight(n))
            best_decision = self._find_best_path_to_candidates(current_id, need_bonus)
            if best_decision:
                best_decision.reason = f"Bonus Catchup: Chạy lại '{best_decision.target_node.name}' để đạt chuẩn Bonus Objective"
                return best_decision

        return None

    def _find_best_path_to_candidates(
        self, current_id: str, candidates: List[AtlasGraphNode]
    ) -> Optional[ProgressionDecision]:
        """Tìm ứng viên có lộ trình chi phí thấp nhất từ current_id."""
        best_path: Optional[AtlasPathResult] = None
        best_target: Optional[AtlasGraphNode] = None
        min_cost = float("inf")

        # Khóa an toàn INV-ATLAS-FARM-NODE: Bắt buộc loại trừ The Burning Monolith
        safe_candidates = [
            target for target in candidates
            if target.node_type != AtlasNodeType.BURNING_MONOLITH
            and "BURNING MONOLITH" not in target.name.upper()
            and "ARBITER OF ASH" not in target.name.upper()
        ]

        for target in safe_candidates:
            res = self.pathfinder.find_shortest_path(self.graph, current_id, target.node_id)
            if res and res.total_cost < min_cost:
                min_cost = res.total_cost
                best_path = res
                best_target = target

        if best_path and best_target:
            next_hop_id = best_path.next_hop_node_id or best_target.node_id
            next_node = self.graph.get_node(next_hop_id) or best_target
            return ProgressionDecision(
                target_node=best_target,
                next_step_node=next_node,
                route_node_ids=best_path.path_node_ids,
                strategy=self.strategy,
                estimated_cost=best_path.total_cost,
                reason="",
            )

        return None
