#include "verify_client.hpp"
#include <sstream>
#include <fstream>
#include <stdexcept>
#include <ctime>
#include <iomanip>
#include <random>
#include <algorithm>

#ifdef USE_OPENSSL
#include <openssl/bio.h>
#include <openssl/buffer.h>
#include <openssl/evp.h>
#include <openssl/rand.h>
#endif

namespace verify {

// ============================================================================
// Crypto Implementation
// ============================================================================

Crypto::Crypto(EncryptType type, const std::string& key)
    : type_(type), key_(key) {
}

Crypto::~Crypto() = default;

std::vector<uint8_t> Crypto::padKey(const std::vector<uint8_t>& key, size_t targetLen) {
    if (key.size() >= targetLen) {
        return std::vector<uint8_t>(key.begin(), key.begin() + targetLen);
    }

    std::vector<uint8_t> padded(targetLen);
    std::copy(key.begin(), key.end(), padded.begin());
    for (size_t i = key.size(); i < targetLen; ++i) {
        padded[i] = key[i % key.size()];
    }
    return padded;
}

std::string base64Encode(const std::vector<uint8_t>& data) {
#ifdef USE_OPENSSL
    BIO* bio = BIO_new(BIO_s_mem());
    BIO* b64 = BIO_new(BIO_f_base64());
    BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
    BIO_push(b64, bio);

    BIO_write(b64, data.data(), static_cast<int>(data.size()));
    BIO_flush(b64);

    BUF_MEM* buffer;
    BIO_get_mem_ptr(b64, &buffer);

    std::string result(buffer->data, buffer->length);
    BIO_free_all(b64);

    return result;
#else
    static const char* chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    std::string result;
    result.reserve(((data.size() + 2) / 3) * 4);

    for (size_t i = 0; i < data.size(); i += 3) {
        uint32_t n = (data[i] << 16);
        if (i + 1 < data.size()) n |= (data[i + 1] << 8);
        if (i + 2 < data.size()) n |= data[i + 2];

        result.push_back(chars[(n >> 18) & 0x3F]);
        result.push_back(chars[(n >> 12) & 0x3F]);
        result.push_back((i + 1 < data.size()) ? chars[(n >> 6) & 0x3F] : '=');
        result.push_back((i + 2 < data.size()) ? chars[n & 0x3F] : '=');
    }
    return result;
#endif
}

std::vector<uint8_t> base64Decode(const std::string& encoded) {
#ifdef USE_OPENSSL
    BIO* bio = BIO_new_mem_buf(encoded.data(), static_cast<int>(encoded.size()));
    BIO* b64 = BIO_new(BIO_f_base64());
    BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
    BIO_push(b64, bio);

    std::vector<uint8_t> result(encoded.size());
    int len = BIO_read(b64, result.data(), static_cast<int>(result.size()));
    result.resize(len > 0 ? len : 0);

    BIO_free_all(b64);
    return result;
#else
    static const int table[] = {
        -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
        -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
        -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63,
        52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-1,-1,-1,
        -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,
        15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,
        -1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,
        41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1
    };

    std::vector<uint8_t> result;
    result.reserve(encoded.size() * 3 / 4);

    int val = 0, bits = 0;
    for (char c : encoded) {
        if (c == '=') break;
        int v = table[static_cast<uint8_t>(c)];
        if (v < 0) continue;

        val = (val << 6) | v;
        bits += 6;

        if (bits >= 8) {
            bits -= 8;
            result.push_back(static_cast<uint8_t>((val >> bits) & 0xFF));
        }
    }
    return result;
#endif
}

std::string Crypto::encrypt(const std::string& plaintext) {
    if (type_ == EncryptType::None || key_.empty()) {
        return plaintext;
    }

    switch (type_) {
        case EncryptType::AES:
            return encryptAES(plaintext);
        case EncryptType::RC4:
            return encryptRC4(plaintext);
        default:
            return plaintext;
    }
}

std::string Crypto::decrypt(const std::string& ciphertext) {
    if (type_ == EncryptType::None || key_.empty()) {
        return ciphertext;
    }

    switch (type_) {
        case EncryptType::AES:
            return decryptAES(ciphertext);
        case EncryptType::RC4:
            return decryptRC4(ciphertext);
        default:
            return ciphertext;
    }
}

std::string Crypto::encryptAES(const std::string& plaintext) {
#ifdef USE_OPENSSL
    std::vector<uint8_t> keyBytes = padKey(std::vector<uint8_t>(key_.begin(), key_.end()), 32);

    EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
    if (!ctx) throw CryptoException("Failed to create cipher context");

    std::vector<uint8_t> nonce(12);
    RAND_bytes(nonce.data(), 12);

    if (EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), nullptr, nullptr, nullptr) != 1) {
        EVP_CIPHER_CTX_free(ctx);
        throw CryptoException("Failed to init encryption");
    }

    EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, 12, nullptr);
    EVP_EncryptInit_ex(ctx, nullptr, nullptr, keyBytes.data(), nonce.data());

    std::vector<uint8_t> ciphertext(plaintext.size() + 16);
    int len;
    EVP_EncryptUpdate(ctx, ciphertext.data(), &len,
                      reinterpret_cast<const uint8_t*>(plaintext.data()), plaintext.size());

    int ciphertextLen = len;
    EVP_EncryptFinal_ex(ctx, ciphertext.data() + len, &len);
    ciphertextLen += len;

    std::vector<uint8_t> tag(16);
    EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, tag.data());

    EVP_CIPHER_CTX_free(ctx);

    // nonce (12) + ciphertext + tag (16)
    std::vector<uint8_t> result;
    result.reserve(12 + ciphertextLen + 16);
    result.insert(result.end(), nonce.begin(), nonce.end());
    result.insert(result.end(), ciphertext.begin(), ciphertext.begin() + ciphertextLen);
    result.insert(result.end(), tag.begin(), tag.end());

    return base64Encode(result);
#else
    throw CryptoException("OpenSSL not available. Compile with -DUSE_OPENSSL");
#endif
}

std::string Crypto::decryptAES(const std::string& ciphertext) {
#ifdef USE_OPENSSL
    std::vector<uint8_t> data = base64Decode(ciphertext);
    if (data.size() < 28) throw CryptoException("Ciphertext too short");

    std::vector<uint8_t> keyBytes = padKey(std::vector<uint8_t>(key_.begin(), key_.end()), 32);

    std::vector<uint8_t> nonce(data.begin(), data.begin() + 12);
    std::vector<uint8_t> tag(data.end() - 16, data.end());
    std::vector<uint8_t> encrypted(data.begin() + 12, data.end() - 16);

    EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
    if (!ctx) throw CryptoException("Failed to create cipher context");

    EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), nullptr, nullptr, nullptr);
    EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, 12, nullptr);
    EVP_DecryptInit_ex(ctx, nullptr, nullptr, keyBytes.data(), nonce.data());

    std::vector<uint8_t> decrypted(encrypted.size());
    int len;
    EVP_DecryptUpdate(ctx, decrypted.data(), &len, encrypted.data(), encrypted.size());

    EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, tag.data());

    int ret = EVP_DecryptFinal_ex(ctx, decrypted.data() + len, &len);
    EVP_CIPHER_CTX_free(ctx);

    if (ret != 1) throw CryptoException("Decryption failed: authentication error");

    return std::string(decrypted.begin(), decrypted.begin() + len);
#else
    throw CryptoException("OpenSSL not available. Compile with -DUSE_OPENSSL");
#endif
}

class RC4 {
public:
    explicit RC4(const std::vector<uint8_t>& key) {
        for (int i = 0; i < 256; ++i) s_[i] = static_cast<uint8_t>(i);

        uint8_t j = 0;
        for (int i = 0; i < 256; ++i) {
            j = j + s_[i] + key[i % key.size()];
            std::swap(s_[i], s_[j]);
        }
    }

    void process(std::vector<uint8_t>& data) {
        for (auto& byte : data) {
            i_++;
            j_ += s_[i_];
            std::swap(s_[i_], s_[j_]);
            byte ^= s_[(s_[i_] + s_[j_]) & 0xFF];
        }
    }

private:
    uint8_t s_[256] = {};
    uint8_t i_ = 0, j_ = 0;
};

std::string Crypto::encryptRC4(const std::string& plaintext) {
    std::vector<uint8_t> keyBytes(key_.begin(), key_.end());
    if (keyBytes.empty()) throw CryptoException("RC4 key cannot be empty");

    std::vector<uint8_t> data(plaintext.begin(), plaintext.end());
    RC4 rc4(keyBytes);
    rc4.process(data);

    return base64Encode(data);
}

std::string Crypto::decryptRC4(const std::string& ciphertext) {
    std::vector<uint8_t> keyBytes(key_.begin(), key_.end());
    if (keyBytes.empty()) throw CryptoException("RC4 key cannot be empty");

    std::vector<uint8_t> data = base64Decode(ciphertext);
    RC4 rc4(keyBytes);
    rc4.process(data);

    return std::string(data.begin(), data.end());
}

// ============================================================================
// HttpClient Implementation
// ============================================================================

HttpClient::HttpClient() {
    curl_global_init(CURL_GLOBAL_DEFAULT);
}

HttpClient::~HttpClient() {
    curl_global_cleanup();
}

void HttpClient::setBaseUrl(const std::string& url) {
    baseUrl_ = url;
    if (!baseUrl_.empty() && baseUrl_.back() == '/') {
        baseUrl_.pop_back();
    }
}

void HttpClient::setTimeout(int seconds) {
    timeout_ = seconds;
}

void HttpClient::addHeader(const std::string& key, const std::string& value) {
    headers_[key] = value;
}

void HttpClient::clearHeaders() {
    headers_.clear();
}

void HttpClient::setCrypto(std::shared_ptr<Crypto> crypto) {
    crypto_ = crypto;
}

std::string HttpClient::buildUrl(const std::string& path) {
    return baseUrl_ + path;
}

size_t HttpClient::writeCallback(void* contents, size_t size, size_t nmemb, std::string* userp) {
    size_t totalSize = size * nmemb;
    userp->append(static_cast<char*>(contents), totalSize);
    return totalSize;
}

size_t HttpClient::writeBinaryCallback(void* contents, size_t size, size_t nmemb, std::vector<uint8_t>* userp) {
    size_t totalSize = size * nmemb;
    userp->insert(userp->end(), static_cast<uint8_t*>(contents), static_cast<uint8_t*>(contents) + totalSize);
    return totalSize;
}

json HttpClient::processResponse(const std::string& response) {
    std::string data = response;

    if (crypto_) {
        try {
            data = crypto_->decrypt(response);
            auto j = json::parse(data);
            if (j.contains("data") && j["data"].is_string()) {
                j["data"] = json::parse(crypto_->decrypt(j["data"].get<std::string>()));
            }
            return j;
        } catch (...) {
            // Try parsing as-is
        }
    }

    return json::parse(response);
}

json HttpClient::get(const std::string& path) {
    CURL* curl = curl_easy_init();
    if (!curl) throw std::runtime_error("Failed to initialize CURL");

    std::string response;
    struct curl_slist* headers = nullptr;

    for (const auto& h : headers_) {
        std::string header = h.first + ": " + h.second;
        headers = curl_slist_append(headers, header.c_str());
    }

    curl_easy_setopt(curl, CURLOPT_URL, buildUrl(path).c_str());
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
    curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout_);
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);

    CURLcode res = curl_easy_perform(curl);

    long httpCode = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (res != CURLE_OK) {
        throw std::runtime_error(std::string("CURL error: ") + curl_easy_strerror(res));
    }

    auto j = processResponse(response);

    if (j["code"].get<int>() != 200) {
        throw ApiException(j["code"].get<int>(), j["message"].get<std::string>());
    }

    return j;
}

json HttpClient::post(const std::string& path, const json& body) {
    CURL* curl = curl_easy_init();
    if (!curl) throw std::runtime_error("Failed to initialize CURL");

    std::string response;
    struct curl_slist* headers = nullptr;

    headers = curl_slist_append(headers, "Content-Type: application/json");
    for (const auto& h : headers_) {
        std::string header = h.first + ": " + h.second;
        headers = curl_slist_append(headers, header.c_str());
    }

    std::string bodyStr = body.dump();

    curl_easy_setopt(curl, CURLOPT_URL, buildUrl(path).c_str());
    curl_easy_setopt(curl, CURLOPT_POST, 1L);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, bodyStr.c_str());
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
    curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout_);
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);

    CURLcode res = curl_easy_perform(curl);

    long httpCode = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (res != CURLE_OK) {
        throw std::runtime_error(std::string("CURL error: ") + curl_easy_strerror(res));
    }

    auto j = processResponse(response);

    if (j["code"].get<int>() != 200) {
        throw ApiException(j["code"].get<int>(), j["message"].get<std::string>());
    }

    return j;
}

json HttpClient::postForm(const std::string& path, const std::map<std::string, std::string>& fields, const std::map<std::string, std::string>& files) {
    CURL* curl = curl_easy_init();
    if (!curl) throw std::runtime_error("Failed to initialize CURL");

    std::string response;
    curl_mime* mime = curl_mime_init(curl);
    struct curl_slist* headers = nullptr;

    for (const auto& h : headers_) {
        std::string header = h.first + ": " + h.second;
        headers = curl_slist_append(headers, header.c_str());
    }

    for (const auto& f : fields) {
        curl_mimepart* part = curl_mime_addpart(mime);
        curl_mime_name(part, f.first.c_str());
        curl_mime_data(part, f.second.c_str(), CURL_ZERO_TERMINATED);
    }

    for (const auto& f : files) {
        curl_mimepart* part = curl_mime_addpart(mime);
        curl_mime_name(part, f.first.c_str());
        curl_mime_filedata(part, f.second.c_str());
    }

    curl_easy_setopt(curl, CURLOPT_URL, buildUrl(path).c_str());
    curl_easy_setopt(curl, CURLOPT_MIMEPOST, mime);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
    curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout_);
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);

    CURLcode res = curl_easy_perform(curl);

    curl_mime_free(mime);
    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (res != CURLE_OK) {
        throw std::runtime_error(std::string("CURL error: ") + curl_easy_strerror(res));
    }

    auto j = processResponse(response);

    if (j["code"].get<int>() != 200) {
        throw ApiException(j["code"].get<int>(), j["message"].get<std::string>());
    }

    return j;
}

std::vector<uint8_t> HttpClient::download(const std::string& path) {
    CURL* curl = curl_easy_init();
    if (!curl) throw std::runtime_error("Failed to initialize CURL");

    std::vector<uint8_t> response;
    struct curl_slist* headers = nullptr;

    for (const auto& h : headers_) {
        std::string header = h.first + ": " + h.second;
        headers = curl_slist_append(headers, header.c_str());
    }

    curl_easy_setopt(curl, CURLOPT_URL, buildUrl(path).c_str());
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeBinaryCallback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
    curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout_);
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);

    CURLcode res = curl_easy_perform(curl);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (res != CURLE_OK) {
        throw std::runtime_error(std::string("CURL error: ") + curl_easy_strerror(res));
    }

    return response;
}

// ============================================================================
// Client Implementation
// ============================================================================

Client::Client(const std::string& baseUrl, const std::string& appKey)
    : baseUrl_(baseUrl), appKey_(appKey) {
    http_ = std::make_shared<HttpClient>();
    http_->setBaseUrl(baseUrl_);
}

Client::~Client() = default;

void Client::setToken(const std::string& token) {
    token_ = token;
    http_->addHeader("Authorization", "Bearer " + token_);
}

void Client::setEncryptType(EncryptType type, const std::string& key) {
    encryptType_ = type;
    crypto_ = std::make_shared<Crypto>(type, key);
    http_->setCrypto(crypto_);
}

void Client::setTimeout(int seconds) {
    http_->setTimeout(seconds);
}

std::string Client::buildPath(const std::string& endpoint) {
    return "/api/v1/app/" + appKey_ + endpoint;
}

AppConfig Client::getAppInfo() {
    auto j = http_->get(buildPath("/info"));

    AppConfig config;
    config.id = j["data"]["id"].get<uint64_t>();
    config.name = j["data"]["name"].get<std::string>();
    config.description = j["data"]["description"].get<std::string>();
    config.iconUrl = j["data"]["icon_url"].get<std::string>();
    config.status = j["data"]["status"].get<std::string>();
    config.billingType = j["data"]["billing_type"].get<std::string>();
    config.loginPolicy = j["data"]["login_policy"].get<std::string>();
    config.maxDevices = j["data"]["max_devices"].get<int>();
    config.multiOpenMode = j["data"]["multi_open_mode"].get<std::string>();
    config.maxInstances = j["data"]["max_instances"].get<int>();
    config.enableTrial = j["data"]["enable_trial"].get<bool>();
    config.trialBalance = j["data"]["trial_balance"].get<double>();
    config.heartbeatInterval = j["data"]["heartbeat_interval"].get<int>();
    config.heartbeatTimeout = j["data"]["heartbeat_timeout"].get<int>();

    return config;
}

VersionInfo Client::checkUpdate(const std::string& version) {
    std::string path = buildPath("/check-update");
    if (!version.empty()) {
        path += "?version=" + version;
    }

    auto j = http_->get(path);

    VersionInfo info;
    info.hasUpdate = j["data"]["has_update"].get<bool>();
    info.latestVersion = j["data"]["latest_version"].get<std::string>();
    info.downloadUrl = j["data"]["download_url"].get<std::string>();
    info.fileSize = j["data"]["file_size"].get<int64_t>();
    info.fileHash = j["data"]["file_hash"].get<std::string>();
    info.entryFile = j["data"]["entry_file"].get<std::string>();
    info.updateNotes = j["data"]["update_notes"].get<std::string>();
    info.updateStrategy = j["data"]["update_strategy"].get<std::string>();
    info.updateType = j["data"]["update_type"].get<std::string>();
    info.updateMethod = j["data"]["update_method"].get<std::string>();

    return info;
}

std::vector<json> Client::getAnnouncements() {
    auto j = http_->get(buildPath("/announcements"));
    return j["data"].get<std::vector<json>>();
}

uint64_t Client::registerUser(const std::string& username,
                               const std::string& password,
                               const std::string& deviceId,
                               const std::string& deviceName,
                               const std::string& deviceType,
                               const std::string& instanceId,
                               const std::string& email,
                               const std::string& emailCode) {
    json body;
    body["username"] = username;
    body["password"] = password;
    body["device_id"] = deviceId;

    if (!deviceName.empty()) body["device_name"] = deviceName;
    if (!deviceType.empty()) body["device_type"] = deviceType;
    if (!instanceId.empty()) body["instance_id"] = instanceId;
    if (!email.empty()) body["email"] = email;
    if (!emailCode.empty()) body["email_code"] = emailCode;

    auto j = http_->post(buildPath("/register"), body);
    return j["data"]["user_id"].get<uint64_t>();
}

json Client::login(const std::string& username,
                   const std::string& password,
                   const std::string& deviceId,
                   const std::string& deviceName,
                   const std::string& deviceType,
                   const std::string& instanceId) {
    json body;
    body["username"] = username;
    body["password"] = password;
    body["device_id"] = deviceId;

    if (!deviceName.empty()) body["device_name"] = deviceName;
    if (!deviceType.empty()) body["device_type"] = deviceType;
    if (!instanceId.empty()) body["instance_id"] = instanceId;

    auto j = http_->post(buildPath("/login"), body);

    // Auto-set token
    if (j["data"].contains("token")) {
        setToken(j["data"]["token"].get<std::string>());
    }

    return j["data"];
}

void Client::sendEmailCode(const std::string& email, const std::string& purpose) {
    json body;
    body["email"] = email;
    body["purpose"] = purpose;

    http_->post(buildPath("/send-email-code"), body);
}

void Client::resetPassword(const std::string& email, const std::string& code, const std::string& newPassword) {
    json body;
    body["email"] = email;
    body["code"] = code;
    body["password"] = newPassword;

    http_->post(buildPath("/reset-password"), body);
}

void Client::changePassword(const std::string& username, const std::string& oldPassword, const std::string& newPassword) {
    json body;
    body["username"] = username;
    body["old_password"] = oldPassword;
    body["new_password"] = newPassword;

    http_->post(buildPath("/change-password"), body);
}

UserInfo Client::getAccount(uint64_t userId) {
    json body;
    body["user_id"] = userId;

    auto j = http_->post(buildPath("/account"), body);

    UserInfo info;
    info.userId = j["data"]["user_id"].get<uint64_t>();
    info.username = j["data"]["username"].get<std::string>();
    info.balance = j["data"]["balance"].get<double>();
    info.status = j["data"]["status"].get<std::string>();

    return info;
}

json Client::heartbeat(uint64_t userId, const std::string& deviceId, const std::string& instanceId) {
    json body;
    body["user_id"] = userId;
    body["device_id"] = deviceId;
    if (!instanceId.empty()) body["instance_id"] = instanceId;

    return http_->post(buildPath("/heartbeat"), body)["data"];
}

json Client::recharge(const std::string& username, const std::string& cardKey, const std::string& deviceId) {
    json body;
    body["username"] = username;
    body["card_key"] = cardKey;
    body["device_id"] = deviceId;

    return http_->post(buildPath("/recharge"), body)["data"];
}

json Client::trial(uint64_t userId) {
    json body;
    body["user_id"] = userId;

    return http_->post(buildPath("/trial"), body)["data"];
}

std::vector<DeviceInfo> Client::getDevices() {
    auto j = http_->get(buildPath("/devices"));

    std::vector<DeviceInfo> devices;
    for (const auto& d : j["data"]) {
        DeviceInfo info;
        info.id = d["id"].get<uint64_t>();
        info.deviceId = d["device_id"].get<std::string>();
        info.deviceName = d["device_name"].get<std::string>();
        info.deviceType = d["device_type"].get<std::string>();
        info.status = d["status"].get<std::string>();
        info.onlineSessions = d["online_sessions"].get<int>();
        info.createdAt = d["created_at"].get<std::string>();
        devices.push_back(info);
    }

    return devices;
}

json Client::getDeviceCount(uint64_t userId) {
    json body;
    body["user_id"] = userId;

    return http_->post(buildPath("/device-count"), body)["data"];
}

void Client::unbindDevice(uint64_t userId, const std::string& deviceId) {
    json body;
    body["user_id"] = userId;
    body["device_id"] = deviceId;

    http_->post(buildPath("/unbind-device"), body);
}

void Client::unbindDeviceWithAuth(const std::string& username, const std::string& password, const std::string& deviceId) {
    json body;
    body["username"] = username;
    body["password"] = password;
    body["device_id"] = deviceId;

    http_->post(buildPath("/unbind-device-with-auth"), body);
}

std::vector<json> Client::getInstances(uint64_t userId) {
    json body;
    body["user_id"] = userId;

    return http_->post(buildPath("/instances"), body)["data"].get<std::vector<json>>();
}

void Client::forceOfflineInstance(uint64_t userId, const std::string& instanceId) {
    json body;
    body["user_id"] = userId;

    http_->post(buildPath("/instances/" + instanceId + "/offline"), body);
}

json Client::getConstants() {
    return http_->get(buildPath("/constants"))["data"];
}

json Client::getConstant(const std::string& key) {
    return http_->get(buildPath("/constants/" + key))["data"];
}

std::vector<uint8_t> Client::downloadConstant(const std::string& key) {
    return http_->download(buildPath("/constants/" + key + "/download"));
}

json Client::getVariables() {
    return http_->get(buildPath("/variables"))["data"];
}

json Client::getVariable(const std::string& key) {
    return http_->get(buildPath("/variables/" + key))["data"];
}

std::vector<uint8_t> Client::downloadVariable(const std::string& key) {
    return http_->download(buildPath("/variables/" + key + "/download"));
}

json Client::uploadVariable(const std::string& key, const std::string& filePath) {
    return http_->postForm(buildPath("/variables/" + key + "/upload"), {}, {{"file", filePath}})["data"];
}

void Client::updateVariables(const std::map<std::string, std::string>& variables) {
    json body;
    body["variables"] = variables;
    http_->post(buildPath("/variables"), body);
}

json Client::createVariableRecord(const std::string& key, const json& data) {
    return http_->post(buildPath("/variables/" + key + "/records"), data)["data"];
}

json Client::getVariableRecords(const std::string& key, int page, int pageSize) {
    std::string path = buildPath("/variables/" + key + "/records") +
                       "?page=" + std::to_string(page) + "&page_size=" + std::to_string(pageSize);
    return http_->get(path)["data"];
}

void Client::deleteVariableRecord(const std::string& key, uint64_t recordId) {
    http_->get(buildPath("/variables/" + key + "/records/" + std::to_string(recordId)));
}

json Client::executeDynamicCode(const std::string& key, const json& params, uint64_t targetUserId) {
    json body;
    body["params"] = params;
    if (targetUserId > 0) {
        body["user_id"] = targetUserId;
    }

    return http_->post(buildPath("/dynamic-code/" + key + "/execute"), body)["data"];
}

} // namespace verify
