#include "RsaPrivateKey.hpp"

#include <openssl/rsa.h>
#include <openssl/evp.h>
#include <openssl/x509.h>

#include "utils/serialize/basics.hpp"


namespace drp::util::crypto {


RsaPrivateKey::RsaPrivateKey() = default;

RsaPrivateKey::RsaPrivateKey(const std::vector<std::uint8_t>& data, const int padMode) {
    this->_data = data;
    this->padMode = padMode;
}


std::vector<std::uint8_t> RsaPrivateKey::decrypt(const std::vector<std::uint8_t>& encryptedData) const {
    const auto key = this->getOpenSslKey();

    // initialize the encryption context
    const std::unique_ptr<EVP_PKEY_CTX, decltype(&EVP_PKEY_CTX_free)> context(
        EVP_PKEY_CTX_new(key.get(), nullptr),
        EVP_PKEY_CTX_free
    );

    if (context == nullptr)
        throw std::runtime_error("Could not create EVP_PKEY_CTX.");

    // initialize the encryption operation
    if (EVP_PKEY_decrypt_init(context.get()) <= 0)
        throw std::runtime_error("Could not initialize encryption.");

    // set the padding
    if (EVP_PKEY_CTX_set_rsa_padding(context.get(), RSA_PKCS1_OAEP_PADDING) <= 0)
        throw std::runtime_error("Could not set RSA padding.");

    // get the size of the output buffer
    std::size_t decryptedDataLength;
    if (EVP_PKEY_decrypt(
        context.get(),
        nullptr,
        &decryptedDataLength,
        encryptedData.data(),
        encryptedData.size()
    ) <= 0)
        throw std::runtime_error("Could not determine output length.");

    std::vector<std::uint8_t> decryptedData(decryptedDataLength);

    // encrypt the data
    if (EVP_PKEY_decrypt(
        context.get(),
        decryptedData.data(),
        &decryptedDataLength,
        encryptedData.data(),
        encryptedData.size()
    ) <= 0)
        throw std::runtime_error("Could not decrypt data.");

    decryptedData.resize(decryptedDataLength);

    return decryptedData;
}


std::vector<std::uint8_t> RsaPrivateKey::serialize() const {
    // serialize the members
    const auto serializedData = serialize::serializeVector(this->_data);
    const auto serializedPadMode = serialize::serializeObject(static_cast<std::uint8_t>(this->padMode));

    // create a buffer to store our members
    std::vector<std::uint8_t> data;

    // store our members
    data.insert(data.end(), serializedData.begin(), serializedData.end());
    data.insert(data.end(), serializedPadMode.begin(), serializedPadMode.end());

    return data;
}

RsaPrivateKey RsaPrivateKey::deserialize(std::vector<std::uint8_t>& data) {
    // deserialize the members
    const auto keyData = serialize::deserializeVector<std::uint8_t>(data);
    const auto keyPadding = static_cast<int>(serialize::deserializeObject<std::uint8_t>(data));

    return RsaPrivateKey(keyData, keyPadding);
}


[[nodiscard]] std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)> RsaPrivateKey::getOpenSslKey() const {
    // get the bio from the private key data
    const std::unique_ptr<BIO, decltype(&BIO_free)> bio(
        BIO_new_mem_buf(
            this->_data.data(),
            static_cast<int>(this->_data.size())
        ),
        BIO_free
    );
    if (bio == nullptr)
        throw std::runtime_error("Could not create BIO for private key.");

    // get the key from the bio
    EVP_PKEY* rawKey = nullptr;
    if (!d2i_PrivateKey_bio(
        bio.get(),
        &rawKey
    ))
        throw std::runtime_error("Could not deserialize RSA private key.");

    return {
        rawKey,
        EVP_PKEY_free
    };
}


}
