"""
Poe2VisualTool Game Path Resolver Unit Tests
Kiểm thử toàn diện 4 tầng phân giải đường dẫn game Path of Exile 2:
  - Tầng 1: Đọc tiến trình PathOfExile.exe đang chạy qua psutil/win32
  - Tầng 2: Đọc cấu hình config.toml
  - Tầng 3: Quét Registry Windows & Steam library (libraryfolders.vdf) trên mọi ổ đĩa
  - Tầng 4: Hộp thoại GUI tương tác prompt_user_for_exe()
Kiểm thử lưu cache tự động (save_game_path) và xác thực tính toàn vẹn của tệp cấu hình.
Bối cảnh: 13/09/2026 (Rule 2)
"""

from __future__ import annotations

import os
import sys
from pathlib import Path
from unittest.mock import MagicMock, patch

import pytest

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))

from src.assistant.game_path_resolver import GamePathResolver, parse_vdf_library_folders


# ==============================================================================
# SAMPLE DATA CHO STEAM VDF VÀ CONFIG TOML
# ==============================================================================

MOCK_STEAM_VDF_CONTENT = """
"libraryfolders"
{
	"0"
	{
		"path"		"C:\\\\Program Files (x86)\\\\Steam"
		"label"		""
		"contentid"		"123456789"
		"total_size"		"500000000000"
		"apps"
		{
			"228380"		"1000000000"
		}
	}
	"1"
	{
		"path"		"D:\\\\SteamLibrary"
		"label"		"Games"
		"contentid"		"987654321"
		"apps"
		{
			"2694490"		"45000000000"
		}
	}
	"2"
	{
		"path"		"E:\\\\Games\\\\Steam"
		"label"		"FastSSD"
	}
}
"""


# ==============================================================================
# TEST SUITE
# ==============================================================================

def test_vdf_parser_multi_libraries():
    """Kiểm tra bóc tách chính xác các đường dẫn thư viện Steam từ tệp VDF thực tế."""
    paths = parse_vdf_library_folders(MOCK_STEAM_VDF_CONTENT)
    assert len(paths) == 3
    assert paths[0] == "C:\\Program Files (x86)\\Steam"
    assert paths[1] == "D:\\SteamLibrary"
    assert paths[2] == "E:\\Games\\Steam"

    # Edge cases
    assert parse_vdf_library_folders("") == []
    assert parse_vdf_library_folders("no path in this text") == []


def test_is_valid_poe_exe(tmp_path: Path):
    """Kiểm tra tính hợp lệ của tệp thực thi POE2."""
    resolver = GamePathResolver(config_path=tmp_path / "config.toml")

    # 1. Tệp không tồn tại -> False
    assert resolver.is_valid_poe_exe(tmp_path / "NonExistent.exe") is False

    # 2. Tệp tồn tại nhưng không phải tên POE2 -> False
    dummy_exe = tmp_path / "notepad.exe"
    dummy_exe.write_text("dummy", encoding="utf-8")
    assert resolver.is_valid_poe_exe(dummy_exe) is False

    # 3. Tệp tồn tại và đúng tên PathOfExile.exe -> True
    poe_exe = tmp_path / "PathOfExile.exe"
    poe_exe.write_text("dummy", encoding="utf-8")
    assert resolver.is_valid_poe_exe(poe_exe) is True

    # 4. Tệp tồn tại và tên PathOfExileSteam.exe -> True
    poe_steam_exe = tmp_path / "PathOfExileSteam.exe"
    poe_steam_exe.write_text("dummy", encoding="utf-8")
    assert resolver.is_valid_poe_exe(poe_steam_exe) is True

    # 5. Tệp tồn tại và tên PathOfExile_x64.exe -> True
    poe_x64_exe = tmp_path / "PathOfExile_x64.exe"
    poe_x64_exe.write_text("dummy", encoding="utf-8")
    assert resolver.is_valid_poe_exe(poe_x64_exe) is True


def test_tier_1_resolve_from_running_process(tmp_path: Path):
    """Test Tầng 1: Đọc từ tiến trình game đang hoạt động."""
    resolver = GamePathResolver(config_path=tmp_path / "config.toml")

    fake_exe = tmp_path / "PathOfExile.exe"
    fake_exe.write_text("binary", encoding="utf-8")

    # Mock psutil.process_iter trả về tiến trình POE2
    mock_proc = MagicMock()
    mock_proc.info = {"name": "PathOfExile.exe", "exe": str(fake_exe), "pid": 12345}
    mock_proc.exe.return_value = str(fake_exe)

    with patch("psutil.process_iter", return_value=[mock_proc]):
        resolved = resolver.resolve_from_running_process()
        assert resolved is not None
        assert Path(resolved) == fake_exe.resolve()

    # Trường hợp không có tiến trình nào khớp
    with patch("psutil.process_iter", return_value=[]):
        assert resolver.resolve_from_running_process() is None


def test_tier_2_resolve_from_config(tmp_path: Path):
    """Test Tầng 2: Đọc cấu hình từ config.toml."""
    cfg_file = tmp_path / "config.toml"
    resolver = GamePathResolver(config_path=cfg_file)

    fake_exe = tmp_path / "PathOfExileSteam.exe"
    fake_exe.write_text("binary", encoding="utf-8")

    # 1. Chưa có config file -> None
    assert resolver.resolve_from_config() is None

    # 2. Config rỗng -> None
    cfg_file.write_text('[game]\nexecutable_path = ""\n', encoding="utf-8")
    assert resolver.resolve_from_config() is None

    # 3. Config trỏ vào tệp không tồn tại -> None
    cfg_file.write_text('[game]\nexecutable_path = "C:\\\\FakePath\\\\PathOfExile.exe"\n', encoding="utf-8")
    assert resolver.resolve_from_config() is None

    # 4. Config có đường dẫn hợp lệ tồn tại trên đĩa -> Resolved
    escaped = str(fake_exe).replace("\\", "\\\\")
    cfg_file.write_text(f'[game]\nexecutable_path = "{escaped}"\n', encoding="utf-8")
    resolved = resolver.resolve_from_config()
    assert resolved is not None
    assert Path(resolved) == fake_exe.resolve()


def test_tier_3_resolve_from_steam_vdf(tmp_path: Path):
    """Test Tầng 3: Quét Steam libraryfolders.vdf và phát hiện game."""
    cfg_file = tmp_path / "config.toml"
    resolver = GamePathResolver(config_path=cfg_file)

    # Tạo giả lập cây thư mục Steam
    steam_dir = tmp_path / "Steam"
    steam_apps = steam_dir / "steamapps"
    steam_apps.mkdir(parents=True)

    vdf_file = steam_apps / "libraryfolders.vdf"
    lib_dir = tmp_path / "CustomLibrary"
    poe_dir = lib_dir / "steamapps" / "common" / "Path of Exile 2"
    poe_dir.mkdir(parents=True)
    fake_exe = poe_dir / "PathOfExile.exe"
    fake_exe.write_text("binary", encoding="utf-8")

    steam_escaped = str(steam_dir).replace("\\", "\\\\")
    lib_escaped = str(lib_dir).replace("\\", "\\\\")
    custom_vdf = f'''
    "libraryfolders"
    {{
        "0"
        {{
            "path"      "{steam_escaped}"
        }}
        "1"
        {{
            "path"      "{lib_escaped}"
        }}
    }}
    '''
    vdf_file.write_text(custom_vdf, encoding="utf-8")

    # Mock _scan_windows_registry trả về None để đi tiếp vào _scan_steam_vdf
    with patch.object(resolver, "_scan_windows_registry", return_value=None), \
         patch.object(resolver, "get_available_drives", return_value=[]), \
         patch("winreg.OpenKey") as mock_open_key, \
         patch("winreg.QueryValueEx", return_value=(str(steam_dir), 1)):
        found = resolver.resolve_from_system_and_steam()
        assert found is not None
        assert Path(found) == fake_exe.resolve()


def test_tier_3_resolve_from_registry(tmp_path: Path):
    """Test Tầng 3: Quét Registry Grinding Gear Games."""
    cfg_file = tmp_path / "config.toml"
    resolver = GamePathResolver(config_path=cfg_file)

    game_dir = tmp_path / "GrindingGearGames" / "Path of Exile 2"
    game_dir.mkdir(parents=True)
    fake_exe = game_dir / "PathOfExile.exe"
    fake_exe.write_text("binary", encoding="utf-8")

    with patch.object(resolver, "_scan_windows_registry", return_value=str(fake_exe)):
        found = resolver.resolve_from_system_and_steam()
        assert found == str(fake_exe)


def test_tier_4_prompt_user_for_exe(tmp_path: Path):
    """Test Tầng 4: Hộp thoại GUI tương tác người dùng."""
    cfg_file = tmp_path / "config.toml"
    resolver = GamePathResolver(config_path=cfg_file)

    fake_exe = tmp_path / "PathOfExileSteam.exe"
    fake_exe.write_text("binary", encoding="utf-8")

    # 1. Người dùng chọn file hợp lệ
    with patch("tkinter.filedialog.askopenfilename", return_value=str(fake_exe)):
        chosen = resolver.prompt_user_for_exe()
        assert chosen is not None
        assert Path(chosen) == fake_exe.resolve()

        # Kiểm tra tự động lưu vào config.toml
        cfg_content = cfg_file.read_text(encoding="utf-8")
        assert "executable_path" in cfg_content
        assert fake_exe.name in cfg_content

    # 2. Người dùng hủy bỏ (chọn rỗng)
    with patch("tkinter.filedialog.askopenfilename", return_value=""):
        chosen_cancel = resolver.prompt_user_for_exe()
        assert chosen_cancel is None


def test_save_game_path_preserves_other_sections(tmp_path: Path):
    """Kiểm tra save_game_path bảo tồn nguyên vẹn các thiết lập khác trong config.toml."""
    cfg_file = tmp_path / "config.toml"
    resolver = GamePathResolver(config_path=cfg_file)

    original_config = """# Config gốc
[game]
executable_path = ""
launch_arguments = "--nologo"

[visual]
zoom_cap = 6.5
disable_fog = true

[inspector]
enabled = true
dwell_time_ms = 220
"""
    cfg_file.write_text(original_config, encoding="utf-8")

    fake_exe = tmp_path / "PathOfExile.exe"
    fake_exe.write_text("binary", encoding="utf-8")

    # Lưu path mới
    success = resolver.save_game_path(fake_exe)
    assert success is True

    # Đọc lại và xác thực
    updated_content = cfg_file.read_text(encoding="utf-8")
    assert "zoom_cap = 6.5" in updated_content
    assert "dwell_time_ms = 220" in updated_content
    assert "disable_fog = true" in updated_content
    assert str(fake_exe).replace("\\", "\\\\") in updated_content or str(fake_exe) in updated_content

    # Kiểm tra resolver đọc lại từ config khớp 100%
    assert resolver.resolve_from_config() == str(fake_exe.resolve())


def test_resolve_waterfall_priority(tmp_path: Path):
    """Kiểm tra thứ tự ưu tiên 4 tầng của hàm resolve()."""
    cfg_file = tmp_path / "config.toml"
    resolver = GamePathResolver(config_path=cfg_file)

    proc_exe = tmp_path / "proc" / "PathOfExile.exe"
    proc_exe.parent.mkdir()
    proc_exe.write_text("proc", encoding="utf-8")

    cfg_exe = tmp_path / "cfg" / "PathOfExileSteam.exe"
    cfg_exe.parent.mkdir()
    cfg_exe.write_text("cfg", encoding="utf-8")

    sys_exe = tmp_path / "sys" / "PathOfExile_x64.exe"
    sys_exe.parent.mkdir()
    sys_exe.write_text("sys", encoding="utf-8")

    gui_exe = tmp_path / "gui" / "PathOfExile.exe"
    gui_exe.parent.mkdir()
    gui_exe.write_text("gui", encoding="utf-8")

    # 1. Khi tiến trình có -> Ưu tiên Tầng 1 (tiến trình)
    with patch.object(resolver, "resolve_from_running_process", return_value=str(proc_exe)), \
         patch.object(resolver, "resolve_from_config", return_value=str(cfg_exe)):
        assert resolver.resolve() == str(proc_exe)

    # 2. Khi tiến trình None -> Ưu tiên Tầng 2 (config)
    with patch.object(resolver, "resolve_from_running_process", return_value=None), \
         patch.object(resolver, "resolve_from_config", return_value=str(cfg_exe)):
        assert resolver.resolve() == str(cfg_exe)

    # 3. Khi tiến trình & config None -> Ưu tiên Tầng 3 (system/steam)
    with patch.object(resolver, "resolve_from_running_process", return_value=None), \
         patch.object(resolver, "resolve_from_config", return_value=None), \
         patch.object(resolver, "resolve_from_system_and_steam", return_value=str(sys_exe)):
        assert resolver.resolve() == str(sys_exe)

    # 4. Khi cả 3 tầng đầu None và allow_prompt=True -> Tầng 4 (GUI prompt)
    with patch.object(resolver, "resolve_from_running_process", return_value=None), \
         patch.object(resolver, "resolve_from_config", return_value=None), \
         patch.object(resolver, "resolve_from_system_and_steam", return_value=None), \
         patch.object(resolver, "prompt_user_for_exe", return_value=str(gui_exe)):
        assert resolver.resolve(allow_prompt=True) == str(gui_exe)
