"""
Unit tests for AutoPOE2 Pickit DSL Parser & Evaluator (Doc 29 Phase P2)
Ref: Doc 29 sections 8.3, 9.2, 9.4, 9.5 & Section 18.P2
Mốc thời gian: 11/09/2026
"""

import pytest
from src.assistant_tool.pickit.parser import parse_ipd, PickitRule, Clause, WeightedSumClause, BinaryOp
from src.assistant_tool.pickit.evaluator import PickitEvaluator, EvaluationResult


def test_parser_basic_and_comments():
    ipd_text = """
    // Single line comment
    [Type] == "Exalted Orb" # [StashItem] == "true"
    //[Rarity] == "Magic" # [Salvage] == "true"
    [Category] == "Waystone" && [WaystoneTier] >= "10" # [RunMap] == "true" // inline comment
    """
    rules = parse_ipd(ipd_text)
    assert len(rules) == 3

    # Rule 1: Exalted Orb
    assert rules[0].enabled is True
    assert "StashItem" in rules[0].actions

    # Rule 2: Disabled rule
    assert rules[1].enabled is False
    assert "Salvage" in rules[1].actions

    # Rule 3: Waystone
    assert rules[2].enabled is True
    assert "RunMap" in rules[2].actions


def test_parser_syntax_error_missing_hash():
    # Rule with actions or content missing '#'
    invalid_ipd = '[Type] == "Exalted Orb" [StashItem] == "true"'
    with pytest.raises(SyntaxError):
        parse_ipd(invalid_ipd)


def test_weighted_sum_evaluator():
    ipd_text = """
    [Rarity] == "Rare" # [WeightedSum(stat_a:2.0,stat_b:3.0)] >= "30" && [StashItem] == "true"
    """
    rules = parse_ipd(ipd_text)
    evaluator = PickitEvaluator(rules)

    # Case 1: 2*10 + 3*5 = 35 >= 30 -> PASS
    item_pass = {
        "identified": True,
        "rarity": "Rare",
        "mods": {"stat_a": 10, "stat_b": 5}
    }
    res_pass = evaluator.evaluate(item_pass)
    assert res_pass.pickup_candidate is True
    assert "StashItem" in res_pass.actions

    # Case 2: 2*5 + 3*5 = 25 < 30 -> FAIL
    item_fail = {
        "identified": True,
        "rarity": "Rare",
        "mods": {"stat_a": 5, "stat_b": 5}
    }
    res_fail = evaluator.evaluate(item_fail)
    assert "StashItem" not in res_fail.actions


def test_uniquename_rejected_in_pre_phase():
    # In POE2, ground item pre-ID cannot be filtered by UniqueName (Doc 29 Section 9.5 INV-PRE-01)
    ipd_text = """
    [UniqueName] == "Mageblood" # [StashItem] == "true"
    """
    rules = parse_ipd(ipd_text)
    evaluator = PickitEvaluator(rules)

    # Item on ground is not identified
    ground_item = {
        "identified": False,
        "type": "Heavy Belt",
        "unique_name": "Mageblood"
    }
    res = evaluator.evaluate(ground_item)
    # Must be rejected because UniqueName cannot be evaluated pre-ID
    assert res.pickup_candidate is False
    assert len(res.actions) == 0


def test_ignore_map_strips_run_map():
    # Doc 29 Section 9.5: IgnoreMap strips RunMap, Upgrade*, Augment*, Exalt*, Corrupt
    ipd_text = """
    [Category] == "Waystone" # [RunMap] == "true"
    [WaystoneTier] <= "5" # [IgnoreMap] == "true"
    """
    rules = parse_ipd(ipd_text)
    evaluator = PickitEvaluator(rules)

    # Low tier waystone
    low_tier_map = {
        "identified": True,
        "category": "Waystone",
        "waystone_tier": 3
    }
    res = evaluator.evaluate(low_tier_map)
    assert res.ignore_map is True
    assert "RunMap" not in res.actions
    assert "IgnoreMap" in res.actions

    # High tier waystone
    high_tier_map = {
        "identified": True,
        "category": "Waystone",
        "waystone_tier": 14
    }
    res_high = evaluator.evaluate(high_tier_map)
    assert res_high.ignore_map is False
    assert "RunMap" in res_high.actions


def test_fail_closed_on_missing_stat():
    ipd_text = """
    [Rarity] == "Rare" # [base_maximum_life] >= "50" && [StashItem] == "true"
    """
    rules = parse_ipd(ipd_text)
    evaluator = PickitEvaluator(rules)

    # Item lacks base_maximum_life
    item_without_mod = {
        "identified": True,
        "rarity": "Rare",
        "mods": {"fire_resistance": 30}
    }
    res = evaluator.evaluate(item_without_mod)
    assert "StashItem" not in res.actions


def test_maps_default_ipd_policy():
    """Kiểm tra file data/maps/default.ipd thực tế theo đặc tả Doc 29 Phase P5."""
    import os
    ipd_path = os.path.join("data", "maps", "default.ipd")
    assert os.path.exists(ipd_path)

    with open(ipd_path, "r", encoding="utf-8") as f:
        ipd_text = f.read()

    rules = parse_ipd(ipd_text)
    assert len(rules) >= 5
    evaluator = PickitEvaluator(rules)

    # 1. Mod nguy hiểm Reflect -> IgnoreMap tước RunMap
    lethal_map = {
        "identified": True,
        "category": "Waystone",
        "name": "Reflect Elemental Damage",
        "waystone_tier": 15,
        "rarity": "Rare"
    }
    res_lethal = evaluator.evaluate(lethal_map)
    assert res_lethal.ignore_map is True
    assert "RunMap" not in res_lethal.actions

    # 2. Waystone T14 Normal -> UpgradeToRare, RunMap
    normal_t14 = {
        "identified": True,
        "category": "Waystone",
        "name": "Waystone (Tier 14)",
        "waystone_tier": 14,
        "rarity": "Normal"
    }
    res_normal = evaluator.evaluate(normal_t14)
    assert res_normal.ignore_map is False
    assert "UpgradeToRare" in res_normal.actions
    assert "RunMap" in res_normal.actions

    # 3. Waystone T16 Rare an toàn -> RunMap
    rare_t16 = {
        "identified": True,
        "category": "Waystone",
        "name": "Waystone (Tier 16)",
        "waystone_tier": 16,
        "rarity": "Rare"
    }
    res_rare = evaluator.evaluate(rare_t16)
    assert res_rare.ignore_map is False
    assert "RunMap" in res_rare.actions

    # 4. Tablet -> UseTablet
    tablet = {
        "identified": True,
        "category": "Tablet",
        "name": "Precursor Tablet",
        "rarity": "Magic"
    }
    res_tablet = evaluator.evaluate(tablet)
    assert "UseTablet" in res_tablet.actions
