"""AutoPOE2 - Stash & Inventory Memory Reader (Doc 63 / SSoT 2026).

Bộ đọc và đồng bộ trạng thái túi đồ / hòm đồ qua bộ nhớ RAM tiến trình POE2:
- Kết nối tiến trình game qua Windows API ReadProcessMemory (100% User Mode, Zero-Write).
- Quản lý bộ nhớ đệm In-Memory Slot Cache truy xuất tức thời O(1).
- Cơ chế Fail-Soft: Tự động suy thoái mượt mà sang Optical/Clipboard nếu memory shift.
- Đồng bộ đa tầng giữa RAM, Optical Scanner và Clipboard Ground-Truth.
"""

from __future__ import annotations

import ctypes
import os
import struct
import time
from dataclasses import dataclass
from typing import Any, Dict, List, Optional, Tuple


@dataclass
class SlotMemoryData:
    """Dữ liệu ô đồ được lưu trữ và đồng bộ trong RAM Cache."""
    col: int
    row: int
    is_occupied: bool = False
    item_name: str = ""
    category: str = "OTHER"
    tier: Optional[int] = None
    quantity: int = 1
    last_updated_ms: int = 0


class StashMemoryReader:
    """Bộ đọc bộ nhớ RAM tiến trình POE2 cho túi đồ và hòm đồ."""

    def __init__(self, pid: Optional[int] = None):
        self.pid = pid
        self.h_process: Optional[int] = None
        self._cache: Dict[Tuple[int, int], SlotMemoryData] = {}
        self._init_cache()
        if self.pid:
            self.attach(self.pid)

    def _init_cache(self) -> None:
        """Khởi tạo lưới 60 ô túi đồ cá nhân (12 cột x 5 hàng)."""
        now_ms = int(time.time() * 1000)
        for r in range(5):
            for c in range(12):
                self._cache[(c, r)] = SlotMemoryData(
                    col=c,
                    row=r,
                    is_occupied=False,
                    last_updated_ms=now_ms,
                )

    def attach(self, pid: int) -> bool:
        """Kết nối tới tiến trình game với quyền đọc bộ nhớ an toàn (PROCESS_VM_READ)."""
        self.detach()
        self.pid = pid

        PROCESS_VM_READ = 0x0010
        PROCESS_QUERY_INFORMATION = 0x0400
        PROCESS_QUERY_LIMITED_INFORMATION = 0x1000

        for flags in (
            PROCESS_VM_READ | PROCESS_QUERY_INFORMATION,
            PROCESS_VM_READ | PROCESS_QUERY_LIMITED_INFORMATION,
            PROCESS_VM_READ,
        ):
            h = ctypes.windll.kernel32.OpenProcess(flags, False, pid)
            if h:
                self.h_process = h
                return True

        self.h_process = None
        return False

    def detach(self) -> None:
        """Giải phóng handle tiến trình Windows chống rò rỉ tài nguyên."""
        if self.h_process:
            try:
                ctypes.windll.kernel32.CloseHandle(self.h_process)
            except Exception:
                pass
            self.h_process = None

    def __del__(self) -> None:
        self.detach()

    def is_attached(self) -> bool:
        """Kiểm tra xem đã kết nối tới bộ nhớ game hay chưa."""
        return self.h_process is not None

    def read_memory(self, addr: int, size: int) -> Optional[bytes]:
        """Đọc chuỗi bytes từ địa chỉ bộ nhớ game."""
        if not self.h_process or addr < 0x10000 or addr > 0x7FFFFFFEFFFF:
            return None

        buf = ctypes.create_string_buffer(size)
        br = ctypes.c_size_t()
        ok = ctypes.windll.kernel32.ReadProcessMemory(
            self.h_process,
            ctypes.c_void_p(addr),
            buf,
            size,
            ctypes.byref(br),
        )
        if ok and br.value == size:
            return buf.raw
        return None

    def read_u32(self, addr: int) -> Optional[int]:
        data = self.read_memory(addr, 4)
        return struct.unpack("<I", data)[0] if data else None

    def read_u64(self, addr: int) -> Optional[int]:
        data = self.read_memory(addr, 8)
        return struct.unpack("<Q", data)[0] if data else None

    def get_slot_data(self, col: int, row: int) -> SlotMemoryData:
        """Truy xuất dữ liệu ô đồ tức thời O(1) từ In-Memory Cache."""
        key = (col, row)
        if key not in self._cache:
            self._cache[key] = SlotMemoryData(col=col, row=row)
        return self._cache[key]

    def update_slot(
        self,
        col: int,
        row: int,
        is_occupied: bool,
        item_name: str = "",
        category: str = "OTHER",
        tier: Optional[int] = None,
        quantity: int = 1,
    ) -> SlotMemoryData:
        """Cập nhật dữ liệu một ô đồ trong RAM Cache."""
        key = (col, row)
        now_ms = int(time.time() * 1000)
        data = SlotMemoryData(
            col=col,
            row=row,
            is_occupied=is_occupied,
            item_name=item_name,
            category=category,
            tier=tier,
            quantity=quantity,
            last_updated_ms=now_ms,
        )
        self._cache[key] = data
        return data

    def sync_with_optical_matrix(self, optical_slots: List[Any]) -> None:
        """
        Đồng bộ hai chiều giữa kết quả quét quang học và RAM Cache:
        - Nếu quang học phát hiện ô đã trống (sau khi cất đồ) -> Cập nhật RAM Cache thành rỗng.
        - Nếu quang học phát hiện ô có đồ mới -> Đánh dấu occupied để sẵn sàng phân loại.
        """
        now_ms = int(time.time() * 1000)
        for s in optical_slots:
            col = getattr(s, "col", None)
            row = getattr(s, "row", None)
            is_occ = getattr(s, "is_occupied", False)
            if col is not None and row is not None:
                cached = self.get_slot_data(col, row)
                if not is_occ:
                    # Ô đã trống
                    cached.is_occupied = False
                    cached.item_name = ""
                    cached.tier = None
                    cached.last_updated_ms = now_ms
                elif not cached.is_occupied:
                    # Ô mới có đồ nhưng chưa inspect
                    cached.is_occupied = True
                    cached.last_updated_ms = now_ms

    def get_all_occupied_slots(self) -> List[SlotMemoryData]:
        """Lấy danh sách tất cả các ô đồ hiện đang có vật phẩm trong túi."""
        return [s for s in self._cache.values() if s.is_occupied]

    def get_waystones_count(self) -> int:
        """Đếm số lượng Waystone hiện đang được xác nhận có trong túi đồ."""
        count = 0
        for s in self._cache.values():
            if s.is_occupied and (s.category == "WAYSTONE" or "Waystone" in s.item_name):
                count += 1
        return count

    def get_waystone_slots(self) -> List[SlotMemoryData]:
        """Lấy danh sách các ô chứa Waystone đã được nhận diện."""
        return [
            s for s in self._cache.values()
            if s.is_occupied and (s.category == "WAYSTONE" or "Waystone" in s.item_name)
        ]
