"""AutoPOE2 - Unit Tests for Stash & Inventory Management (Doc 63 / SSoT 2026).

Kiểm thử toàn diện hệ thống Stash:
- Tính toán tọa độ và co giãn độ phân giải 1080p -> 1440p.
- Phân loại tên Tab Stash.
- Đánh giá quy tắc cất đồ (Deposit) và giữ lại (Keep) theo Pickit.
- Đánh giá quy tắc rút Waystone (Withdraw).
- Giả lập chu trình điều phối StashOrchestrator không cần game client.
"""

from unittest.mock import MagicMock, patch
import pytest
from PIL import Image

from src.assistant_tool.stash.stash_constants import (
    StashAction,
    StashConfig,
    StashTabType,
    TAB_NAME_MAPPINGS,
)
from src.assistant_tool.stash.stash_ui_scanner import StashTabInfo, StashUiScanner
from src.assistant_tool.stash.stash_item_matrix import StashItemMatrix, SlotItemInfo
from src.assistant_tool.stash.stash_evaluator import StashEvaluator
from src.assistant_tool.stash.stash_orchestrator import StashOrchestrator
from src.assistant_tool.item_inspector import (
    ItemCategory,
    ItemInspectionReport,
    ItemRarity,
)


def test_stash_coordinates_scaling():
    """Kiểm tra co giãn tọa độ tham chiếu 1080p sang 1440p (tỷ lệ 4/3)."""
    # Trên 1920x1080
    matrix_1080 = StashItemMatrix(screen_w=1920, screen_h=1080)
    inv_0_0_1080 = matrix_1080.get_inventory_slot_center(0, 0)
    assert inv_0_0_1080 == (1298, 616)

    stash_0_0_1080 = matrix_1080.get_stash_slot_center(0, 0)
    assert stash_0_0_1080 == (206, 216)

    # Trên 2560x1440 (scale 4/3)
    matrix_1440 = StashItemMatrix(screen_w=2560, screen_h=1440)
    inv_0_0_1440 = matrix_1440.get_inventory_slot_center(0, 0)
    assert inv_0_0_1440 == (1731, 822)

    stash_0_0_1440 = matrix_1440.get_stash_slot_center(0, 0)
    assert stash_0_0_1440 == (275, 288)


def test_tab_name_classification():
    """Kiểm tra nhận diện và phân loại tên Stash Tab."""
    scanner = StashUiScanner(screen_w=2560, screen_h=1440)
    assert scanner._classify_tab_name("CURRENCY") == StashTabType.CURRENCY
    assert scanner._classify_tab_name("Curr") == StashTabType.CURRENCY
    assert scanner._classify_tab_name("Waystones") == StashTabType.WAYSTONES
    assert scanner._classify_tab_name("T16 Maps") == StashTabType.WAYSTONES
    assert scanner._classify_tab_name("GEMS") == StashTabType.GEMS
    assert scanner._classify_tab_name("Uncut Gems") == StashTabType.GEMS
    assert scanner._classify_tab_name("FRAGMENTS") == StashTabType.FRAGMENTS
    assert scanner._classify_tab_name("Dump 1") == StashTabType.DUMP
    assert scanner._classify_tab_name("Random Tab") == StashTabType.GENERAL


def test_stash_evaluator_deposit_and_keep():
    """Kiểm tra logic phân luồng và ra quyết định cất đồ / giữ lại."""
    cfg = StashConfig(keep_waystones_count=1)
    evaluator = StashEvaluator(config=cfg)

    # 1. Scroll of Wisdom -> Luôn KEEP
    scroll = ItemInspectionReport(
        item_name="Scroll of Wisdom",
        base_type="Scroll of Wisdom",
        category=ItemCategory.CURRENCY,
    )
    d1 = evaluator.evaluate_inventory_item(scroll)
    assert d1.action == StashAction.KEEP

    # 2. Waystone đầu tiên -> KEEP (vì keep_waystones_count = 1)
    waystone1 = ItemInspectionReport(
        item_name="Waystone (Tier 15)",
        base_type="Waystone",
        category=ItemCategory.WAYSTONE,
    )
    d2 = evaluator.evaluate_inventory_item(waystone1, current_waystones_kept=0)
    assert d2.action == StashAction.KEEP
    assert d2.target_tab_type == StashTabType.WAYSTONES

    # 3. Waystone thứ hai -> DEPOSIT (vì cfg này keep_waystones_count = 1)
    d3 = evaluator.evaluate_inventory_item(waystone1, current_waystones_kept=1)
    assert d3.action == StashAction.DEPOSIT
    assert d3.target_tab_type == StashTabType.WAYSTONES

    # 3b. Kiểm tra với cấu hình mặc định (keep_waystones_count = 5)
    evaluator_5 = StashEvaluator(config=StashConfig(keep_waystones_count=5))
    for i in range(5):
        decision_keep = evaluator_5.evaluate_inventory_item(waystone1, current_waystones_kept=i)
        assert decision_keep.action == StashAction.KEEP, f"Waystone {i} phải được KEEP khi keep=5"
    decision_dep = evaluator_5.evaluate_inventory_item(waystone1, current_waystones_kept=5)
    assert decision_dep.action == StashAction.DEPOSIT, "Waystone thứ 6 phải được DEPOSIT khi keep=5"

    # 4. Currency (Chaos Orb) -> DEPOSIT
    chaos = ItemInspectionReport(
        item_name="Chaos Orb",
        base_type="Chaos Orb",
        category=ItemCategory.CURRENCY,
    )
    d4 = evaluator.evaluate_inventory_item(chaos)
    assert d4.action == StashAction.DEPOSIT
    assert d4.target_tab_type == StashTabType.CURRENCY

    # 5. Uncut Skill Gem -> DEPOSIT
    gem = ItemInspectionReport(
        item_name="Uncut Skill Gem (Tier 19)",
        base_type="Uncut Skill Gem",
        category=ItemCategory.OTHER,
    )
    d5 = evaluator.evaluate_inventory_item(gem)
    assert d5.action == StashAction.DEPOSIT
    assert d5.target_tab_type == StashTabType.GEMS

    # 6. Rare Gear (Quarterstaff) -> DEPOSIT vào DUMP
    gear = ItemInspectionReport(
        item_name="Spire of Torment",
        base_type="Gothic Quarterstaff",
        category=ItemCategory.WEAPON,
        rarity=ItemRarity.RARE,
    )
    d6 = evaluator.evaluate_inventory_item(gear)
    assert d6.action == StashAction.DEPOSIT
    assert d6.target_tab_type == StashTabType.DUMP


def test_stash_evaluator_withdraw():
    """Kiểm tra điều kiện rút Waystone bổ sung khi túi đồ thiếu."""
    cfg = StashConfig(keep_waystones_count=1)
    evaluator = StashEvaluator(config=cfg)

    # Túi có 0 Waystone -> Cần rút
    assert evaluator.should_withdraw_waystone(current_inventory_waystone_count=0) is True
    # Túi đã có 1 Waystone -> Không cần rút
    assert evaluator.should_withdraw_waystone(current_inventory_waystone_count=1) is False
    # Túi có 2 Waystone -> Không cần rút
    assert evaluator.should_withdraw_waystone(current_inventory_waystone_count=2) is False


def test_stash_ui_scanner_mock_ocr():
    """Kiểm tra quét Tab Bar bằng Mock WinOCR."""
    scanner = StashUiScanner(screen_w=2560, screen_h=1440)

    # Tạo ảnh giả lập
    fake_img = Image.new("RGB", (2560, 1440), color=(20, 20, 20))

    mock_ocr_result = {
        "lines": [
            {
                "text": "Currency",
                "words": [{"text": "Currency", "bounding_rect": {"x": 50, "y": 20, "width": 80, "height": 30}}],
            },
            {
                "text": "Waystones",
                "words": [{"text": "Waystones", "bounding_rect": {"x": 160, "y": 20, "width": 90, "height": 30}}],
            },
            {
                "text": "Gems",
                "words": [{"text": "Gems", "bounding_rect": {"x": 280, "y": 20, "width": 60, "height": 30}}],
            },
        ]
    }

    with patch("winocr.recognize_pil_sync", return_value=mock_ocr_result):
        tabs = scanner.scan_stash_tabs(fake_img)
        assert len(tabs) == 3
        assert tabs[0].name == "Currency"
        assert tabs[0].tab_type == StashTabType.CURRENCY
        assert tabs[1].name == "Waystones"
        assert tabs[1].tab_type == StashTabType.WAYSTONES
        assert tabs[2].name == "Gems"
        assert tabs[2].tab_type == StashTabType.GEMS

        # Kiểm tra tìm kiếm tab
        curr_tab = scanner.find_tab_by_type(StashTabType.CURRENCY)
        assert curr_tab is not None
        assert curr_tab.name == "Currency"

        way_tab = scanner.find_tab_by_name("Waystone")
        assert way_tab is not None
        assert way_tab.tab_type == StashTabType.WAYSTONES


def test_stash_orchestrator_mock_cycle():
    """Kiểm tra chu trình điều phối StashOrchestrator với mock input."""
    # Tạo fake image
    fake_img = Image.new("RGB", (2560, 1440), color=(10, 10, 10))

    cfg = StashConfig(background_input=False)
    orchestrator = StashOrchestrator(
        screen_w=2560,
        screen_h=1440,
        config=cfg,
        capture_func=lambda: fake_img,
    )
    orchestrator.hwnd = 0

    # Giả lập is_stash_open trả về True
    with patch.object(orchestrator.ui_scanner, "is_stash_open", return_value=True), \
         patch.object(orchestrator, "_ensure_focus", return_value=True), \
         patch("src.assistant_tool.stash.stash_orchestrator.click") as mock_click, \
         patch("src.assistant_tool.stash.stash_orchestrator.ctrl_click") as mock_ctrl_click, \
         patch.object(orchestrator.ui_scanner, "close_stash") as mock_close:

        # Giả lập 2 ô trong túi có đồ (ô 0: Currency, ô 1: Waystone)
        slot0 = SlotItemInfo(col=0, row=0, center_x=1731, center_y=822, is_occupied=True)
        slot1 = SlotItemInfo(col=1, row=0, center_x=1801, center_y=822, is_occupied=True)
        slot_empty = SlotItemInfo(col=2, row=0, center_x=1871, center_y=822, is_occupied=False)

        with patch.object(orchestrator.matrix, "scan_inventory_occupancy", return_value=[slot0, slot1, slot_empty]):
            # Chạy deposit (không inspect -> cất qua Stash Affinity)
            deposited = orchestrator.deposit_inventory_items(fake_img, inspect_items_live=False)
            # Cả 2 ô có đồ đều được Ctrl+Click cất vào Stash
            assert deposited == 2
            assert mock_ctrl_click.call_count == 2

        # Chạy close stash
        orchestrator.close_stash()
        assert mock_close.call_count == 1


def test_stash_orchestrator_background_input():
    """Kiểm tra chế độ tương tác ngầm PostMessageW trong StashOrchestrator."""
    fake_img = Image.new("RGB", (2560, 1440), color=(10, 10, 10))
    cfg = StashConfig(background_input=True)
    orchestrator = StashOrchestrator(
        screen_w=2560,
        screen_h=1440,
        config=cfg,
        capture_func=lambda: fake_img,
    )
    orchestrator.hwnd = 0x12345

    with patch("src.assistant_tool.stash.stash_orchestrator.post_message_ctrl_click") as mock_pm_ctrl, \
         patch("src.assistant_tool.stash.stash_orchestrator.post_message_key") as mock_pm_key:
        orchestrator._ctrl_click(100, 200)
        mock_pm_ctrl.assert_called_once_with(0x12345, 100, 200, hold_ms=0.04, delay_after=0.1)

        orchestrator.close_stash()
        mock_pm_key.assert_called_once()


def test_windowed_aspect_ratio_and_image_calibration():
    """Kiểm tra tính toán tọa độ lưới trong chế độ Windowed (4:3) và hiệu chuẩn từ ảnh."""
    # Windowed mode (1189x891 ~ aspect 1.33)
    matrix_win = StashItemMatrix(screen_w=1189, screen_h=891)
    slot_0_0 = matrix_win.get_inventory_slot_center(0, 0)
    # Tọa độ slot (0,0) phải nằm ở dải X ~ 660-675, Y ~ 535-545
    assert 650 <= slot_0_0[0] <= 680
    assert 530 <= slot_0_0[1] <= 550

    # Kiểm tra hiệu chuẩn từ mock image có nhãn INVENTORY
    fake_img = Image.new("RGB", (1189, 891), color=(10, 10, 10))
    with patch("src.common.vision_ocr.find_button_by_text", return_value=(914, 58)):
        calibrated = matrix_win.calibrate_from_image(fake_img)
        assert calibrated is True
        cal_0_0 = matrix_win.get_inventory_slot_center(0, 0)
        assert 650 <= cal_0_0[0] <= 675
        assert 530 <= cal_0_0[1] <= 550

