/*
 * AES API SDK for networkValide.
 *
 * Build example:
 *   g++ cpp-aes-sdk.cpp -lssl -lcrypto
 *
 * The API uses AES-256-CBC for encrypted applications:
 *   data = base64(iv + ciphertext)
 *   key  = sha256(AES key, binary)
 *   sign = md5(data + appKey)
 */

#include <openssl/evp.h>
#include <openssl/md5.h>
#include <openssl/rand.h>
#include <openssl/sha.h>
#include <openssl/buffer.h>

#include <ctime>
#include <iomanip>
#include <sstream>
#include <string>
#include <vector>

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::vector<unsigned char> sha256Bytes(const std::string& input) {
    std::vector<unsigned char> digest(SHA256_DIGEST_LENGTH);
    SHA256(reinterpret_cast<const unsigned char*>(input.data()), input.size(), digest.data());
    return digest;
}

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;
}

std::string nvAesEncrypt(const std::string& plain, const std::string& aesKey) {
    unsigned char iv[16];
    if (RAND_bytes(iv, sizeof(iv)) != 1) return "";

    std::vector<unsigned char> key = sha256Bytes(aesKey);
    EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
    if (!ctx) return "";

    std::string cipher(plain.size() + EVP_MAX_BLOCK_LENGTH, '\0');
    int len = 0;
    int total = 0;

    if (EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr, key.data(), iv) != 1 ||
        EVP_EncryptUpdate(ctx, reinterpret_cast<unsigned char*>(&cipher[0]), &len,
                          reinterpret_cast<const unsigned char*>(plain.data()), static_cast<int>(plain.size())) != 1) {
        EVP_CIPHER_CTX_free(ctx);
        return "";
    }
    total = len;

    if (EVP_EncryptFinal_ex(ctx, reinterpret_cast<unsigned char*>(&cipher[0]) + total, &len) != 1) {
        EVP_CIPHER_CTX_free(ctx);
        return "";
    }
    total += len;
    EVP_CIPHER_CTX_free(ctx);

    std::string raw(reinterpret_cast<char*>(iv), sizeof(iv));
    raw.append(cipher.data(), static_cast<size_t>(total));
    return base64Encode(raw);
}

std::string nvAesDecrypt(const std::string& encrypted, const std::string& aesKey) {
    std::string raw = base64Decode(encrypted);
    if (raw.size() <= 16) return "";

    const unsigned char* iv = reinterpret_cast<const unsigned char*>(raw.data());
    const unsigned char* cipher = reinterpret_cast<const unsigned char*>(raw.data() + 16);
    int cipherLen = static_cast<int>(raw.size() - 16);

    std::vector<unsigned char> key = sha256Bytes(aesKey);
    EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
    if (!ctx) return "";

    std::string plain(static_cast<size_t>(cipherLen) + EVP_MAX_BLOCK_LENGTH, '\0');
    int len = 0;
    int total = 0;

    if (EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr, key.data(), iv) != 1 ||
        EVP_DecryptUpdate(ctx, reinterpret_cast<unsigned char*>(&plain[0]), &len, cipher, cipherLen) != 1) {
        EVP_CIPHER_CTX_free(ctx);
        return "";
    }
    total = len;

    if (EVP_DecryptFinal_ex(ctx, reinterpret_cast<unsigned char*>(&plain[0]) + total, &len) != 1) {
        EVP_CIPHER_CTX_free(ctx);
        return "";
    }
    total += len;
    EVP_CIPHER_CTX_free(ctx);

    plain.resize(static_cast<size_t>(total));
    return plain;
}

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