"""
Poe2VisualTool Game Path Resolver
Tự động định vị và quản lý đường dẫn thực thi của Path of Exile 2 (PathOfExile.exe / PathOfExileSteam.exe).
Cơ chế phân giải 4 tầng (4-Tier Resolution Architecture):
  - Tầng 1: Đọc tiến trình PathOfExile.exe đang chạy qua Win32 API / psutil.
  - Tầng 2: Đọc tệp cấu hình config.toml (mục [game] executable_path).
  - Tầng 3: Quét Registry Windows & Steam library (steamapps/libraryfolders.vdf) trên mọi ổ đĩa (C, D, E, F, G...).
  - Tầng 4: Hộp thoại GUI tương tác prompt_user_for_exe() qua tkinter.filedialog.
Lưu cache tự động vào config.toml khi tìm thấy đường dẫn hợp lệ.
Bối cảnh: 13/09/2026 (Rule 2)
"""

from __future__ import annotations

import os
import re
import string
import sys
from pathlib import Path
from typing import List, Optional, Tuple

import psutil


def parse_vdf_library_folders(vdf_content: str) -> List[str]:
    """
    Phân tích cú pháp tệp Valve Data Format (libraryfolders.vdf)
    để trích xuất danh sách tất cả các thư mục thư viện Steam trên máy.
    """
    paths: List[str] = []
    if not vdf_content:
        return paths

    # Tìm các mẫu: "path"  "D:\\SteamLibrary" hoặc "path"	"C:\\..."
    pattern = re.compile(r'"path"\s+"([^"]+)"', re.IGNORECASE)
    for match in pattern.finditer(vdf_content):
        raw_path = match.group(1).replace("\\\\", "\\")
        if raw_path and raw_path not in paths:
            paths.append(raw_path)
    return paths


class GamePathResolver:
    """
    Bộ phân giải đường dẫn game Path of Exile 2 theo 4 tầng linh hoạt.
    """

    ALLOWED_EXE_NAMES: Tuple[str, ...] = (
        "PathOfExile.exe",
        "PathOfExileSteam.exe",
        "PathOfExile_x64.exe",
        "PathOfExile_x64Steam.exe",
        "PathOfExileKG.exe",
        "PathOfExile_x64KG.exe",
    )

    COMMON_SUBPATHS: Tuple[str, ...] = (
        r"SteamLibrary\steamapps\common\Path of Exile 2",
        r"Steam\steamapps\common\Path of Exile 2",
        r"Program Files (x86)\Steam\steamapps\common\Path of Exile 2",
        r"Program Files\Steam\steamapps\common\Path of Exile 2",
        r"Games\Path of Exile 2",
        r"Games\SteamLibrary\steamapps\common\Path of Exile 2",
        r"Program Files\Grinding Gear Games\Path of Exile 2",
        r"Program Files (x86)\Grinding Gear Games\Path of Exile 2",
        r"Grinding Gear Games\Path of Exile 2",
        # Hỗ trợ cả thư mục Path of Exile (1 hoặc beta 2)
        r"SteamLibrary\steamapps\common\Path of Exile",
        r"Steam\steamapps\common\Path of Exile",
        r"Program Files (x86)\Steam\steamapps\common\Path of Exile",
        r"Games\Path of Exile",
        r"Grinding Gear Games\Path of Exile",
    )

    def __init__(self, config_path: Optional[Path | str] = None):
        if config_path is not None:
            self.config_path = Path(config_path).resolve()
        else:
            # Mặc định nằm tại gốc của repo Poe2VisualTool
            root = Path(__file__).resolve().parents[2]
            self.config_path = root / "config.toml"

    def is_valid_poe_exe(self, path: Path | str) -> bool:
        """Kiểm tra đường dẫn tệp có tồn tại và đúng tên file thực thi POE2 không."""
        try:
            p = Path(path).resolve()
            if not p.is_file():
                return False
            name_lower = p.name.lower()
            allowed_lower = [exe.lower() for exe in self.ALLOWED_EXE_NAMES]
            if name_lower in allowed_lower:
                return True
            if name_lower.startswith("pathofexile") and name_lower.endswith(".exe"):
                return True
        except Exception:
            return False
        return False

    def resolve_from_running_process(self) -> Optional[str]:
        """
        Tầng 1: Quét danh sách tiến trình đang hoạt động trên hệ thống.
        Độ trễ < 5ms, ưu tiên cao nhất khi người chơi đang mở game.
        """
        allowed_lower = [exe.lower() for exe in self.ALLOWED_EXE_NAMES]
        for proc in psutil.process_iter(["name", "exe", "pid"]):
            try:
                name = proc.info.get("name")
                if name and name.lower() in allowed_lower:
                    exe_path = proc.info.get("exe") or proc.exe()
                    if exe_path and self.is_valid_poe_exe(exe_path):
                        return str(Path(exe_path).resolve())
            except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
                continue
            except Exception:
                continue
        return None

    def resolve_from_config(self) -> Optional[str]:
        """
        Tầng 2: Đọc tệp cấu hình config.toml (mục [game] executable_path).
        """
        if not self.config_path.is_file():
            return None

        try:
            content = self.config_path.read_text(encoding="utf-8", errors="ignore")
            # Dùng regex bóc tách executable_path / client_exe / game_path để tương thích hoàn toàn
            m = re.search(r'(?:executable_path|client_exe|game_path|exe_path)\s*=\s*["\']([^"\']+)["\']', content)
            if m:
                configured_path = m.group(1).strip()
                if configured_path and self.is_valid_poe_exe(configured_path):
                    return str(Path(configured_path).resolve())
        except Exception:
            pass
        return None

    def resolve_from_system_and_steam(self) -> Optional[str]:
        """
        Tầng 3: Quét Registry Windows, thư viện Steam libraryfolders.vdf và các ổ đĩa.
        """
        # 3.1: Quét Registry Windows
        reg_path = self._scan_windows_registry()
        if reg_path:
            return reg_path

        # 3.2: Quét Steam libraryfolders.vdf
        steam_path = self._scan_steam_vdf()
        if steam_path:
            return steam_path

        # 3.3: Quét mọi ổ đĩa (C, D, E, F, G...)
        drive_path = self._scan_all_drives()
        if drive_path:
            return drive_path

        return None

    def _scan_windows_registry(self) -> Optional[str]:
        """Quét Registry Windows tìm đường dẫn cài đặt Path of Exile."""
        if os.name != "nt":
            return None

        try:
            import winreg

            # Danh sách khóa registry cài đặt POE / GGG
            registry_targets = [
                (winreg.HKEY_CURRENT_USER, r"Software\GrindingGearGames\Path of Exile 2", ["InstallFolder", "Path"]),
                (winreg.HKEY_CURRENT_USER, r"Software\GrindingGearGames\Path of Exile", ["InstallFolder", "Path"]),
                (winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\GrindingGearGames\Path of Exile 2", ["InstallFolder", "Path"]),
                (winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\WOW6432Node\GrindingGearGames\Path of Exile 2", ["InstallFolder", "Path"]),
            ]

            for root_key, sub_key, val_names in registry_targets:
                try:
                    with winreg.OpenKey(root_key, sub_key) as key:
                        for val_name in val_names:
                            try:
                                folder, _ = winreg.QueryValueEx(key, val_name)
                                if folder and os.path.isdir(folder):
                                    found = self._find_exe_in_dir(Path(folder))
                                    if found:
                                        return str(found)
                            except Exception:
                                pass
                except Exception:
                    pass

            # Quét khóa Uninstall của Windows
            uninstall_keys = [
                r"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall",
                r"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall",
            ]
            for u_sub in uninstall_keys:
                try:
                    with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, u_sub) as key:
                        num_subkeys, _, _ = winreg.QueryInfoKey(key)
                        for i in range(num_subkeys):
                            try:
                                subkey_name = winreg.EnumKey(key, i)
                                with winreg.OpenKey(key, subkey_name) as app_key:
                                    display_name, _ = winreg.QueryValueEx(app_key, "DisplayName")
                                    if "Path of Exile" in str(display_name):
                                        try:
                                            loc, _ = winreg.QueryValueEx(app_key, "InstallLocation")
                                            if loc and os.path.isdir(loc):
                                                found = self._find_exe_in_dir(Path(loc))
                                                if found:
                                                    return str(found)
                                        except Exception:
                                            pass
                            except Exception:
                                pass
                except Exception:
                    pass
        except Exception:
            pass

        return None

    def _scan_steam_vdf(self) -> Optional[str]:
        """Quét đường dẫn Steam và tệp libraryfolders.vdf."""
        steam_dirs: List[Path] = []

        # 1. Đọc Steam path từ Registry
        if os.name == "nt":
            try:
                import winreg
                with winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Software\Valve\Steam") as key:
                    steam_path, _ = winreg.QueryValueEx(key, "SteamPath")
                    if steam_path and os.path.isdir(steam_path):
                        steam_dirs.append(Path(steam_path))
            except Exception:
                pass

        # 2. Các vị trí cài Steam mặc định
        for d in self.get_available_drives():
            default_steam = Path(d) / "Program Files (x86)" / "Steam"
            if default_steam.is_dir() and default_steam not in steam_dirs:
                steam_dirs.append(default_steam)
            default_steam_64 = Path(d) / "Program Files" / "Steam"
            if default_steam_64.is_dir() and default_steam_64 not in steam_dirs:
                steam_dirs.append(default_steam_64)

        # 3. Đọc libraryfolders.vdf từ các thư mục Steam tìm thấy
        for s_dir in steam_dirs:
            vdf_file = s_dir / "steamapps" / "libraryfolders.vdf"
            if vdf_file.is_file():
                try:
                    content = vdf_file.read_text(encoding="utf-8", errors="ignore")
                    library_paths = parse_vdf_library_folders(content)
                    # Thêm chính thư mục s_dir
                    if str(s_dir) not in library_paths:
                        library_paths.insert(0, str(s_dir))

                    for lib in library_paths:
                        lib_path = Path(lib)
                        for candidate_name in ("Path of Exile 2", "Path of Exile"):
                            poe_folder = lib_path / "steamapps" / "common" / candidate_name
                            if poe_folder.is_dir():
                                found = self._find_exe_in_dir(poe_folder)
                                if found:
                                    return str(found)
                except Exception:
                    pass

        return None

    def _scan_all_drives(self) -> Optional[str]:
        """Quét danh mục thư mục cài đặt phổ biến trên mọi ổ đĩa."""
        drives = self.get_available_drives()
        for drive in drives:
            drive_root = Path(drive)
            for sub in self.COMMON_SUBPATHS:
                target_dir = drive_root / sub
                if target_dir.is_dir():
                    found = self._find_exe_in_dir(target_dir)
                    if found:
                        return str(found)
        return None

    def _find_exe_in_dir(self, directory: Path) -> Optional[Path]:
        """Tìm file thực thi POE2 hợp lệ trong một thư mục."""
        for exe_name in self.ALLOWED_EXE_NAMES:
            p = directory / exe_name
            if p.is_file():
                return p.resolve()
        return None

    def get_available_drives(self) -> List[str]:
        """Lấy danh sách các ổ đĩa hiện có trên hệ thống Windows."""
        drives: List[str] = []
        if os.name == "nt":
            try:
                import win32api
                raw_drives = win32api.GetLogicalDriveStrings()
                drives = [d.strip("\\") + "\\" for d in raw_drives.split("\000") if d.strip()]
            except Exception:
                pass
        if not drives:
            # Fallback kiểm tra các ký tự ổ đĩa A-Z
            for letter in string.ascii_uppercase:
                root_drive = f"{letter}:\\"
                if os.path.exists(root_drive):
                    drives.append(root_drive)
        if not drives:
            drives = ["C:\\"]
        return drives

    def prompt_user_for_exe(self, parent=None) -> Optional[str]:
        """
        Tầng 4: Hộp thoại GUI tương tác người dùng chọn file thực thi.
        Sử dụng tkinter.filedialog.askopenfilename() với tiêu đề tiếng Việt rõ ràng.
        """
        try:
            import tkinter as tk
            from tkinter import filedialog

            owns_tk = False
            root_window = parent
            if root_window is None:
                try:
                    root_window = tk.Tk()
                    root_window.withdraw()
                    owns_tk = True
                except Exception:
                    return None

            title = "Chọn tệp PathOfExile.exe hoặc PathOfExileSteam.exe..."
            filetypes = [
                ("Path of Exile Executable", "PathOfExile*.exe;*.exe"),
                ("All Files", "*.*"),
            ]

            chosen = filedialog.askopenfilename(
                parent=root_window,
                title=title,
                filetypes=filetypes,
            )

            if owns_tk:
                try:
                    root_window.destroy()
                except Exception:
                    pass

            if chosen and self.is_valid_poe_exe(chosen):
                norm_path = str(Path(chosen).resolve())
                self.save_game_path(norm_path)
                return norm_path
        except Exception:
            pass
        return None

    def save_game_path(self, path: str | Path) -> bool:
        """
        Lưu đường dẫn game hợp lệ vào tệp config.toml.
        Bảo toàn các thiết lập khác trong tệp cấu hình.
        """
        p = Path(path).resolve()
        if not self.is_valid_poe_exe(p):
            return False

        escaped_path = str(p).replace("\\", "\\\\")

        try:
            self.config_path.parent.mkdir(parents=True, exist_ok=True)
            if self.config_path.is_file():
                content = self.config_path.read_text(encoding="utf-8", errors="ignore")
                if "[game]" in content:
                    if re.search(r'executable_path\s*=', content):
                        new_content = re.sub(
                            r'executable_path\s*=\s*["\'][^"\']*["\']',
                            f'executable_path = "{escaped_path}"',
                            content,
                        )
                    else:
                        new_content = content.replace(
                            "[game]",
                            f'[game]\nexecutable_path = "{escaped_path}"',
                        )
                else:
                    new_content = f'[game]\nexecutable_path = "{escaped_path}"\n\n' + content
            else:
                new_content = (
                    "# Poe2VisualTool Configuration\n"
                    "# Bối cảnh: 13/09/2026 (Rule 2)\n\n"
                    "[game]\n"
                    f'executable_path = "{escaped_path}"\n'
                    'launch_arguments = ""\n\n'
                    "[visual]\n"
                    "zoom_cap = 8.0\n"
                    "enable_zoom = true\n"
                    "disable_fog = true\n"
                    "auto_reveal = true\n"
                    "wheel_drive = true\n\n"
                    "[inspector]\n"
                    "enabled = true\n"
                    "dwell_time_ms = 180\n"
                )

            self.config_path.write_text(new_content, encoding="utf-8")
            return True
        except Exception:
            return False

    def resolve(self, allow_prompt: bool = False, parent=None) -> Optional[str]:
        """
        Quy trình phân giải tự động 4 tầng:
        1. Tiến trình đang chạy
        2. Cấu hình config.toml
        3. Quét hệ thống & Steam
        4. Hỏi người dùng qua GUI (nếu allow_prompt=True)
        """
        # Tầng 1: Tiến trình đang chạy
        path = self.resolve_from_running_process()
        if path:
            return path

        # Tầng 2: Cấu hình config.toml
        path = self.resolve_from_config()
        if path:
            return path

        # Tầng 3: Quét Registry & Steam
        path = self.resolve_from_system_and_steam()
        if path:
            self.save_game_path(path)
            return path

        # Tầng 4: Hộp thoại GUI tương tác
        if allow_prompt:
            return self.prompt_user_for_exe(parent=parent)

        return None
