#include "RsaKeyPair.hpp"

#include <openssl/x509.h>


namespace drp::util::crypto {


RsaKeyPair::RsaKeyPair(const std::size_t size, const int padMode) {
    // create the context
    const std::unique_ptr<EVP_PKEY_CTX, decltype(&EVP_PKEY_CTX_free)> context(
        EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, nullptr),
        EVP_PKEY_CTX_free
    );

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

    if (EVP_PKEY_keygen_init(context.get()) <= 0)
        throw std::runtime_error("Could not initialize the EVP context.");

    // configure the context
    if (EVP_PKEY_CTX_set_rsa_keygen_bits(context.get(), static_cast<int>(size)) <= 0)
        throw std::runtime_error("Error setting RSA key size.");

    // create the key pair
    EVP_PKEY* rawKeyPair = nullptr;
    if (EVP_PKEY_keygen(context.get(), &rawKeyPair) <= 0)
        throw std::runtime_error("Could not generate RSA key pair.");
    if (rawKeyPair == nullptr)
        throw std::runtime_error("Could not generate RSA key pair.");

    std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)> keyPair(
        rawKeyPair,
        EVP_PKEY_free
    );

    // extract the private key
    const std::unique_ptr<BIO, decltype(&BIO_free)> privateBio(
        BIO_new(BIO_s_mem()),
        BIO_free
    );
    if (privateBio == nullptr)
        throw std::runtime_error("Could not create RSA private key.");

    if (!i2d_PrivateKey_bio(
        privateBio.get(),
        keyPair.get()
    ))
        throw std::runtime_error("Could not generate RSA private key.");

    std::vector<std::uint8_t> privateKeyData(BIO_pending(privateBio.get()));
    if (BIO_read(
        privateBio.get(),
        privateKeyData.data(),
        static_cast<int>(privateKeyData.size())
    ) <= 0)
        throw std::runtime_error("Could not read RSA private key.");

    this->privateKey = RsaPrivateKey(privateKeyData, padMode);

    // extract the public key
    const std::unique_ptr<BIO, decltype(&BIO_free)> publicBio(
        BIO_new(BIO_s_mem()),
        BIO_free
    );
    if (publicBio == nullptr)
        throw std::runtime_error("Could not create RSA public key.");

    if (!i2d_PUBKEY_bio(
        publicBio.get(),
        keyPair.get()
    ))
        throw std::runtime_error("Could not generate RSA public key.");

    std::vector<std::uint8_t> publicKeyData(BIO_pending(publicBio.get()));
    if (BIO_read(
        publicBio.get(),
        publicKeyData.data(),
        static_cast<int>(publicKeyData.size())
    ) <= 0)
        throw std::runtime_error("Could not read RSA public key.");

    this->publicKey = RsaPublicKey(publicKeyData, padMode);
}


RsaPublicKey RsaKeyPair::getPublicKey() const {
    return this->publicKey;
}


RsaPrivateKey RsaKeyPair::getPrivateKey() const {
    return this->privateKey;
}


}
