"""AutoPOE2 - Stash Orchestrator (Doc 63 / SSoT 2026).

Bộ điều phối tối cao chu trình tương tác Hòm đồ (Stash & Inventory):
- Mở Stash bằng click LMB tuyệt đối tại Hideout (INV-KEY-NO-F-FOR-INTERACT).
- Xác minh trạng thái mở của Stash UI và quét danh mục Tabs.
- Cất đồ tự động (Auto-Stash) qua Ctrl+Click có jitter tự nhiên (INV-STASH-CTRL-CLICK).
- Rút bổ sung Waystone (Auto-Withdraw) khi túi đồ thiếu thốn.
- Đóng Stash an toàn bằng phím Spacebar chuẩn người chơi.
"""

from __future__ import annotations

import os
import random
import time
from datetime import datetime
from typing import Any, Callable, Dict, List, Optional, Tuple, Union

try:
    from PIL import Image
except ImportError:
    Image = None  # type: ignore[assignment]

from src.assistant_tool.stash.stash_constants import (
    StashAction,
    StashConfig,
    StashTabType,
)
from src.assistant_tool.stash.stash_memory_reader import StashMemoryReader
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.common.win32_input import (
    VK_SPACE,
    click,
    ctrl_click,
    dismiss_modals,
    post_message_click,
    post_message_ctrl_click,
    post_message_key,
)
from src.common.win32_window import find_poe2_window, ensure_window_focus, get_client_rect
from src.common.vision_ocr import find_button_by_text


class StashOrchestrator:
    """Máy trạng thái điều phối chu trình Stash & Restock tự hành."""

    def __init__(
        self,
        screen_w: Optional[int] = None,
        screen_h: Optional[int] = None,
        config: Optional[StashConfig] = None,
        capture_func: Optional[Callable[[], Optional[Image.Image]]] = None,
        memory_reader: Optional[StashMemoryReader] = None,
        is_simulated: bool = False,
    ):
        self.config = config or StashConfig()
        self.is_simulated = is_simulated
        self.hwnd = find_poe2_window()
        self.client_left = 0
        self.client_top = 0

        # Tự động truy vấn kích thước thật từ client rect nếu không chỉ định rõ
        if self.hwnd:
            cl, ct, cw, ch = get_client_rect(self.hwnd)
            if cw > 0 and ch > 0:
                self.client_left = cl
                self.client_top = ct
                if screen_w is None:
                    screen_w = cw
                if screen_h is None:
                    screen_h = ch

        self.screen_w = screen_w or 2560
        self.screen_h = screen_h or 1440
        self.capture_func = capture_func
        self.memory_reader = memory_reader

        self.ui_scanner = StashUiScanner(
            screen_w=self.screen_w,
            screen_h=self.screen_h,
            click_func=lambda x, y: self._click(x, y, button="left", hold_ms=0.04, delay_after=0.25),
        )
        self.matrix = StashItemMatrix(
            screen_w=self.screen_w,
            screen_h=self.screen_h,
            config=self.config,
            memory_reader=self.memory_reader,
            is_simulated=self.is_simulated,
        )
        self.evaluator = StashEvaluator(config=self.config)

    def _click(self, x: int, y: int, button: str = "left", hold_ms: float = 0.04, delay_after: float = 0.1) -> None:
        """Click chuột với cơ chế tự động chuyển đổi giữa Background (PostMessageW) và Foreground.
        Tuân thủ INV-FOCUS-SAFETY-SEPARATION: trong mock/simulated cấm gửi Win32 hardware event.
        """
        if self.is_simulated:
            click(self.client_left + x, self.client_top + y, button=button, hold_ms=hold_ms, delay_after=delay_after, is_simulated=True, hwnd=self.hwnd)
            return
        if self.config.background_input and self.hwnd:
            post_message_click(self.hwnd, x, y, button=button, hold_ms=hold_ms, delay_after=delay_after)
        else:
            click(self.client_left + x, self.client_top + y, button=button, hold_ms=hold_ms, delay_after=delay_after, hwnd=self.hwnd)

    def _ctrl_click(self, x: int, y: int, hold_ms: float = 0.04, delay_after: float = 0.1) -> None:
        """Ctrl+Click với cơ chế tự động chuyển đổi giữa Background (PostMessageW) và Foreground.
        Tuân thủ INV-FOCUS-SAFETY-SEPARATION: trong mock/simulated cấm gửi Win32 hardware event.
        """
        if self.is_simulated:
            ctrl_click(self.client_left + x, self.client_top + y, hold_ms=hold_ms, delay_after=delay_after, is_simulated=True, hwnd=self.hwnd)
            return
        if self.config.background_input and self.hwnd:
            post_message_ctrl_click(self.hwnd, x, y, hold_ms=hold_ms, delay_after=delay_after)
        else:
            ctrl_click(self.client_left + x, self.client_top + y, hold_ms=hold_ms, delay_after=delay_after, hwnd=self.hwnd)

    def _get_current_screen(self) -> Optional[Image.Image]:
        if self.capture_func:
            return self.capture_func()
        return None

    def _ensure_focus(self) -> bool:
        if self.is_simulated:
            return True
        # Nếu ở chế độ background_input, tuyệt đối không cướp focus của người dùng
        if self.config.background_input:
            return bool(self.hwnd)
        if self.hwnd:
            return ensure_window_focus(self.hwnd, timeout_ms=500)
        return True

    def open_stash(
        self,
        img_current: Optional[Image.Image] = None,
        timeout_sec: float = 3.0,
    ) -> bool:
        """
        Tìm và mở hòm đồ Stash tại Hideout.
        Tuân thủ INV-KEY-NO-F-FOR-INTERACT: Click LMB có chủ đích, tuyệt đối cấm phím F.
        """
        self._ensure_focus()

        img = img_current or self._get_current_screen()
        # 1. Kiểm tra xem Stash đã mở sẵn chưa
        if img and self.ui_scanner.is_stash_open(img):
            return True

        # 2. Định vị tọa độ Stash trên màn hình
        scale_x = self.screen_w / 2560.0
        scale_y = self.screen_h / 1440.0
        stash_x = int(self.config.default_stash_screen_x * scale_x)
        stash_y = int(self.config.default_stash_screen_y * scale_y)

        play_area_crop = (
            int(100 * scale_x),
            int(100 * scale_y),
            int(1900 * scale_x),
            int(1300 * scale_y),
        )

        if img:
            # Tìm nhãn chữ 'STASH' qua OCR trong vùng chơi (loại trừ minimap)
            found_pos = find_button_by_text(img, "STASH", crop_box=play_area_crop)
            if found_pos:
                stash_x, stash_y = found_pos

        # 3. Khóa tiếp cận WASD (INV-WASD-NO-BLIND-LMB):
        # Nếu nhân vật cách xa Stash > 200px, dùng WASD tiếp cận trước
        center_x = self.screen_w // 2
        center_y = int(540 * (self.screen_h / 1080.0))
        screen_dx = stash_x - center_x
        screen_dy = stash_y - center_y
        dist = (screen_dx * screen_dx + screen_dy * screen_dy) ** 0.5

        if dist > 200:
            dwell = min(0.8, max(0.35, dist / 800.0))
            from src.common.win32_input import send_wasd_direction
            # Chuyển đổi từ tọa độ pixel màn hình sang vectơ thế giới WASD Isometric
            target_sx = screen_dx
            target_sy = -screen_dy
            w_dx = target_sx + target_sy
            w_dy = target_sy - target_sx
            send_wasd_direction(w_dx, w_dy, dwell_ms=dwell, is_simulated=self.is_simulated)
            time.sleep(0.3)

            # Chụp lại ảnh mới sau khi di chuyển để cập nhật tọa độ Stash
            fresh_img = self._get_current_screen()
            if fresh_img:
                refound = find_button_by_text(fresh_img, "STASH", crop_box=play_area_crop)
                if refound:
                    stash_x, stash_y = refound

        # 4. Click LMB mở Stash có chủ đích
        self._click(stash_x, stash_y, button="left", hold_ms=0.05, delay_after=0.3)

        # 5. Chờ xác minh Stash UI đã mở thực sự
        start_t = time.time()
        while time.time() - start_t < timeout_sec:
            time.sleep(0.25)
            verify_img = self._get_current_screen()
            if verify_img and self.ui_scanner.is_stash_open(verify_img):
                return True

        return False

    def scan_tabs(self, img: Optional[Image.Image] = None) -> List[StashTabInfo]:
        """Quét và nhận diện danh sách các Tab Stash hiện có."""
        cur_img = img or self._get_current_screen()
        if cur_img:
            return self.ui_scanner.scan_stash_tabs(cur_img)
        return []

    def deposit_inventory_items(
        self,
        img: Optional[Image.Image] = None,
        inspect_items_live: bool = False,
    ) -> int:
        """
        Quét các ô trong túi đồ và cất các vật phẩm phù hợp vào Stash.
        Tuân thủ INV-STASH-CTRL-CLICK: dùng Ctrl + Click kèm jitter 180ms +- 40ms.
        """
        self._ensure_focus()
        cur_img = img or self._get_current_screen()
        if not cur_img:
            return 0

        # Quét trạng thái chiếm dụng của 60 ô túi đồ
        slots = self.matrix.scan_inventory_occupancy(cur_img)
        occupied_slots = [s for s in slots if s.is_occupied]

        transferred_count = 0
        waystones_kept = 0

        for slot in occupied_slots:
            report = None
            if inspect_items_live:
                report = self.matrix.inspect_slot_item_live(
                    slot.center_x,
                    slot.center_y,
                    col=slot.col,
                    row=slot.row,
                )

            # Đánh giá hành động cất/giữ
            decision = self.evaluator.evaluate_inventory_item(
                report,
                current_waystones_kept=waystones_kept,
            )

            if decision.action == StashAction.KEEP:
                if decision.target_tab_type == StashTabType.WAYSTONES:
                    waystones_kept += 1
                continue

            if decision.action == StashAction.DEPOSIT:
                # Nếu không dùng Stash Affinity, chuyển sang đúng tab trước
                if not self.config.use_stash_affinity:
                    self.ui_scanner.switch_to_tab(decision.target_tab_type)

                # Thêm jitter mô phỏng con người
                jitter = random.randint(-self.config.jitter_ms, self.config.jitter_ms)
                delay = max(0.12, (self.config.delay_click_ms + jitter) / 1000.0)

                self._ctrl_click(slot.center_x, slot.center_y, hold_ms=0.04, delay_after=delay)
                transferred_count += 1

                # Cập nhật trạng thái ô rỗng trong RAM Memory Cache
                if self.matrix.memory_reader:
                    self.matrix.memory_reader.update_slot(slot.col, slot.row, is_occupied=False)

        return transferred_count

    def withdraw_waystone_if_needed(
        self,
        img: Optional[Image.Image] = None,
        current_waystones_in_bag: int = 0,
    ) -> bool:
        """
        Kiểm tra và rút đủ số lượng Waystone còn thiếu từ Tab Waystones/Map (SSoT keep_waystones_count).
        """
        # Nếu chưa cung cấp số lượng, thử kiểm tra trong RAM Memory Cache
        if current_waystones_in_bag == 0 and self.matrix.memory_reader:
            cached_count = self.matrix.memory_reader.get_waystones_count()
            if cached_count > 0:
                current_waystones_in_bag = cached_count

        needed = self.config.keep_waystones_count - current_waystones_in_bag
        if needed <= 0:
            return True

        self._ensure_focus()
        # Chuyển sang Tab Waystones (thử WAYSTONES, 'Map', 'Waystone')
        tab_switched = self.ui_scanner.switch_to_tab(StashTabType.WAYSTONES)
        if not tab_switched:
            tab_switched = self.ui_scanner.switch_to_tab("Map")
        if not tab_switched:
            tab_switched = self.ui_scanner.switch_to_tab("Waystone")

        time.sleep(0.4)
        cur_img = self._get_current_screen()
        if not cur_img:
            return False

        # 1. Thử kiểm tra Dedicated Map Stash Tab (bấm nút Tier XV để nạp Waystones T15)
        scale_x = self.screen_w / 2560.0
        scale_y = self.screen_h / 1440.0
        tier15_btn_x = int(678 * scale_x)
        tier15_btn_y = int(325 * scale_y)

        self._click(tier15_btn_x, tier15_btn_y, button="left", hold_ms=0.04, delay_after=0.3)
        tier_img = self._get_current_screen() or cur_img

        # Quét lưới subgrid của Map Tab: 8 cột x 9 hàng bắt đầu từ (72, 490) trên 1440p
        subgrid_origin_x = int(72 * scale_x)
        subgrid_origin_y = int(490 * scale_y)
        subgrid_step_x = int(52 * scale_x)
        subgrid_step_y = int(52 * scale_y)

        map_tab_slots: List[Tuple[int, int]] = []
        for r in range(9):
            for c in range(8):
                cx = subgrid_origin_x + c * subgrid_step_x
                cy = subgrid_origin_y + r * subgrid_step_y
                total_b = 0
                samples = 0
                for dx in range(-4, 5, 2):
                    for dy in range(-4, 5, 2):
                        px = cx + dx
                        py = cy + dy
                        if 0 <= px < tier_img.width and 0 <= py < tier_img.height:
                            rgb = tier_img.getpixel((px, py))
                            total_b += (rgb[0] + rgb[1] + rgb[2]) / 3.0
                            samples += 1
                avg_b = (total_b / samples) if samples > 0 else 0.0
                if avg_b > self.config.brightness_threshold_waystone:
                    map_tab_slots.append((cx, cy))

        # Nếu tìm thấy Waystones trong Dedicated Map Tab
        if map_tab_slots:
            count_to_withdraw = min(needed, len(map_tab_slots))
            for i in range(count_to_withdraw):
                tx, ty = map_tab_slots[i]
                jitter = random.randint(-self.config.jitter_ms, self.config.jitter_ms)
                delay = max(0.12, (self.config.delay_click_ms + jitter) / 1000.0)
                self._ctrl_click(tx, ty, hold_ms=0.04, delay_after=delay)
            return True

        # 2. Fallback: Nếu là Tab thường / Quad Tab, quét lưới 12x12
        stash_slots = self.matrix.scan_stash_occupancy(cur_img)
        occupied_slots = [s for s in stash_slots if s.is_occupied]

        if not occupied_slots:
            return False

        # Rút đủ số lượng Waystone còn thiếu
        count_to_withdraw = min(needed, len(occupied_slots))
        for i in range(count_to_withdraw):
            target_slot = occupied_slots[i]
            jitter = random.randint(-self.config.jitter_ms, self.config.jitter_ms)
            delay = max(0.12, (self.config.delay_click_ms + jitter) / 1000.0)
            self._ctrl_click(target_slot.center_x, target_slot.center_y, hold_ms=0.04, delay_after=delay)
        return True

    def close_stash(self) -> None:
        """Đóng bảng hòm đồ an toàn bằng phím Spacebar."""
        if self.is_simulated:
            dismiss_modals(count=1, delay_after=0.1, is_simulated=True)
            return
        self._ensure_focus()
        if self.config.background_input and self.hwnd:
            post_message_key(self.hwnd, VK_SPACE, dwell_ms=0.05, delay_after=0.2)
        else:
            self.ui_scanner.close_stash(delay_after=0.2)

    def run_full_stash_cycle(
        self,
        inspect_items_live: bool = False,
    ) -> Dict[str, Any]:
        """
        Chạy toàn bộ chu trình Stash khép kín:
        Mở Stash -> Quét Tabs -> Cất đồ -> Rút Waystone -> Đóng Stash.
        """
        outcome = {
            "success": False,
            "stash_opened": False,
            "tabs_detected": 0,
            "items_deposited": 0,
            "waystone_withdrawn": False,
            "stash_closed": False,
            "error": "",
        }

        # 1. Mở Stash
        opened = self.open_stash()
        outcome["stash_opened"] = opened
        if not opened:
            outcome["error"] = "Khong the mo giao dien Stash tai Hideout"
            return outcome

        # 2. Quét Tabs
        tabs = self.scan_tabs()
        outcome["tabs_detected"] = len(tabs)

        # 3. Cất đồ
        deposited = self.deposit_inventory_items(inspect_items_live=inspect_items_live)
        outcome["items_deposited"] = deposited

        # 4. Rút Waystone nếu cần
        withdrawn = self.withdraw_waystone_if_needed()
        outcome["waystone_withdrawn"] = withdrawn

        # 5. Đóng Stash
        self.close_stash()
        outcome["stash_closed"] = True
        outcome["success"] = True
        return outcome
