"""
Module Trích Xuất Snapshot RAM Đồng Bộ Cùng Timestamp (SyncMemorySnapshotManager)
Kết nối bộ nhớ game Path of Exile 2, bóc tách toàn bộ trạng thái nhân vật,
duyệt Entity Pointer Array để phát hiện quái vật và chiếu tọa độ 3D sang pixel màn hình.
"""

import ctypes
import math
import os
import struct
import time
from typing import Any, Dict, Optional, Tuple


class SyncMemorySnapshotManager:
    def __init__(self, pid: Optional[int] = None):
        self.pid = pid
        self.h_process = None
        self._init_process_handle()

    def _init_process_handle(self):
        """Mở handle tiến trình PathOfExile.exe nếu có quyền."""
        if self.h_process:
            return
        if not self.pid:
            import psutil
            for p in psutil.process_iter(['pid', 'name']):
                if p.info['name'] and 'pathofexile' in p.info['name'].lower():
                    self.pid = p.info['pid']
                    break

        if self.pid:
            PROCESS_VM_READ = 0x0010
            PROCESS_QUERY_INFORMATION = 0x0400
            PROCESS_QUERY_LIMITED_INFORMATION = 0x1000
            # Thử các quyền đọc bộ nhớ
            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, self.pid)
                if h:
                    self.h_process = h
                    break

    def close(self):
        """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):
        self.close()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()

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

        buf = ctypes.create_string_buffer(size)
        br = ctypes.c_size_t()
        if ctypes.windll.kernel32.ReadProcessMemory(
            self.h_process, ctypes.c_void_p(addr), buf, size, ctypes.byref(br)
        ) 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 read_f32(self, addr: int) -> Optional[float]:
        data = self.read_memory(addr, 4)
        return struct.unpack("<f", data)[0] if data else None

    @staticmethod
    def is_valid_xyz(x: float, y: float, z: float) -> bool:
        """Kiểm tra tọa độ có nằm trong dải hợp lý của game POE2 hay không."""
        if not (math.isfinite(x) and math.isfinite(y) and math.isfinite(z)):
            return False
        # Giới hạn không gian thực tế bản đồ POE2
        if abs(x) > 50000.0 or abs(y) > 50000.0 or abs(z) > 10000.0:
            return False
        # Loại bỏ uniform scale vector của shader/particle (9.3, 9.3, 9.3)
        if abs(x - y) < 0.01 and abs(y - z) < 0.01:
            return False
        # Loại bỏ vector rỗng
        if abs(x) < 0.1 and abs(y) < 0.1 and abs(z) < 0.1:
            return False
        return True

    def read_xyz(self, addr: int) -> Optional[Tuple[float, float, float]]:
        data = self.read_memory(addr, 12)
        if data:
            x, y, z = struct.unpack("<3f", data)
            if self.is_valid_xyz(x, y, z):
                return (x, y, z)
        return None

    def project_3d_to_screen(
        self,
        target_x: float,
        target_y: float,
        player_x: float,
        player_y: float,
        screen_w: int = 2560,
        screen_h: int = 1440
    ) -> Tuple[int, int]:
        """
        Chiếu tọa độ không gian bản đồ POE2 sang tọa độ điểm ảnh (Pixel) màn hình.
        Sử dụng hệ chiếu Isometric góc nhìn 45 độ chuẩn POE2.
        """
        dx = target_x - player_x
        dy = target_y - player_y
        dist = math.sqrt(dx * dx + dy * dy)

        cx = screen_w / 2.0
        cy = screen_h / 2.0

        if dist < 0.001:
            return (int(cx), int(cy))

        ndx = dx / dist
        ndy = dy / dist

        # Hệ số phóng đại pixel trên đơn vị bản đồ POE2
        # ~10 đơn vị bản đồ = ~140 pixels ở 1440p
        scale = 13.5
        proj_x = cx + (ndx - ndy) * dist * scale * 0.7071
        proj_y = cy - (ndx + ndy) * dist * scale * 0.4500

        # Giới hạn trong vùng màn hình
        proj_x = max(20, min(screen_w - 20, proj_x))
        proj_y = max(20, min(screen_h - 20, proj_y))

        return (int(proj_x), int(proj_y))

    def capture_ram_snapshot(
        self,
        fallback_telemetry: Optional[Dict[str, Any]] = None,
        screen_w: int = 2560,
        screen_h: int = 1440
    ) -> Dict[str, Any]:
        """
        Thực hiện một snapshot toàn diện bộ nhớ RAM tại thời điểm hiện tại.
        Trả về dictionary chi tiết về Player, XYZ, Entity Array và tọa độ chiếu màn hình.
        """
        now_ts = time.time()
        now_ms = int(now_ts * 1000)

        snapshot: Dict[str, Any] = {
            "timestamp_ms": now_ms,
            "timestamp_iso": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(now_ts)),
            "pid": self.pid,
            "memory_attached": bool(self.h_process),
            "player": {},
            "monsters": [],
            "items": [],
            "total_entities": 0,
            "diagnostics": {}
        }

        # 1. Đọc địa chỉ người chơi (PlayerAddr) từ telemetry hoặc cache
        player_addr = 0
        xyz_addr = 0
        
        if fallback_telemetry and "player_addr" in fallback_telemetry:
            player_addr = fallback_telemetry["player_addr"]

        offsets_file = os.path.join(os.path.dirname(__file__), "../../bin/Release/offsets.toml")
        if not os.path.exists(offsets_file):
            offsets_file = os.path.join(os.path.dirname(__file__), "../../bin/offsets.toml")

        if player_addr == 0 and os.path.exists(offsets_file):
            try:
                with open(offsets_file, "r") as f:
                    for line in f:
                        if "=" in line:
                            parts = line.split("=")
                            key = parts[0].strip().lower()
                            val = parts[1].split("#")[0].strip().strip('"\'')
                            if "player" in key and "addr" in key:
                                try:
                                    player_addr = int(val, 16) if val.startswith("0x") or val.startswith("0X") else int(val)
                                except ValueError:
                                    pass
            except Exception:
                pass

        # Đọc danh sách xyz_addrs nếu có file
        xyz_file = os.path.join(os.path.dirname(__file__), "../../bin/Release/xyz_addrs.txt")
        if os.path.exists(xyz_file):
            try:
                with open(xyz_file, "r") as f:
                    lines = [line.strip() for line in f if line.strip()]
                    if lines:
                        xyz_addr = int(lines[0], 16)
            except Exception:
                pass

        # 2. Đọc chỉ số người chơi (Mặc định 0, chỉ nhận dữ liệu thật từ RAM hoặc Telemetry)
        px, py, pz = 0.0, 0.0, 0.0
        hp, max_hp = 0, 0
        es, max_es = 0, 0
        mana, max_mana = 0, 0
        spirit, max_spirit = 0, 0

        if self.h_process and player_addr != 0:
            # Đọc HP
            p_hp = self.read_u32(player_addr)
            p_mhp = self.read_u32(player_addr + 4)
            if p_hp is not None and p_mhp is not None and 0 < p_mhp <= 50000:
                hp, max_hp = p_hp, p_mhp

            # Đọc ES
            p_es = self.read_u32(player_addr + 12)
            p_mes = self.read_u32(player_addr + 16)
            if p_es is not None and p_mes is not None and p_mes <= 5000:
                es, max_es = p_es, p_mes

            # Đọc XYZ
            if xyz_addr != 0:
                p_xyz = self.read_xyz(xyz_addr)
                if p_xyz and self.is_valid_xyz(*p_xyz):
                    px, py, pz = p_xyz

        # Trích xuất telemetry từ SharedMemory bridge (nếu có)
        if fallback_telemetry:
            try:
                # 1. Các trường số nguyên trực tiếp
                if "current_hp" in fallback_telemetry and fallback_telemetry["current_hp"] is not None:
                    hp = int(fallback_telemetry["current_hp"])
                if "max_hp" in fallback_telemetry and fallback_telemetry["max_hp"] is not None:
                    max_hp = int(fallback_telemetry["max_hp"])
                if "current_mana" in fallback_telemetry and fallback_telemetry["current_mana"] is not None:
                    mana = int(fallback_telemetry["current_mana"])
                if "max_mana" in fallback_telemetry and fallback_telemetry["max_mana"] is not None:
                    max_mana = int(fallback_telemetry["max_mana"])
                if "current_es" in fallback_telemetry and fallback_telemetry["current_es"] is not None:
                    es = int(fallback_telemetry["current_es"])
                if "max_es" in fallback_telemetry and fallback_telemetry["max_es"] is not None:
                    max_es = int(fallback_telemetry["max_es"])
                if "current_spirit" in fallback_telemetry and fallback_telemetry["current_spirit"] is not None:
                    spirit = int(fallback_telemetry["current_spirit"])
                if "max_spirit" in fallback_telemetry and fallback_telemetry["max_spirit"] is not None:
                    max_spirit = int(fallback_telemetry["max_spirit"])

                # 2. Phân tích từ các chuỗi nhãn giao diện
                hp_str = str(fallback_telemetry.get("hp", ""))
                if "/" in hp_str and max_hp == 0:
                    parts = hp_str.split("/")
                    hp = int(''.join(filter(str.isdigit, parts[0])))
                    max_hp = int(''.join(filter(str.isdigit, parts[1].split()[0])))

                mana_raw = str(fallback_telemetry.get("mana", ""))
                if "Mana:" in mana_raw:
                    try:
                        m_part = mana_raw.split("Mana:")[1].split("|")[0].split("(")[0]
                        if "/" in m_part:
                            m_sub = m_part.split("/")
                            mana = int(''.join(filter(str.isdigit, m_sub[0])))
                            max_mana = int(''.join(filter(str.isdigit, m_sub[1])))
                    except Exception:
                        pass
                elif "/" in mana_raw and max_mana == 0:
                    try:
                        parts = mana_raw.split("/")
                        mana = int(''.join(filter(str.isdigit, parts[0])))
                        max_mana = int(''.join(filter(str.isdigit, parts[1].split()[0])))
                    except Exception:
                        pass

                if "ES:" in mana_raw:
                    try:
                        es_part = mana_raw.split("ES:")[1].split("|")[0]
                        if "/" in es_part:
                            es_sub = es_part.split("/")
                            es = int(''.join(filter(str.isdigit, es_sub[0])))
                            max_es = int(''.join(filter(str.isdigit, es_sub[1])))
                    except Exception:
                        pass

                if "Spirit:" in mana_raw:
                    try:
                        sp_part = mana_raw.split("Spirit:")[1].split("|")[0]
                        if "/" in sp_part:
                            sp_sub = sp_part.split("/")
                            spirit = int(''.join(filter(str.isdigit, sp_sub[0])))
                            max_spirit = int(''.join(filter(str.isdigit, sp_sub[1])))
                    except Exception:
                        pass

                xyz_str = str(fallback_telemetry.get("xyz", ""))
                if "(" in xyz_str and ")" in xyz_str and (px == 0.0 and py == 0.0):
                    raw_coords = xyz_str.split("(")[1].split(")")[0].split(",")
                    cx = float(raw_coords[0].strip())
                    cy = float(raw_coords[1].strip())
                    cz = float(raw_coords[2].strip()) if len(raw_coords) > 2 else 0.0
                    if self.is_valid_xyz(cx, cy, cz):
                        px, py, pz = cx, cy, cz
            except Exception:
                pass

        snapshot["player"] = {
            "address": f"0x{player_addr:X}",
            "xyz_address": f"0x{xyz_addr:X}",
            "hp": hp,
            "max_hp": max_hp,
            "hp_percent": round((hp / max_hp * 100.0) if max_hp > 0 else 0, 1),
            "es": es,
            "max_es": max_es,
            "mana": mana,
            "max_mana": max_mana,
            "spirit": spirit,
            "max_spirit": max_spirit,
            "pos_x": round(px, 2),
            "pos_y": round(py, 2),
            "pos_z": round(pz, 2)
        }

        # 3. Duyệt Entity Pointer Array
        # Dựa trên memprobe: Array slot của player tại 0x50265410038, statOff = 0x3B8
        # Stride = 8 bytes. Quét 128 slot xung quanh
        monsters: list[dict[str, Any]] = []
        stat_offset = 0x3B8

        if self.h_process and player_addr != 0:
            base_array_slot = player_addr - 0x3B0
            player_pos_valid = self.is_valid_xyz(px, py, pz)
            for i in range(-64, 128):
                slot_addr = base_array_slot + i * 8
                ent_ptr = self.read_u64(slot_addr)
                if not ent_ptr or ent_ptr < 0x10000 or ent_ptr > 0x7FFFFFFEFFFF:
                    continue

                m_hp = self.read_u32(ent_ptr + stat_offset)
                m_max_hp = self.read_u32(ent_ptr + stat_offset + 4)

                # Kiểm tra xem có phải thực thể sống không
                if m_hp is not None and m_max_hp is not None and 0 < m_hp <= m_max_hp and m_max_hp <= 20000000:
                    if ent_ptr == (player_addr - stat_offset):
                        continue  # Bỏ qua chính người chơi

                    # Tìm tọa độ của quái vật trong các Component được trỏ tới từ Entity
                    mx, my, mz = None, None, None
                    for comp_off in (0x18, 0x20, 0x28, 0x30, 0x38, 0x40, 0x48, 0x50):
                        comp_ptr = self.read_u64(ent_ptr + comp_off)
                        if comp_ptr and 0x10000 <= comp_ptr <= 0x7FFFFFFEFFFF:
                            # Quét các offset float triple trong component
                            for pos_off in (0x28, 0x2C, 0x30, 0x38, 0x40, 0x44, 0x48):
                                cand_xyz = self.read_xyz(comp_ptr + pos_off)
                                if cand_xyz:
                                    cx, cy, cz = cand_xyz
                                    if player_pos_valid:
                                        if abs(cx - px) <= 1500.0 and abs(cy - py) <= 1500.0 and abs(cz - pz) <= 500.0:
                                            mx, my, mz = cx, cy, cz
                                            break
                                    else:
                                        mx, my, mz = cx, cy, cz
                                        break
                            if mx is not None:
                                break

                    # Tính cự ly và chiếu màn hình
                    dist = math.sqrt((mx - px) ** 2 + (my - py) ** 2) if (mx is not None and my is not None and player_pos_valid) else 50.0
                    screen_pos = self.project_3d_to_screen(
                        mx if mx is not None else (px + 30.0),
                        my if my is not None else (py + 20.0),
                        px, py, screen_w, screen_h
                    )

                    is_boss = (m_max_hp > 15000)
                    m_name = "Elite Boss" if is_boss else ("Rare Monster" if m_max_hp > 3000 else "Monster")

                    monsters.append({
                        "slot_index": i,
                        "entity_address": f"0x{ent_ptr:X}",
                        "name": m_name,
                        "hp": m_hp,
                        "max_hp": m_max_hp,
                        "hp_percent": round(m_hp / m_max_hp * 100.0, 1),
                        "distance": round(dist, 1),
                        "pos_x": round(mx, 2) if mx is not None else None,
                        "pos_y": round(my, 2) if my is not None else None,
                        "pos_z": round(mz, 2) if mz is not None else None,
                        "screen_x": screen_pos[0],
                        "screen_y": screen_pos[1]
                    })

        # Sắp xếp quái theo khoảng cách gần nhất
        monsters.sort(key=lambda m: m["distance"])
        snapshot["monsters"] = monsters
        snapshot["total_entities"] = len(monsters)

        snapshot["diagnostics"] = {
            "scan_window": f"Entity Pointer Array (slot 0x{player_addr - 0x3B0:X} stride 8)" if player_addr != 0 else "N/A (chưa có player_addr)",
            "stat_offset": f"0x{stat_offset:X}",
            "monsters_detected": len(monsters),
            "screen_resolution": f"{screen_w}x{screen_h}"
        }

        return snapshot

def draw_correlated_image(screenshot_path: str, ram_snapshot: Dict[str, Any], output_path: str) -> bool:
    """
    Vẽ lớp phủ tương quan (Correlation Overlay) lên ảnh chụp màn hình:
    - Bounding box và tâm ngắm vào từng quái vật phát hiện từ RAM
    - Thanh máu, cự ly và thông tin quái vật
    - Bảng thông số Telemetry người chơi
    """
    try:
        from PIL import Image, ImageDraw
        if not os.path.exists(screenshot_path):
            return False

        base_img = Image.open(screenshot_path).convert("RGBA")
        w, h = base_img.size

        overlay = Image.new("RGBA", (w, h), (0, 0, 0, 0))
        draw = ImageDraw.Draw(overlay)

        # 1. Vẽ HUD bảng thông số tổng quan ở góc trên bên trái
        p = ram_snapshot.get("player", {})
        mobs = ram_snapshot.get("monsters", [])

        hud_box = [20, 20, 520, 195]
        draw.rectangle(hud_box, fill=(15, 23, 42, 230), outline=(56, 189, 248, 255), width=2)

        # Tiêu đề
        ts_str = ram_snapshot.get("timestamp_iso", "")
        draw.text((35, 30), "⚡ POE2 SYNC TELEMETRY & RAM SNAPSHOT", fill=(56, 189, 248))
        draw.text((35, 52), f"Thời gian: {ts_str}", fill=(148, 163, 184))

        # Player stats
        hp_text = f"Máu: {p.get('hp', 0)} / {p.get('max_hp', 0)} ({p.get('hp_percent', 0)}%) | ES: {p.get('es', 0)}/{p.get('max_es', 0)}"
        draw.text((35, 75), hp_text, fill=(239, 68, 68) if p.get('hp_percent', 100) < 40 else (74, 222, 128))

        mana_text = f"Mana: {p.get('mana', 0)} / {p.get('max_mana', 0)} | Spirit: {p.get('spirit', 0)} / {p.get('max_spirit', 0)}"
        draw.text((35, 98), mana_text, fill=(96, 165, 250))

        xyz_text = f"Tọa độ XYZ: ({p.get('pos_x', 0)}, {p.get('pos_y', 0)}, {p.get('pos_z', 0)})"
        draw.text((35, 121), xyz_text, fill=(250, 204, 21))

        mobs_text = f"🎯 Quái vật trong RAM: {len(mobs)} con | Radar Core: {'ĐỒNG BỘ' if len(mobs) > 0 else 'CHƯA KHÓA'}"
        draw.text((35, 144), mobs_text, fill=(244, 114, 182) if len(mobs) > 0 else (248, 113, 113))

        # 2. Vẽ vị trí tâm nhân vật (Screen Center)
        cx, cy = w // 2, h // 2
        draw.ellipse([cx - 8, cy - 8, cx + 8, cy + 8], outline=(74, 222, 128, 255), width=2)
        draw.text((cx - 24, cy + 12), "PLAYER", fill=(74, 222, 128, 255))

        # 3. Vẽ từng quái vật phát hiện từ RAM
        for idx, mob in enumerate(mobs[:15]):  # Vẽ tối đa 15 quái gần nhất
            sx = mob.get("screen_x", cx)
            sy = mob.get("screen_y", cy)
            dist = mob.get("distance", 0)
            m_hp = mob.get("hp", 0)
            m_mhp = mob.get("max_hp", 0)
            name = mob.get("name", "Monster")
            is_boss = (m_mhp > 15000)

            # Màu sắc: Đỏ cho Boss, Vàng cho Rare, Cam cho Thường
            color = (239, 68, 68, 255) if is_boss else ((250, 204, 21, 255) if m_mhp > 3000 else (249, 115, 22, 255))
            fill_color = (239, 68, 68, 50) if is_boss else (250, 204, 21, 30)

            # Vẽ đường nối từ nhân vật tới quái
            draw.line([(cx, cy), (sx, sy)], fill=(color[0], color[1], color[2], 120), width=1)

            # Khung chữ nhật bao quanh quái
            box_r = 35 if is_boss else 24
            draw.rectangle([sx - box_r, sy - box_r, sx + box_r, sy + box_r], outline=color, fill=fill_color, width=2)

            # Tâm ngắm
            draw.line([(sx - 8, sy), (sx + 8, sy)], fill=color, width=1)
            draw.line([(sx, sy - 8), (sx, sy + 8)], fill=color, width=1)

            # Nhãn thông tin quái
            label = f"[{idx+1}] {name} (cự ly {dist:.0f})"
            hp_label = f"HP: {m_hp}/{m_mhp}"
            draw.rectangle([sx - 60, sy - box_r - 28, sx + 60, sy - box_r - 4], fill=(15, 23, 42, 200), outline=color, width=1)
            draw.text((sx - 55, sy - box_r - 26), label, fill=color)
            draw.text((sx - 55, sy - box_r - 14), hp_label, fill=(255, 255, 255))

        # Ghép hai lớp ảnh
        final_img = Image.alpha_composite(base_img, overlay).convert("RGB")
        final_img.save(output_path, "PNG")
        return True
    except Exception as e:
        print(f"[draw_correlated_image] Lỗi: {e}")
        return False

