"""
AutoPOE2 Pickit DSL Evaluator (Phase P2 - Doc 29 mục 8.3 & 9.5)
Đánh giá vật phẩm dựa trên cây AST của các quy tắc .ipd.
Tuân thủ nghiêm ngặt:
- Fail-closed: thiếu thuộc tính số để đánh giá -> mệnh đề trả về False.
- Reject UniqueName ở PRE-phase (đồ chưa ID trên sàn không thể biết Unique Name).
- IgnoreMap tước toàn bộ quyền RunMap và Upgrade/Craft.
"""

from __future__ import annotations
from dataclasses import dataclass, field
from typing import Dict, Any, List, Set, Tuple, Optional
from .parser import ASTNode, Clause, WeightedSumClause, BinaryOp, PickitRule


@dataclass
class EvaluationResult:
    """Kết quả đánh giá 1 vật phẩm qua danh mục rule."""
    pickup_candidate: bool = False
    actions: Set[str] = field(default_factory=set)
    matched_rules: List[int] = field(default_factory=list)  # danh sách line_num khớp
    ignore_map: bool = False


class PickitEvaluator:
    """Động cơ đánh giá quy tắc Pickit DSL."""

    def __init__(self, rules: List[PickitRule]):
        self.rules = rules

    def evaluate_node(self, node: Optional[ASTNode], item: Dict[str, Any], phase: str) -> bool:
        """Đánh giá đệ quy một nút AST trên thuộc tính của vật phẩm."""
        if node is None:
            return True

        if isinstance(node, BinaryOp):
            if node.op == "&&":
                return self.evaluate_node(node.left, item, phase) and self.evaluate_node(node.right, item, phase)
            elif node.op == "||":
                return self.evaluate_node(node.left, item, phase) or self.evaluate_node(node.right, item, phase)
            return False

        if isinstance(node, WeightedSumClause):
            # WeightedSum chỉ đánh giá khi có mods hoặc ở pha POST
            val = 0.0
            mods = item.get("mods", {})
            for w, k in node.terms:
                # Tìm trong computed fields hoặc mods
                term_val = None
                if k in item:
                    term_val = item[k]
                elif k in mods:
                    term_val = mods[k]
                else:
                    # Tra cứu normalize
                    norm_k = k.lower().replace(" ", "_")
                    if norm_k in item:
                        term_val = item[norm_k]
                    elif norm_k in mods:
                        term_val = mods[norm_k]

                if term_val is None:
                    # Fail closed: thiếu stat thì cả mệnh đề fail
                    return False
                try:
                    val += w * float(term_val)
                except (ValueError, TypeError):
                    return False

            return self._compare_numeric(val, node.op, node.value)

        if isinstance(node, Clause):
            key = node.key
            op = node.op
            val_str = node.value

            # Hành động (Actions) luôn coi là điều kiện True khi đánh giá biểu thức
            # vì chúng đại diện cho lệnh thực thi (Stash, Run, v.v.)
            from .parser import KNOWN_ACTIONS
            if key in KNOWN_ACTIONS:
                return True

            # INV-PRE-01: UniqueName bị reject ở PRE phase (Doc 29 mục 9.5)
            if phase == "PRE" and key.lower() in ("uniquename", "unique_name"):
                return False

            # Lấy giá trị thực tế của item
            item_val = self._get_item_value(item, key)
            if item_val is None:
                # Fail closed
                return False

            # So sánh số hoặc chuỗi
            return self._compare_values(item_val, op, val_str)

        return False

    def _get_item_value(self, item: Dict[str, Any], key: str) -> Any:
        """Truy xuất giá trị thuộc tính từ item dict (hỗ trợ normalize hoa thường và alias)."""
        # Trực tiếp (case-sensitive và case-insensitive)
        if key in item:
            return item[key]
        k_low = key.lower()
        if k_low in item:
            return item[k_low]

        # Aliases chuẩn Doc 29
        alias_map = {
            "type": "type",
            "name": "type",
            "rarity": "rarity",
            "category": "category",
            "weaponcategory": "weapon_category",
            "weapon_category": "weapon_category",
            "waystonetier": "waystone_tier",
            "waystone_tier": "waystone_tier",
            "tier": "waystone_tier",
            "itemlevel": "item_level",
            "item_level": "item_level",
            "ilvl": "item_level",
            "quality": "quality",
            "sockets": "sockets",
            "identified": "identified",
            "totalresistances": "total_resistances",
            "total_resistances": "total_resistances",
            "totaladditionalattributes": "total_additional_attributes",
            "total_additional_attributes": "total_additional_attributes",
        }

        mapped = alias_map.get(k_low)
        if mapped and mapped in item:
            return item[mapped]

        # Kiểm tra trong item.mods
        mods = item.get("mods", {})
        if key in mods:
            return mods[key]
        if k_low in mods:
            return mods[k_low]

        norm_key = k_low.replace(" ", "_")
        if norm_key in mods:
            return mods[norm_key]

        # Nếu tìm [Name] hoặc [Mod] nhưng không khớp type, trả về danh sách mods nếu có
        if k_low in ("name", "mod", "mods") and "mods" in item:
            return item["mods"]

        return None

    def _compare_values(self, item_val: Any, op: str, target_val_str: str) -> bool:
        """So sánh giá trị item_val với target_val_str theo toán tử op."""
        # Danh sách / Tập hợp (ví dụ danh sách mods trên Waystone / Item)
        if isinstance(item_val, (list, set, tuple)):
            target_norm = target_val_str.strip().lower()
            if op == "==":
                return any(str(x).strip().lower() == target_norm for x in item_val)
            elif op == "!=":
                return not any(str(x).strip().lower() == target_norm for x in item_val)
            return False

        if isinstance(item_val, dict):
            target_norm = target_val_str.strip().lower()
            if op == "==":
                return any(str(k).strip().lower() == target_norm for k in item_val.keys())
            elif op == "!=":
                return not any(str(k).strip().lower() == target_norm for k in item_val.keys())
            return False

        # Bool
        if isinstance(item_val, bool):
            target_bool = target_val_str.lower() in ("true", "1", "yes")
            if op == "==":
                return item_val == target_bool
            elif op == "!=":
                return item_val != target_bool
            return False

        # Numeric (nếu cả 2 đều là số)
        try:
            num_item = float(item_val)
            num_target = float(target_val_str)
            return self._compare_numeric(num_item, op, num_target)
        except (ValueError, TypeError):
            pass

        # String compare (case-insensitive)
        str_item = str(item_val).strip().lower()
        str_target = target_val_str.strip().lower()

        if op == "==":
            return str_item == str_target
        elif op == "!=":
            return str_item != str_target
        elif op == "<":
            return str_item < str_target
        elif op == "<=":
            return str_item <= str_target
        elif op == ">":
            return str_item > str_target
        elif op == ">=":
            return str_item >= str_target

        return False

    def _compare_numeric(self, left: float, op: str, right: float) -> bool:
        if op == "==":
            return abs(left - right) < 1e-6
        elif op == "!=":
            return abs(left - right) >= 1e-6
        elif op == "<":
            return left < right
        elif op == "<=":
            return left <= right
        elif op == ">":
            return left > right
        elif op == ">=":
            return left >= right
        return False

    def evaluate(self, item: Dict[str, Any]) -> EvaluationResult:
        """
        Đánh giá toàn bộ danh mục rule lên một vật phẩm.
        Trả về EvaluationResult gồm cờ nhặt trên sàn và danh mục action sau khi ID.
        """
        result = EvaluationResult()
        is_identified = bool(item.get("identified", False))

        for rule in self.rules:
            if not rule.enabled:
                continue

            # Pha PRE-identify
            if not self.evaluate_node(rule.pre_ast, item, phase="PRE"):
                continue

            if is_identified:
                # Đã ID: đánh giá tiếp POST-identify
                if rule.post_ast and not self.evaluate_node(rule.post_ast, item, phase="POST"):
                    continue

                result.matched_rules.append(rule.line_num)
                result.actions.update(rule.actions)
                if "IgnoreMap" in rule.actions:
                    result.ignore_map = True
                result.pickup_candidate = True
            else:
                # Chưa ID trên sàn:
                # Nếu rule có action giữ đồ/chạy map -> chấp nhận là pickup candidate
                if rule.actions:
                    result.pickup_candidate = True
                    result.matched_rules.append(rule.line_num)
                    if "StashUnid" in rule.actions:
                        result.actions.add("StashUnid")

        # Hợp đồng Doc 29 mục 9.5: IgnoreMap tước RunMap, Upgrade*, Augment*, Exalt*, Corrupt
        if result.ignore_map:
            strip_actions = {"RunMap", "UpgradeToRare", "Augment", "Exalt", "Corrupt"}
            result.actions.difference_update(strip_actions)

        return result
