#include <Windows.h>
#include <TlHelp32.h>
#include <iostream>
#include "utils.h"
#include "inifile.h"

bool debug = false;

void Log(const std::string& message) {
    if (!debug) return;
    std::cout << message << std::endl;
    Sleep(2000);
}

std::wstring ExtractResource(std::string resourceName, std::wstring& saveDir) {
    std::string fileName = resourceName;
    std::replace(fileName.begin(), fileName.end(), '_', '.');

    std::wstring fullPath = saveDir + StringToWString("\\" + fileName);

    // Ищем ресурс по его имени
    HRSRC hResource = FindResourceW(NULL, StringToWString(resourceName).c_str(), RT_RCDATA);
    if (!hResource) {
        Log("Resource not found: " + resourceName);
        return {};
    }

    DWORD dwSize = SizeofResource(NULL, hResource);
    HGLOBAL hLoadedResource = LoadResource(NULL, hResource);
    void* pData = LockResource(hLoadedResource);

    std::ofstream file(fullPath, std::ios::binary);
    if (file.is_open()) {
        file.write(reinterpret_cast<char*>(pData), dwSize);
        file.close();
    } else {
        Log("Failed to write resource to file: " + WStringToString(fullPath));
    }

    return fullPath;
}


bool InjectDLL(DWORD pid, std::wstring& dllDir) {
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
    if (!hProcess) return false;

    BOOL isX32 = FALSE;
    IsWow64Process(hProcess, &isX32);

    LPVOID pLoadLibraryW = NULL;
    std::wstring dllPath = {};

    if (!isX32) {
        dllPath = ExtractResource("x64_dll", dllDir);  // Для 64 bit процесса
        pLoadLibraryW = (LPVOID)GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "LoadLibraryW");
    } else {
        dllPath = ExtractResource("x32_dll", dllDir);  // Для 32 bit процесса
        std::wstring x32ExePath = ExtractResource("x32_exe", dllDir);

        STARTUPINFO si = { sizeof(si) };
        PROCESS_INFORMATION pi;
        if (!CreateProcessW(x32ExePath.c_str(), NULL, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi)) {
            Log("Failed to start the 32-bit process.");
            return false;
        }
        WaitForSingleObject(pi.hProcess, INFINITE);

        DWORD exitCode;
        GetExitCodeProcess(pi.hProcess, &exitCode);
        pLoadLibraryW = (LPVOID)exitCode;

        Log("pLoadLibraryW: " + std::to_string(exitCode));

        DeleteFileW(x32ExePath.c_str());
    }

    LPVOID pRemoteMem = VirtualAllocEx(hProcess, NULL,
                                       (dllPath.size() + 1) * sizeof(wchar_t),
                                       MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);

    WriteProcessMemory(hProcess, pRemoteMem,
                       dllPath.c_str(),
                       (dllPath.size() + 1) * sizeof(wchar_t),
                       NULL);

    HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0,
                                        (LPTHREAD_START_ROUTINE)pLoadLibraryW,
                                        pRemoteMem, 0, NULL);

    if (!hThread) {
        VirtualFreeEx(hProcess, pRemoteMem, 0, MEM_RELEASE);
        CloseHandle(hProcess);
        return false;
    }

    WaitForSingleObject(hThread, INFINITE);
    CloseHandle(hThread);
    VirtualFreeEx(hProcess, pRemoteMem, 0, MEM_RELEASE);
    CloseHandle(hProcess);

    DeleteFileW(dllPath.c_str());
    return true;
}



bool WaitForProcessAndInject(std::vector<std::string>& target, std::wstring& dllDir, FILETIME& startTime) {
    HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (hSnapshot == INVALID_HANDLE_VALUE) {
        Log("Failed to create snapshot of processes.");
        return false;
    }

    PROCESSENTRY32 pe = { 0 };
    pe.dwSize = sizeof(PROCESSENTRY32);

    if (Process32First(hSnapshot, &pe)) {
        do {
            FILETIME creationTime;
            if (IsTargetProcess(pe.szExeFile, target) && GetProcessCreationTime(pe.th32ProcessID, creationTime) && CompareFileTime(&startTime, &creationTime) <= 0) {
                std::string processName = WStringToString(pe.szExeFile);
                if (InjectDLL(pe.th32ProcessID, dllDir)) {
                    Log("injected: " + processName);
                    CloseHandle(hSnapshot);
                } else {
                    Log("Failed to inject DLL: " + processName);
                }
                return true;
            }
        } while (Process32Next(hSnapshot, &pe));
    }
    Log("No matching processes found for injection.");
    CloseHandle(hSnapshot);
    return false;
}


int main(int argc, char *argv[])
{


    IniFile ini;
    if (!FileExists("LauncherOptions.ini") || !ini.read("LauncherOptions.ini")) {
        MessageBoxA(0, "Could not find LauncherOptions.ini (file is missing or corrupted).", "Error", MB_OK | MB_ICONERROR);
        return 0;
    }

    debug = ini.toBool("debug");
    if (debug) {
        AllocConsole();
        freopen("CONOUT$", "w", stdout);
        freopen("CONOUT$", "w", stderr);
        SetConsoleCP(CP_UTF8);
        SetConsoleOutputCP(CP_UTF8);
    }



    std::wstring selfDir = GetSelfDirectoryW();
    std::string targetExe = ini.toString("target", "BrowserAutomationStudio.exe");
    Log("targetExe: " +  WStringToString(selfDir) + "\\" + targetExe);

    if (!FileExists(targetExe.c_str())) {
        MessageBoxA(0, std::string("Could not find target: " + targetExe).c_str(), "Error", MB_OK | MB_ICONERROR);
        return 0;
    }


    std::wstring cmdArgs = GetCommandLineArgumentsW();
    Log("Arguments: " + WStringToString(cmdArgs));

    STARTUPINFOW si = { sizeof(si) };
    PROCESS_INFORMATION pi = { 0 };


    wchar_t* cmdArgsPtr = _wcsdup(cmdArgs.c_str());
    if (!CreateProcessW(StringToWString(targetExe).c_str(), cmdArgsPtr, NULL, NULL, FALSE, CREATE_SUSPENDED | CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi)) {
        Log("Failed to create process: " + std::to_string(GetLastError()));
        free(cmdArgsPtr);
        return 0;
    }
    free(cmdArgsPtr);

    if (InjectDLL(pi.dwProcessId, selfDir)) {
        Log("injected: " + targetExe);
    } else {
        Log("Failed to inject DLL: " + std::to_string(GetLastError()));
        return 0;
    };

    ResumeThread(pi.hThread);

    FILETIME startTime;
    GetSystemTimeAsFileTime(&startTime);

    WaitForSingleObject(pi.hProcess, INFINITE);
    CloseHandle(pi.hThread);
    CloseHandle(pi.hProcess);

    DWORD startTick = GetTickCount();
    DWORD timeout = ini.toUint32("targetNextWaitTimeout", 15000);

    std::vector<std::string> targetProcesses = ini.toArray("targetNext");
    while (targetProcesses.size() > 0) {

        if (WaitForProcessAndInject(targetProcesses, selfDir, startTime)) {
            break;
        }

        if (GetTickCount() - startTick > timeout) {
            Log("Timeout reached: " + std::to_string(timeout));
            break;
        }

        Sleep(100);
    }

    Log("DONE");

    return 0;
}
