/*
 * RSA2 SDK for networkValide.
 *
 * Build example:
 *   g++ cpp-rsa2-sdk.cpp -lssl -lcrypto
 */

#include <openssl/pem.h>
#include <openssl/rsa.h>
#include <openssl/md5.h>
#include <iomanip>
#include <sstream>
#include <string>

static std::string md5Hex(const std::string& input) {
    unsigned char digest[MD5_DIGEST_LENGTH];
    MD5(reinterpret_cast<const unsigned char*>(input.data()), input.size(), digest);
    std::ostringstream ss;
    for (int i = 0; i < MD5_DIGEST_LENGTH; ++i) ss << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(digest[i]);
    return ss.str();
}

static std::string base64Encode(const std::string& input) {
    BIO* b64 = BIO_new(BIO_f_base64());
    BIO* mem = BIO_new(BIO_s_mem());
    BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
    BIO_push(b64, mem);
    BIO_write(b64, input.data(), static_cast<int>(input.size()));
    BIO_flush(b64);
    BUF_MEM* ptr = nullptr;
    BIO_get_mem_ptr(mem, &ptr);
    std::string out(ptr->data, ptr->length);
    BIO_free_all(b64);
    return out;
}

static std::string base64Decode(const std::string& input) {
    BIO* b64 = BIO_new(BIO_f_base64());
    BIO* mem = BIO_new_mem_buf(input.data(), static_cast<int>(input.size()));
    BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
    BIO_push(b64, mem);
    std::string out(input.size(), '\0');
    int len = BIO_read(b64, &out[0], static_cast<int>(out.size()));
    BIO_free_all(b64);
    if (len < 0) return "";
    out.resize(static_cast<size_t>(len));
    return out;
}

static RSA* loadPublicKey(const std::string& pem) {
    BIO* bio = BIO_new_mem_buf(pem.data(), static_cast<int>(pem.size()));
    RSA* rsa = PEM_read_bio_RSA_PUBKEY(bio, nullptr, nullptr, nullptr);
    BIO_free(bio);
    return rsa;
}

std::string nvRsa2EncryptData(const std::string& rawData, const std::string& publicKeyPem) {
    RSA* rsa = loadPublicKey(publicKeyPem);
    if (!rsa) return "";
    int keySize = RSA_size(rsa);
    int chunkSize = keySize - 11;
    std::string encrypted;
    std::string out(keySize, '\0');
    for (size_t i = 0; i < rawData.size(); i += chunkSize) {
        int len = static_cast<int>(std::min<size_t>(chunkSize, rawData.size() - i));
        int n = RSA_public_encrypt(len, reinterpret_cast<const unsigned char*>(rawData.data() + i),
                                   reinterpret_cast<unsigned char*>(&out[0]), rsa, RSA_PKCS1_PADDING);
        if (n <= 0) { RSA_free(rsa); return ""; }
        encrypted.append(out.data(), n);
    }
    RSA_free(rsa);
    return base64Encode(encrypted);
}

std::string nvRsa2DecryptMsg(const std::string& msg, const std::string& publicKeyPem) {
    RSA* rsa = loadPublicKey(publicKeyPem);
    if (!rsa) return "";
    std::string raw = base64Decode(msg);
    int keySize = RSA_size(rsa);
    std::string plain;
    std::string out(keySize, '\0');
    for (size_t i = 0; i < raw.size(); i += keySize) {
        int n = RSA_public_decrypt(keySize, reinterpret_cast<const unsigned char*>(raw.data() + i),
                                   reinterpret_cast<unsigned char*>(&out[0]), rsa, RSA_PKCS1_PADDING);
        if (n <= 0) { RSA_free(rsa); return ""; }
        plain.append(out.data(), n);
    }
    RSA_free(rsa);
    return plain;
}

std::string nvMakeSign(const std::string& data, const std::string& appKey) {
    return md5Hex(data + appKey);
}
