#include "HybridHmac.hpp"
#include <NitroModules/ArrayBuffer.hpp>

#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <openssl/params.h>
#include <openssl/core_names.h>

namespace margelo::nitro::nitrokryptom {

std::shared_ptr<Promise<std::shared_ptr<ArrayBuffer>>> HybridHmac::sign(HmacAlgorithmHybridSpec algorithm, const std::shared_ptr<ArrayBuffer>& data, const std::shared_ptr<ArrayBuffer>& key) {
    char algId[16];
    switch (algorithm) {
        case HmacAlgorithmHybridSpec::HMACSHA256:
            strcpy(algId, "SHA-256");
            break;
        case HmacAlgorithmHybridSpec::HMACSHA512:
            strcpy(algId, "SHA-512");
            break;
    }
    std::unique_ptr<OSSL_LIB_CTX, decltype(&OSSL_LIB_CTX_free)> library_context(OSSL_LIB_CTX_new(), OSSL_LIB_CTX_free);
    std::unique_ptr<EVP_MAC, decltype(&EVP_MAC_free)> mac_context(EVP_MAC_fetch(library_context.get(), "HMAC", nullptr), EVP_MAC_free);
    std::unique_ptr<EVP_MAC_CTX, decltype(&EVP_MAC_CTX_free)> ctx(EVP_MAC_CTX_new(mac_context.get()), EVP_MAC_CTX_free);
    OSSL_PARAM params[2];
    params[0] = OSSL_PARAM_construct_utf8_string(OSSL_MAC_PARAM_DIGEST, algId, 0);
    params[1] = OSSL_PARAM_construct_end();
    if (EVP_MAC_init(ctx.get(), key->data(), key->size(), params) != 1) {
        throw std::runtime_error("EVP_MAC_init failed");
    }
    if (EVP_MAC_update(ctx.get(), data->data(), data->size()) != 1) {
        throw std::runtime_error("EVP_MAC_update failed");
    }
    size_t outLen = 0;
    if (EVP_MAC_final(ctx.get(), nullptr, &outLen, 0) != 1 || outLen <= 0) {
        throw std::runtime_error("EVP_MAC_final get len failed");
    }
    uint8_t* resBuffer = new uint8_t[outLen];
    size_t updatedOutLen = 0;
    if (EVP_MAC_final(ctx.get(), resBuffer, &updatedOutLen, outLen) != 1 || outLen != updatedOutLen) {
        delete[] resBuffer;
        throw std::runtime_error("EVP_MAC_final failed");
    }
    return Promise<std::shared_ptr<ArrayBuffer>>::resolved(std::make_shared<margelo::nitro::NativeArrayBuffer>(resBuffer, outLen, [=]() { delete[] resBuffer; }));
}

}
