#include "common/window_utils.hpp"
#include "common/string_utils.hpp"
#include "common/toml_parser.hpp"
#include "memory/imemory_reader.hpp"

#include <commdlg.h>
#include <psapi.h>
#include <chrono>
#include <thread>
#include <fstream>
#include <vector>
#include <iostream>

namespace common {

namespace {

inline bool FileExistsW(const std::wstring& path) {
    if (path.empty()) return false;
    const DWORD attr = GetFileAttributesW(path.c_str());
    return (attr != INVALID_FILE_ATTRIBUTES && !(attr & FILE_ATTRIBUTE_DIRECTORY));
}

std::wstring CheckRegistryString(HKEY root, const wchar_t* subkey, const wchar_t* valueName) {
    HKEY hKey = nullptr;
    if (RegOpenKeyExW(root, subkey, 0, KEY_READ, &hKey) == ERROR_SUCCESS) {
        wchar_t buf[MAX_PATH] = {};
        DWORD type = 0;
        DWORD bytes = sizeof(buf);
        if (RegQueryValueExW(hKey, valueName, nullptr, &type, reinterpret_cast<LPBYTE>(buf), &bytes) == ERROR_SUCCESS) {
            RegCloseKey(hKey);
            return std::wstring(buf);
        }
        RegCloseKey(hKey);
    }
    return L"";
}

} // namespace

HWND FindPoe2Window() noexcept {
    HWND hwnd = FindWindowW(L"POEWindowClass", nullptr);
    if (!hwnd) hwnd = FindWindowW(nullptr, L"Path of Exile 2");
    if (!hwnd) hwnd = FindWindowW(L"POEC2_MainWindow", nullptr);
    if (!hwnd) hwnd = FindWindowW(nullptr, L"Path of Exile");
    return hwnd;
}

bool EnsurePoe2WindowFocus(uint32_t timeoutMs) noexcept {
    HWND hwnd = FindPoe2Window();
    if (!hwnd) return false;

    HWND fg = GetForegroundWindow();
    if (fg != hwnd) {
        DWORD curThread = GetCurrentThreadId();
        DWORD fgThread = GetWindowThreadProcessId(fg, nullptr);
        AttachThreadInput(curThread, fgThread, TRUE);
        SetForegroundWindow(hwnd);
        BringWindowToTop(hwnd);
        AttachThreadInput(curThread, fgThread, FALSE);
    }

    if (GetForegroundWindow() == hwnd) return true;
    if (timeoutMs == 0) return (GetForegroundWindow() == hwnd);

    uint32_t elapsed = 0;
    while (elapsed < timeoutMs) {
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
        elapsed += 10;
        if (GetForegroundWindow() == hwnd) return true;
    }
    return (GetForegroundWindow() == hwnd);
}

bool GetGameClientRect(HWND hwnd, RECT& outRect) noexcept {
    if (!hwnd || !IsWindow(hwnd)) return false;
    RECT rc{};
    if (!GetClientRect(hwnd, &rc)) return false;

    POINT ptTopLeft{ rc.left, rc.top };
    POINT ptBottomRight{ rc.right, rc.bottom };
    if (!ClientToScreen(hwnd, &ptTopLeft) || !ClientToScreen(hwnd, &ptBottomRight)) {
        return false;
    }
    outRect.left = ptTopLeft.x;
    outRect.top = ptTopLeft.y;
    outRect.right = ptBottomRight.x;
    outRect.bottom = ptBottomRight.y;
    return true;
}

float GetDpiScale(HWND hwnd) noexcept {
    if (!hwnd || !IsWindow(hwnd)) return 1.0f;

    using PFN_GetDpiForWindow = UINT(WINAPI*)(HWND);
    static PFN_GetDpiForWindow pfnGetDpiForWindow = reinterpret_cast<PFN_GetDpiForWindow>(
        GetProcAddress(GetModuleHandleW(L"user32.dll"), "GetDpiForWindow"));

    if (pfnGetDpiForWindow) {
        UINT dpi = pfnGetDpiForWindow(hwnd);
        if (dpi > 0) return static_cast<float>(dpi) / 96.0f;
    }

    HDC hdc = GetDC(hwnd);
    if (hdc) {
        int dpi = GetDeviceCaps(hdc, LOGPIXELSX);
        ReleaseDC(hwnd, hdc);
        if (dpi > 0) return static_cast<float>(dpi) / 96.0f;
    }
    return 1.0f;
}

std::wstring GetExecutableDirectory() noexcept {
    wchar_t exePath[MAX_PATH] = {};
    GetModuleFileNameW(nullptr, exePath, MAX_PATH);
    std::wstring dir(exePath);
    const auto slash = dir.find_last_of(L"\\/");
    return (slash == std::wstring::npos) ? L"." : dir.substr(0, slash);
}

std::wstring ResolveConfigPath() noexcept {
    const std::wstring exeDir = GetExecutableDirectory();
    const std::wstring candidates[] = {
        exeDir + L"\\config.toml",
        exeDir + L"\\..\\config.toml",
        exeDir + L"\\..\\..\\config.toml",
        exeDir + L"\\config\\config.toml",
        exeDir + L"\\..\\config\\config.toml",
        exeDir + L"\\..\\..\\config\\config.toml"
    };
    for (const auto& c : candidates) {
        if (FileExistsW(c)) return c;
    }
    return exeDir + L"\\config.toml";
}

void GetGameResolution(int& outW, int& outH) noexcept {
    HWND hwnd = FindPoe2Window();
    if (hwnd) {
        RECT rc{};
        if (GetGameClientRect(hwnd, rc)) {
            int w = rc.right - rc.left;
            int h = rc.bottom - rc.top;
            if (w > 200 && h > 200) {
                outW = w;
                outH = h;
                return;
            }
        }
    }
    outW = GetSystemMetrics(SM_CXSCREEN);
    outH = GetSystemMetrics(SM_CYSCREEN);
    if (outW <= 0) outW = 1920;
    if (outH <= 0) outH = 1080;
}

std::wstring PromptUserForGameExe(HWND parent) {
    wchar_t szFile[MAX_PATH] = {};
    OPENFILENAMEW ofn{};
    ofn.lStructSize = sizeof(ofn);
    ofn.hwndOwner = parent;
    ofn.lpstrFilter = L"Path of Exile Executable (PathOfExile*.exe)\0PathOfExile*.exe;PathOfExile.exe;PathOfExileSteam.exe;PathOfExile_x64.exe;PathOfExile2.exe\0Executable Files (*.exe)\0*.exe\0All Files (*.*)\0*.*\0\0";
    ofn.lpstrFile = szFile;
    ofn.nMaxFile = MAX_PATH;
    ofn.lpstrTitle = L"Select Path of Exile 2 Executable (PathOfExile*.exe)";
    ofn.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_ENABLESIZING;

    if (GetOpenFileNameW(&ofn)) {
        return std::wstring(szFile);
    }
    return L"";
}

bool SaveClientExeToConfig(const std::wstring& configPath, const std::wstring& exePath) {
    if (configPath.empty() || exePath.empty()) return false;

    // Chuyển exePath từ std::wstring sang std::string UTF-8
    int utf8Len = WideCharToMultiByte(CP_UTF8, 0, exePath.c_str(), -1, nullptr, 0, nullptr, nullptr);
    if (utf8Len <= 0) return false;
    std::string exeUtf8(utf8Len, '\0');
    WideCharToMultiByte(CP_UTF8, 0, exePath.c_str(), -1, exeUtf8.data(), utf8Len, nullptr, nullptr);
    while (!exeUtf8.empty() && exeUtf8.back() == '\0') exeUtf8.pop_back();

    // Escape backslashes cho định dạng TOML
    std::string escapedExe;
    for (char c : exeUtf8) {
        if (c == '\\') escapedExe += "\\\\";
        else escapedExe += c;
    }

    std::vector<std::string> lines;
    bool replaced = false;

    // Đọc các dòng hiện tại nếu file đã tồn tại
    {
        std::ifstream inFile(configPath);
        if (inFile.is_open()) {
            std::string line;
            while (std::getline(inFile, line)) {
                std::string trimmed = std::string(Trim(line));
                if (trimmed.starts_with("executable_path") ||
                    trimmed.starts_with("client_exe") ||
                    trimmed.starts_with("game_path") ||
                    trimmed.starts_with("exe_path") ||
                    trimmed.starts_with("path")) {
                    lines.push_back("executable_path = \"" + escapedExe + "\"");
                    replaced = true;
                } else {
                    lines.push_back(line);
                }
            }
        }
    }

    if (!replaced) {
        bool hasGameSection = false;
        for (const auto& l : lines) {
            if (Trim(l) == "[game]") {
                hasGameSection = true;
                break;
            }
        }
        if (!hasGameSection) {
            if (!lines.empty() && !lines.back().empty()) lines.push_back("");
            lines.push_back("[game]");
        }
        lines.push_back("executable_path = \"" + escapedExe + "\"");
    }

    std::ofstream outFile(configPath);
    if (!outFile.is_open()) return false;
    for (const auto& l : lines) {
        outFile << l << "\n";
    }
    return true;
}

std::wstring FindOrPromptPoe2ClientExe(const std::wstring& configPath) {
    // ------------------------------------------------------------------
    // TẦNG 1: Đọc từ file cấu hình config.toml
    // ------------------------------------------------------------------
    if (!configPath.empty()) {
        std::ifstream file(configPath);
        if (file.is_open()) {
            std::string line;
            while (std::getline(file, line)) {
                std::string val;
                if (ParseTomlString(line, "executable_path", val) ||
                    ParseTomlString(line, "client_exe", val) ||
                    ParseTomlString(line, "game_path", val) ||
                    ParseTomlString(line, "exe_path", val) ||
                    ParseTomlString(line, "path", val)) {
                    if (!val.empty()) {
                        int wlen = MultiByteToWideChar(CP_UTF8, 0, val.c_str(), -1, nullptr, 0);
                        if (wlen > 0) {
                            std::wstring wpath(wlen, L'\0');
                            MultiByteToWideChar(CP_UTF8, 0, val.c_str(), -1, wpath.data(), wlen);
                            while (!wpath.empty() && wpath.back() == L'\0') wpath.pop_back();
                            if (FileExistsW(wpath)) {
                                return wpath;
                            }
                        }
                    }
                }
            }
        }
    }

    // ------------------------------------------------------------------
    // TẦNG 2: Kiểm tra tiến trình game đang chạy
    // ------------------------------------------------------------------
    const uint32_t pid = FindPoeProcessId();
    if (pid != 0) {
        HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
        if (!hProcess) {
            hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
        }
        if (hProcess) {
            wchar_t procPath[MAX_PATH] = {};
            DWORD size = MAX_PATH;
            if (QueryFullProcessImageNameW(hProcess, 0, procPath, &size)) {
                CloseHandle(hProcess);
                std::wstring found(procPath);
                if (FileExistsW(found)) {
                    SaveClientExeToConfig(configPath, found);
                    return found;
                }
            } else {
                CloseHandle(hProcess);
            }
        }
    }

    // ------------------------------------------------------------------
    // TẦNG 3: Kiểm tra Windows Registry & các đường dẫn chuẩn mặc định
    // ------------------------------------------------------------------
    // 3.1 Registry GGG
    std::wstring regPoe2 = CheckRegistryString(HKEY_CURRENT_USER, L"Software\\GrindingGearGames\\Path of Exile 2", L"InstallLocation");
    if (regPoe2.empty()) regPoe2 = CheckRegistryString(HKEY_CURRENT_USER, L"Software\\GrindingGearGames\\Path of Exile 2", L"Path");
    if (!regPoe2.empty()) {
        std::wstring candidate = regPoe2 + (regPoe2.back() == L'\\' ? L"" : L"\\") + L"PathOfExile.exe";
        if (FileExistsW(candidate)) {
            SaveClientExeToConfig(configPath, candidate);
            return candidate;
        }
    }

    // 3.2 Registry Steam
    std::wstring steamPath = CheckRegistryString(HKEY_CURRENT_USER, L"Software\\Valve\\Steam", L"SteamPath");
    if (!steamPath.empty()) {
        for (auto& ch : steamPath) { if (ch == L'/') ch = L'\\'; }
        std::wstring steamCandidate1 = steamPath + L"\\steamapps\\common\\Path of Exile 2\\PathOfExileSteam.exe";
        std::wstring steamCandidate2 = steamPath + L"\\steamapps\\common\\Path of Exile 2\\PathOfExile.exe";
        if (FileExistsW(steamCandidate1)) {
            SaveClientExeToConfig(configPath, steamCandidate1);
            return steamCandidate1;
        }
        if (FileExistsW(steamCandidate2)) {
            SaveClientExeToConfig(configPath, steamCandidate2);
            return steamCandidate2;
        }
    }

    // 3.3 Registry Steam App Uninstall 2694490
    std::wstring steamAppInstall = CheckRegistryString(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Steam App 2694490", L"InstallLocation");
    if (!steamAppInstall.empty()) {
        std::wstring candidate = steamAppInstall + (steamAppInstall.back() == L'\\' ? L"" : L"\\") + L"PathOfExileSteam.exe";
        if (FileExistsW(candidate)) {
            SaveClientExeToConfig(configPath, candidate);
            return candidate;
        }
    }

    // 3.4 Các đường dẫn mặc định tiêu chuẩn
    static const wchar_t* kCandidates[] = {
        L"C:\\Program Files (x86)\\Grinding Gear Games\\Path of Exile 2\\PathOfExile.exe",
        L"C:\\Program Files (x86)\\Grinding Gear Games\\Path of Exile 2\\PathOfExile_x64.exe",
        L"C:\\Program Files\\Grinding Gear Games\\Path of Exile 2\\PathOfExile.exe",
        L"C:\\Program Files\\Grinding Gear Games\\Path of Exile 2\\PathOfExile_x64.exe",
        L"C:\\Grinding Gear Games\\Path of Exile 2\\PathOfExile.exe",
        L"D:\\Grinding Gear Games\\Path of Exile 2\\PathOfExile.exe",
        L"E:\\Grinding Gear Games\\Path of Exile 2\\PathOfExile.exe",
        L"C:\\Program Files (x86)\\Steam\\steamapps\\common\\Path of Exile 2\\PathOfExileSteam.exe",
        L"C:\\Program Files (x86)\\Steam\\steamapps\\common\\Path of Exile 2\\PathOfExile.exe",
        L"D:\\SteamLibrary\\steamapps\\common\\Path of Exile 2\\PathOfExileSteam.exe",
        L"D:\\SteamLibrary\\steamapps\\common\\Path of Exile 2\\PathOfExile.exe",
        L"E:\\SteamLibrary\\steamapps\\common\\Path of Exile 2\\PathOfExileSteam.exe",
        L"D:\\Games\\Path of Exile 2\\PathOfExile.exe"
    };

    for (const wchar_t* candidate : kCandidates) {
        if (FileExistsW(candidate)) {
            SaveClientExeToConfig(configPath, candidate);
            return std::wstring(candidate);
        }
    }

    // ------------------------------------------------------------------
    // TẦNG 4: Hiển thị hộp thoại chọn file Native cho người dùng
    // ------------------------------------------------------------------
    std::wstring prompted = PromptUserForGameExe(nullptr);
    if (!prompted.empty()) {
        SaveClientExeToConfig(configPath, prompted);
        return prompted;
    }

    return L"";
}

} // namespace common
