#pragma once

// language: C++, file: apexauth.h, target: Windows 10/11, MSVC / MinGW
// Production-grade, zero-dependency native C++ SDK for ApexAuth.
// Links WinHTTP and BCrypt directly — no third-party libraries required.

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winhttp.h>
#include <bcrypt.h>
#include <string>
#include <sstream>
#include <iomanip>
#include <chrono>
#include <vector>
#include <stdexcept>

#pragma comment(lib, "winhttp.lib")
#pragma comment(lib, "bcrypt.lib")

#ifndef NT_SUCCESS
#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
#endif

namespace ApexAuth {

struct user_data_class {
    std::string username;
    std::string ip;
    std::string hwid;
    std::string createdate;
    std::string lastlogin;
    std::string expiry;
    std::vector<std::string> subscriptions;
};

struct response_class {
    bool success = false;
    std::string message = "";
};

class api {
public:
    std::string name;
    std::string ownerid;
    std::string secret;
    std::string version;
    std::string url;

    struct app_data_class {
        std::string numUsers;
        std::string numOnlineUsers;
        std::string numKeys;
        std::string version;
        std::string customerPanel;
        std::string downloadUrl;
        std::string changelog;
        bool updateAvailable = false;
        bool forceUpdate = false;
        bool isSilentHotfix = false;
        int buildIteration = 1;
    } app_data;

    bool has_update() const { return app_data.updateAvailable; }
    bool is_silent_hotfix() const { return app_data.isSilentHotfix; }
    std::string get_update_version() const { return app_data.version; }

    user_data_class user_data;
    response_class response;


private:
    std::string sessionToken;
    std::string encKey;

    static std::wstring toWide(const std::string& str) {
        if (str.empty()) return L"";
        int len = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int)str.length(), nullptr, 0);
        std::wstring wstr(len, 0);
        MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int)str.length(), &wstr[0], len);
        return wstr;
    }

    static std::string generateNonce(size_t length = 16) {
        static const char hex[] = "0123456789abcdef";
        std::string nonce;
        nonce.reserve(length);
        unsigned char buffer[32];
        BCryptGenRandom(nullptr, buffer, sizeof(buffer), BCRYPT_USE_SYSTEM_PREFERRED_RNG);
        for (size_t i = 0; i < length; ++i) {
            nonce += hex[buffer[i] % 16];
        }
        return nonce;
    }

    static std::string sha256Hex(const std::string& input) {
        BCRYPT_ALG_HANDLE hAlg = nullptr;
        BCRYPT_HASH_HANDLE hHash = nullptr;
        std::string hexResult;

        if (NT_SUCCESS(BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_SHA256_ALGORITHM, nullptr, 0))) {
            DWORD hashObjSize = 0, dataSize = sizeof(DWORD);
            BCryptGetProperty(hAlg, BCRYPT_OBJECT_LENGTH, (PBYTE)&hashObjSize, sizeof(DWORD), &dataSize, 0);
            std::vector<BYTE> hashObj(hashObjSize);

            if (NT_SUCCESS(BCryptCreateHash(hAlg, &hHash, hashObj.data(), hashObjSize, nullptr, 0, 0))) {
                BCryptHashData(hHash, (PBYTE)input.c_str(), (ULONG)input.length(), 0);
                BYTE hash[32];
                if (NT_SUCCESS(BCryptFinishHash(hHash, hash, sizeof(hash), 0))) {
                    std::stringstream ss;
                    for (BYTE b : hash) {
                        ss << std::hex << std::setw(2) << std::setfill('0') << (int)b;
                    }
                    hexResult = ss.str();
                }
                BCRYPT_DestroyHash(hHash);
            }
            BCryptCloseAlgorithmProvider(hAlg, 0);
        }
        return hexResult;
    }

    std::string calculateHmac(const std::string& payload, const std::string& key) {
        BCRYPT_ALG_HANDLE hAlg = nullptr;
        BCRYPT_HASH_HANDLE hHash = nullptr;
        std::string hexResult;

        if (NT_SUCCESS(BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_SHA256_ALGORITHM, nullptr, BCRYPT_ALG_HANDLE_HMAC_FLAG))) {
            DWORD hashObjSize = 0, dataSize = sizeof(DWORD);
            BCryptGetProperty(hAlg, BCRYPT_OBJECT_LENGTH, (PBYTE)&hashObjSize, sizeof(DWORD), &dataSize, 0);
            std::vector<BYTE> hashObj(hashObjSize);

            if (NT_SUCCESS(BCryptCreateHash(hAlg, &hHash, hashObj.data(), hashObjSize, (PBYTE)key.c_str(), (ULONG)key.length(), 0))) {
                BCryptHashData(hHash, (PBYTE)payload.c_str(), (ULONG)payload.length(), 0);
                BYTE hash[32];
                if (NT_SUCCESS(BCryptFinishHash(hHash, hash, sizeof(hash), 0))) {
                    std::stringstream ss;
                    for (BYTE b : hash) {
                        ss << std::hex << std::setw(2) << std::setfill('0') << (int)b;
                    }
                    hexResult = ss.str();
                }
                BCRYPT_DestroyHash(hHash);
            }
            BCryptCloseAlgorithmProvider(hAlg, 0);
        }
        return hexResult;
    }

    // Hardware ID derived from Windows MachineGuid + System Volume Serial
    std::string getHwid() {
        std::string guidStr;
        HKEY hKey;
        if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Cryptography", 0, KEY_READ | KEY_WOW64_64KEY, &hKey) == ERROR_SUCCESS) {
            char val[256];
            DWORD size = sizeof(val);
            if (RegQueryValueExA(hKey, "MachineGuid", nullptr, nullptr, (LPBYTE)val, &size) == ERROR_SUCCESS) {
                guidStr = val;
            }
            RegCloseKey(hKey);
        }

        DWORD volSerial = 0;
        GetVolumeInformationA("C:\\", nullptr, 0, &volSerial, nullptr, nullptr, nullptr, 0);

        std::string raw = guidStr + "-" + std::to_string(volSerial);
        return sha256Hex(raw);
    }

    static std::string unescapeJson(const std::string& str) {
        std::string res;
        res.reserve(str.length());
        for (size_t i = 0; i < str.length(); ++i) {
            if (str[i] == '\\' && i + 1 < str.length()) {
                if (str[i + 1] == '/') { res += '/'; ++i; continue; }
                if (str[i + 1] == '\\') { res += '\\'; ++i; continue; }
                if (str[i + 1] == '"') { res += '"'; ++i; continue; }
                if (str[i + 1] == 'n') { res += '\n'; ++i; continue; }
                if (str[i + 1] == 'r') { res += '\r'; ++i; continue; }
                if (str[i + 1] == 't') { res += '\t'; ++i; continue; }
            }
            res += str[i];
        }
        return res;
    }

    // JSON value extractor
    static std::string extractJsonString(const std::string& json, const std::string& key) {
        std::string needle = "\"" + key + "\":\"";
        size_t pos = json.find(needle);
        if (pos != std::string::npos) {
            pos += needle.length();
            size_t end = json.find("\"", pos);
            if (end != std::string::npos) return unescapeJson(json.substr(pos, end - pos));
        }
        // Check without quotes for numbers / bools
        needle = "\"" + key + "\":";
        pos = json.find(needle);
        if (pos != std::string::npos) {
            pos += needle.length();
            while (pos < json.length() && (json[pos] == ' ' || json[pos] == '\t')) pos++;
            size_t end = json.find_first_of(",}\"\r\n", pos);
            if (end != std::string::npos) return unescapeJson(json.substr(pos, end - pos));
        }
        return "";
    }

    static bool extractJsonBool(const std::string& json, const std::string& key) {
        std::string needle = "\"" + key + "\":";
        size_t pos = json.find(needle);
        if (pos == std::string::npos) return false;
        pos += needle.length();
        while (pos < json.length() && (json[pos] == ' ' || json[pos] == '\t')) pos++;
        return (json.compare(pos, 4, "true") == 0);
    }

    std::string performRequest(const std::string& endpoint, const std::string& bodyJson, bool bypassSignature = false) {
        std::string apiUrl = this->url;
        bool isHttps = (apiUrl.rfind("https://", 0) == 0);
        if (isHttps) {
            apiUrl = apiUrl.substr(8);
        } else if (apiUrl.rfind("http://", 0) == 0) {
            apiUrl = apiUrl.substr(7);
        }

        std::string host = apiUrl;
        std::string basePath = "";
        size_t slashPos = apiUrl.find('/');
        if (slashPos != std::string::npos) {
            host = apiUrl.substr(0, slashPos);
            basePath = apiUrl.substr(slashPos);
        }

        INTERNET_PORT port = isHttps ? INTERNET_DEFAULT_HTTPS_PORT : INTERNET_DEFAULT_HTTP_PORT;
        size_t colon = host.find(':');
        if (colon != std::string::npos) {
            std::string portStr = host.substr(colon + 1);
            host = host.substr(0, colon);
            if (!portStr.empty()) {
                try {
                    port = (INTERNET_PORT)std::stoi(portStr);
                } catch (...) {}
            }
        }

        std::string fullPath = basePath + endpoint;

        auto now = std::chrono::system_clock::now();
        std::string timestamp = std::to_string(std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count());
        std::string nonce = generateNonce(16);

        std::string headers = "Content-Type: application/json\r\n";
        headers += "x-app-secret: " + secret + "\r\n";
        headers += "x-owner-id: " + ownerid + "\r\n";
        headers += "x-timestamp: " + timestamp + "\r\n";
        headers += "x-nonce: " + nonce + "\r\n";

        if (!bypassSignature && !encKey.empty()) {
            std::string sigPayload = timestamp + nonce + bodyJson;
            headers += "x-signature: " + calculateHmac(sigPayload, encKey) + "\r\n";
        }

        HINTERNET hSession = WinHttpOpen(L"ApexAuth-Native-SDK/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
        if (!hSession) return "{\"success\":false,\"message\":\"WinHttpOpen failed.\"}";

        HINTERNET hConnect = WinHttpConnect(hSession, toWide(host).c_str(), port, 0);
        if (!hConnect) {
            WinHttpCloseHandle(hSession);
            return "{\"success\":false,\"message\":\"WinHttpConnect failed.\"}";
        }

        DWORD reqFlags = isHttps ? WINHTTP_FLAG_SECURE : 0;
        HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"POST", toWide(fullPath).c_str(), nullptr, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, reqFlags);
        if (!hRequest) {
            WinHttpCloseHandle(hConnect);
            WinHttpCloseHandle(hSession);
            return "{\"success\":false,\"message\":\"WinHttpOpenRequest failed.\"}";
        }

        DWORD secFlags = SECURITY_FLAG_IGNORE_UNKNOWN_CA | SECURITY_FLAG_IGNORE_CERT_CN_INVALID | SECURITY_FLAG_IGNORE_CERT_DATE_INVALID;
        WinHttpSetOption(hRequest, WINHTTP_OPTION_SECURITY_FLAGS, &secFlags, sizeof(secFlags));

        std::wstring wHeaders = toWide(headers);
        BOOL bResults = WinHttpSendRequest(
            hRequest,
            wHeaders.c_str(),
            (DWORD)wHeaders.length(),
            (LPVOID)bodyJson.c_str(),
            (DWORD)bodyJson.length(),
            (DWORD)bodyJson.length(),
            0
        );

        if (bResults) {
            bResults = WinHttpReceiveResponse(hRequest, nullptr);
        }

        if (!bResults) {
            DWORD err = GetLastError();
            WinHttpCloseHandle(hRequest);
            WinHttpCloseHandle(hConnect);
            WinHttpCloseHandle(hSession);
            return "{\"success\":false,\"message\":\"HTTP request failed with error code: " + std::to_string(err) + "\"}";
        }

        std::string response;
        DWORD dwSize = 0;
        do {
            dwSize = 0;
            if (!WinHttpQueryDataAvailable(hRequest, &dwSize) || dwSize == 0) break;
            std::vector<char> buffer(dwSize + 1);
            DWORD dwDownloaded = 0;
            if (WinHttpReadData(hRequest, buffer.data(), dwSize, &dwDownloaded)) {
                response.append(buffer.data(), dwDownloaded);
            }
        } while (dwSize > 0);

        WinHttpCloseHandle(hRequest);
        WinHttpCloseHandle(hConnect);
        WinHttpCloseHandle(hSession);

        return response;
    }

    void parseAuthResponse(const std::string& raw) {
        response.success = extractJsonBool(raw, "success");
        response.message = extractJsonString(raw, "message");

        if (response.success) {
            user_data.username = extractJsonString(raw, "username");
            user_data.ip = extractJsonString(raw, "ip");
            user_data.hwid = extractJsonString(raw, "hwid");
            user_data.createdate = extractJsonString(raw, "createdate");
            user_data.lastlogin = extractJsonString(raw, "lastlogin");
            user_data.expiry = extractJsonString(raw, "expires");
            if (user_data.expiry.empty()) user_data.expiry = extractJsonString(raw, "expires_at");

            std::string token = extractJsonString(raw, "session_token");
            if (token.empty()) token = extractJsonString(raw, "sessionid");
            if (!token.empty()) sessionToken = token;

            // Extract subscriptions
            user_data.subscriptions.clear();
            std::string needle = "\"subscription\":\"";
            size_t pos = 0;
            while ((pos = raw.find(needle, pos)) != std::string::npos) {
                pos += needle.length();
                size_t end = raw.find("\"", pos);
                if (end != std::string::npos) {
                    user_data.subscriptions.push_back(raw.substr(pos, end - pos));
                    pos = end + 1;
                } else {
                    break;
                }
            }
        }
    }

public:
    api(std::string name, std::string ownerid, std::string secret, std::string version, std::string url)
        : name(name), ownerid(ownerid), secret(secret), version(version), url(url) {
        while (!this->url.empty() && this->url.back() == '/') {
            this->url.pop_back();
        }
        if (this->url.size() >= 7 && this->url.substr(this->url.size() - 7) == "/api/v1") {
            this->url = this->url.substr(0, this->url.size() - 7);
        }
    }

    void init() {
        std::string body = "{\"name\":\"" + name + "\",\"ownerid\":\"" + ownerid + "\",\"secret\":\"" + secret + "\",\"version\":\"" + version + "\"}";
        std::string raw = performRequest("/api/v1/init", body, true);

        response.success = extractJsonBool(raw, "success");
        response.message = extractJsonString(raw, "message");

        if (response.success) {
            encKey = extractJsonString(raw, "encryption_key");
            std::string sess = extractJsonString(raw, "session_token");
            if (sess.empty()) sess = extractJsonString(raw, "sessionid");
            if (!sess.empty()) sessionToken = sess;

            app_data.version = extractJsonString(raw, "version");
            app_data.downloadUrl = extractJsonString(raw, "download_url");
            app_data.changelog = extractJsonString(raw, "changelog");
            app_data.updateAvailable = extractJsonBool(raw, "update_available");
            app_data.forceUpdate = extractJsonBool(raw, "force_update");
            app_data.isSilentHotfix = extractJsonBool(raw, "is_silent_hotfix");
            std::string bStr = extractJsonString(raw, "build_id");
            if (!bStr.empty()) app_data.buildIteration = std::atoi(bStr.c_str());
        } else {
            // Check if server rejected handshake because a mandatory update is required
            std::string dl = extractJsonString(raw, "download_url");
            if (!dl.empty()) {
                app_data.downloadUrl = dl;
                app_data.version = extractJsonString(raw, "current_version");
                app_data.changelog = extractJsonString(raw, "changelog");
                app_data.updateAvailable = true;
                app_data.forceUpdate = true;
            }
        }
    }

    void license(std::string key) {
        std::string body = "{\"license\":\"" + key + "\",\"hwid\":\"" + getHwid() + "\"}";
        std::string raw = performRequest("/api/v1/license", body, false);
        parseAuthResponse(raw);
    }

    void login(std::string username, std::string password) {
        std::string body = "{\"username\":\"" + username + "\",\"password\":\"" + password + "\",\"hwid\":\"" + getHwid() + "\"}";
        std::string raw = performRequest("/api/v1/login", body, false);
        parseAuthResponse(raw);
    }

    void register_user(std::string username, std::string password, std::string key, std::string email = "") {
        if (email.empty()) email = username + "@client.local";
        std::string body = "{\"username\":\"" + username + "\",\"email\":\"" + email + "\",\"password\":\"" + password + "\",\"license\":\"" + key + "\",\"hwid\":\"" + getHwid() + "\"}";
        std::string raw = performRequest("/api/v1/register", body, false);
        parseAuthResponse(raw);
    }

    // Registration alias
    void reg(std::string username, std::string password, std::string key, std::string email = "") {
        register_user(username, password, key, email);
    }

    void check() {
        std::string body = "{\"session_token\":\"" + sessionToken + "\",\"hwid\":\"" + getHwid() + "\"}";
        std::string raw = performRequest("/api/v1/check", body, false);
        response.success = extractJsonBool(raw, "success");
        response.message = extractJsonString(raw, "message");
    }

    std::string var(std::string varname) {
        std::string body = "{\"name\":\"" + varname + "\"}";
        std::string raw = performRequest("/api/v1/var", body, false);
        response.success = extractJsonBool(raw, "success");
        response.message = extractJsonString(raw, "message");
        if (response.success) {
            return extractJsonString(raw, "value");
        }
        return "";
    }

    void log(std::string message) {
        std::string body = "{\"message\":\"" + message + "\"}";
        performRequest("/api/v1/log", body, false);
    }

    void ban(std::string reason = "") {
        std::string body = "{\"reason\":\"" + reason + "\",\"hwid\":\"" + getHwid() + "\"}";
        performRequest("/api/v1/ban", body, false);
    }

    void upgrade(std::string username, std::string key) {
        std::string body = "{\"username\":\"" + username + "\",\"key\":\"" + key + "\"}";
        std::string raw = performRequest("/api/v1/upgrade", body, false);
        parseAuthResponse(raw);
    }

    std::string webhook(std::string id, std::string param) {
        std::string body = "{\"secret\":\"" + id + "\",\"payload\":\"" + param + "\"}";
        return performRequest("/api/v1/webhook", body, false);
    }

    bool has_update() const {
        return app_data.updateAvailable;
    }

    bool is_force_update() const {
        return app_data.forceUpdate;
    }

    std::string get_latest_version() const {
        return app_data.version;
    }

    std::string get_download_url() const {
        return app_data.downloadUrl;
    }

    std::string get_changelog() const {
        return app_data.changelog;
    }

    bool check_update() {
        std::string body = "{\"version\":\"" + version + "\"}";
        std::string raw = performRequest("/api/v1/version", body, false);
        bool success = extractJsonBool(raw, "success");
        if (success) {
            bool isLatest = extractJsonBool(raw, "is_latest");
            app_data.updateAvailable = !isLatest;
            app_data.version = extractJsonString(raw, "current_version");
            app_data.downloadUrl = extractJsonString(raw, "download_url");
            app_data.changelog = extractJsonString(raw, "changelog");
            app_data.forceUpdate = extractJsonBool(raw, "force_update");
            return app_data.updateAvailable;
        }
        return false;
    }

    typedef void(*UpdateProgressCallback)(float progress);

    bool auto_update(UpdateProgressCallback progress_callback = nullptr) {
        std::string targetUrl = app_data.downloadUrl;
        if (targetUrl.empty()) {
            response.success = false;
            response.message = "No download URL configured for this update.";
            return false;
        }

        // Clean any escaped backslashes in URL (e.g. https:\/\/ -> https://)
        while (targetUrl.find("\\/") != std::string::npos) {
            targetUrl.replace(targetUrl.find("\\/"), 2, "/");
        }

        // Handle relative URLs
        if (targetUrl.rfind("/", 0) == 0) {
            targetUrl = this->url + targetUrl;
        }

        // Parse target URL
        bool isHttps = false;
        std::string urlCopy = targetUrl;
        if (urlCopy.rfind("https://", 0) == 0) {
            isHttps = true;
            urlCopy = urlCopy.substr(8);
        } else if (urlCopy.rfind("http://", 0) == 0) {
            isHttps = false;
            urlCopy = urlCopy.substr(7);
        }

        std::string host = urlCopy;
        std::string path = "/";
        size_t slashPos = urlCopy.find('/');
        if (slashPos != std::string::npos) {
            host = urlCopy.substr(0, slashPos);
            path = urlCopy.substr(slashPos);
        }

        INTERNET_PORT port = isHttps ? INTERNET_DEFAULT_HTTPS_PORT : INTERNET_DEFAULT_HTTP_PORT;
        size_t colon = host.find(':');
        if (colon != std::string::npos) {
            std::string portStr = host.substr(colon + 1);
            host = host.substr(0, colon);
            if (!portStr.empty()) {
                try {
                    port = (INTERNET_PORT)std::stoi(portStr);
                } catch (...) {}
            }
        }

        // Get running executable path
        wchar_t currentExe[MAX_PATH];
        if (GetModuleFileNameW(NULL, currentExe, MAX_PATH) == 0) {
            response.success = false;
            response.message = "Failed to determine current executable path.";
            return false;
        }

        std::wstring tempNewExe = std::wstring(currentExe) + L".new";
        std::wstring tempOldExe = std::wstring(currentExe) + L".old";

        // Clean up any stale files from previous updates
        DeleteFileW(tempNewExe.c_str());
        DeleteFileW(tempOldExe.c_str());

        // Open local target file for writing
        HANDLE hFile = CreateFileW(tempNewExe.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
        if (hFile == INVALID_HANDLE_VALUE) {
            response.success = false;
            response.message = "Failed to create temporary file for update payload. Please run as Administrator.";
            return false;
        }

        // Connect and download payload via WinHTTP
        HINTERNET hSession = WinHttpOpen(L"ApexAuth-AutoUpdater/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
        if (!hSession) {
            CloseHandle(hFile);
            DeleteFileW(tempNewExe.c_str());
            response.success = false;
            response.message = "Failed to initialize WinHTTP session for download.";
            return false;
        }

        HINTERNET hConnect = WinHttpConnect(hSession, toWide(host).c_str(), port, 0);
        if (!hConnect) {
            WinHttpCloseHandle(hSession);
            CloseHandle(hFile);
            DeleteFileW(tempNewExe.c_str());
            response.success = false;
            response.message = "Failed to connect to update server host.";
            return false;
        }

        DWORD reqFlags = isHttps ? WINHTTP_FLAG_SECURE : 0;
        HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"GET", toWide(path).c_str(), NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, reqFlags);
        if (!hRequest) {
            WinHttpCloseHandle(hConnect);
            WinHttpCloseHandle(hSession);
            CloseHandle(hFile);
            DeleteFileW(tempNewExe.c_str());
            response.success = false;
            response.message = "Failed to open update request.";
            return false;
        }

        // Follow HTTP 301/302 redirects automatically (crucial for CDN redirects)
        DWORD redirectOption = WINHTTP_OPTION_REDIRECT_POLICY_ALWAYS;
        WinHttpSetOption(hRequest, WINHTTP_OPTION_REDIRECT_POLICY, &redirectOption, sizeof(redirectOption));

        DWORD secFlags = SECURITY_FLAG_IGNORE_UNKNOWN_CA | SECURITY_FLAG_IGNORE_CERT_CN_INVALID | SECURITY_FLAG_IGNORE_CERT_DATE_INVALID;
        WinHttpSetOption(hRequest, WINHTTP_OPTION_SECURITY_FLAGS, &secFlags, sizeof(secFlags));

        BOOL bSent = WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0);
        if (!bSent || !WinHttpReceiveResponse(hRequest, NULL)) {
            WinHttpCloseHandle(hRequest);
            WinHttpCloseHandle(hConnect);
            WinHttpCloseHandle(hSession);
            CloseHandle(hFile);
            DeleteFileW(tempNewExe.c_str());
            response.success = false;
            response.message = "Failed to receive response from update server.";
            return false;
        }

        // Query content length for progress calculation
        DWORD contentLength = 0;
        DWORD sizeOfCl = sizeof(contentLength);
        WinHttpQueryHeaders(hRequest, WINHTTP_QUERY_CONTENT_LENGTH | WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX, &contentLength, &sizeOfCl, WINHTTP_NO_HEADER_INDEX);

        DWORD totalDownloaded = 0;
        DWORD bytesRead = 0;
        char buffer[32768];

        while (WinHttpReadData(hRequest, buffer, sizeof(buffer), &bytesRead) && bytesRead > 0) {
            DWORD bytesWritten = 0;
            WriteFile(hFile, buffer, bytesRead, &bytesWritten, NULL);
            totalDownloaded += bytesRead;

            if (progress_callback && contentLength > 0) {
                float progress = (float)totalDownloaded / (float)contentLength;
                if (progress > 1.0f) progress = 1.0f;
                progress_callback(progress);
            }
        }

        CloseHandle(hFile);
        WinHttpCloseHandle(hRequest);
        WinHttpCloseHandle(hConnect);
        WinHttpCloseHandle(hSession);

        if (totalDownloaded == 0) {
            DeleteFileW(tempNewExe.c_str());
            response.success = false;
            response.message = "Downloaded empty binary payload from update server.";
            return false;
        }

        if (progress_callback) {
            progress_callback(1.0f);
        }

        // In-place atomic replacement:
        // 1. Rename running currentExe -> currentExe.old
        if (!MoveFileExW(currentExe, tempOldExe.c_str(), MOVEFILE_REPLACE_EXISTING)) {
            DeleteFileW(tempNewExe.c_str());
            response.success = false;
            response.message = "Failed to stage current executable for replacement (MoveFileEx error: " + std::to_string(GetLastError()) + ").";
            return false;
        }

        // 2. Rename downloaded tempNewExe -> currentExe
        if (!MoveFileExW(tempNewExe.c_str(), currentExe, MOVEFILE_REPLACE_EXISTING)) {
            // Roll back
            MoveFileExW(tempOldExe.c_str(), currentExe, MOVEFILE_REPLACE_EXISTING);
            response.success = false;
            response.message = "Failed to swap new executable into position.";
            return false;
        }

        // 3. Launch newly updated binary seamlessly and exit current process
        STARTUPINFOW si = { sizeof(si) };
        PROCESS_INFORMATION pi = { 0 };
        BOOL created = CreateProcessW(currentExe, GetCommandLineW(), NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
        if (created) {
            CloseHandle(pi.hProcess);
            CloseHandle(pi.hThread);
            ExitProcess(0);
        }

        return true;
    }
};


typedef api ApexAuthSDK;
typedef api ApexAuthApp;

} // namespace ApexAuth


