#include <cstdio>
#include <mutex>
#include <stdexcept>

#include "../utils/base64.h"
#include "HybridKeyObjectHandle.hpp"
#include "QuickCryptoUtils.hpp"
#include <openssl/bn.h>
#include <openssl/core_names.h>
#include <openssl/crypto.h>
#include <openssl/ec.h>
#include <openssl/evp.h>
#include <openssl/obj_mac.h>
#include <openssl/provider.h>
#include <openssl/rsa.h>

namespace margelo::nitro::crypto {

#if OPENSSL_VERSION_NUMBER >= 0x30600000L
// Configure loaded providers to prefer seed-only PKCS#8 output for ML-DSA /
// ML-KEM, falling back to priv-only when no seed is available. Without this,
// OpenSSL defaults to "seed-priv" — a longer encoding that bundles both —
// which breaks interop with Node and the exact-length export check in subtle.ts.
// Mirrors src/crypto/crypto_util.cc in Node.
static void configurePqcOutputFormats() {
  static std::once_flag once;
  std::call_once(once, []() {
    OSSL_PROVIDER_do_all(
        nullptr,
        [](OSSL_PROVIDER* provider, void*) -> int {
          OSSL_PROVIDER_add_conf_parameter(provider, "ml-kem.output_formats", "seed-only,priv-only");
          OSSL_PROVIDER_add_conf_parameter(provider, "ml-dsa.output_formats", "seed-only,priv-only");
          OSSL_PROVIDER_add_conf_parameter(provider, "slh-dsa.output_formats", "seed-only,priv-only");
          return 1;
        },
        nullptr);
  });
}
#endif

HybridKeyObjectHandle::HybridKeyObjectHandle() : HybridObject(TAG) {
#if OPENSSL_VERSION_NUMBER >= 0x30600000L
  // Configure once on first handle construction. Providers are guaranteed
  // loaded by this point (any prior crypto op routed through ncrypto), and
  // the call_once flag makes subsequent constructions cheap.
  configurePqcOutputFormats();
#endif
}

// Helper functions for base64url encoding/decoding with BIGNUMs
static std::string bn_to_base64url(const BIGNUM* bn, size_t expected_size = 0) {
  if (!bn)
    return "";

  int num_bytes = BN_num_bytes(bn);
  if (num_bytes == 0)
    return "";

  // If expected_size is provided and larger than num_bytes, pad with leading zeros
  size_t buffer_size =
      (expected_size > 0 && expected_size > static_cast<size_t>(num_bytes)) ? expected_size : static_cast<size_t>(num_bytes);

  std::vector<unsigned char> buffer(buffer_size, 0);

  // BN_bn2bin writes to the end of the buffer if it's larger than needed
  size_t offset = buffer_size - num_bytes;
  BN_bn2bin(bn, buffer.data() + offset);

  // Return clean base64url - RFC 7517 compliant (no padding characters)
  return base64_encode<std::string>(buffer.data(), buffer.size(), true);
}

// Helper to add padding to base64url strings
static std::string add_base64_padding(const std::string& b64) {
  std::string padded = b64;
  // Base64 strings should be a multiple of 4 characters
  // Add '=' padding to make it so
  while (padded.length() % 4 != 0) {
    padded += '=';
  }
  return padded;
}

static BIGNUM* base64url_to_bn(const std::string& b64) {
  if (b64.empty())
    return nullptr;

  try {
    // Strip trailing periods (some JWK implementations use '.' as padding)
    std::string cleaned = b64;
    while (!cleaned.empty() && cleaned.back() == '.') {
      cleaned.pop_back();
    }

    // Add padding if needed for base64url
    std::string padded = add_base64_padding(cleaned);
    std::string decoded = base64_decode<std::string>(padded, false);
    if (decoded.empty())
      return nullptr;

    return BN_bin2bn(reinterpret_cast<const unsigned char*>(decoded.data()), static_cast<int>(decoded.size()), nullptr);
  } catch (const std::exception& e) {
    throw std::runtime_error(std::string("Input is not valid base64-encoded data."));
  }
}

static std::string base64url_encode(const unsigned char* data, size_t len) {
  return base64_encode<std::string>(data, len, true);
}

static std::string base64url_decode(const std::string& input) {
  // Strip trailing periods (some JWK implementations use '.' as padding)
  std::string cleaned = input;
  while (!cleaned.empty() && cleaned.back() == '.') {
    cleaned.pop_back();
  }

  // Add padding if needed for base64url
  std::string padded = add_base64_padding(cleaned);
  return base64_decode<std::string>(padded, false);
}

std::shared_ptr<ArrayBuffer> HybridKeyObjectHandle::exportKey(std::optional<KFormatType> format, std::optional<KeyEncoding> type,
                                                              const std::optional<std::string>& cipher,
                                                              const std::optional<std::shared_ptr<ArrayBuffer>>& passphrase) {
  auto keyType = data_.GetKeyType();

  // Copy to avoid JSI ArrayBuffer GC issues. See #645.
  if (keyType == KeyType::SECRET) {
    return ToNativeArrayBuffer(data_.GetSymmetricKey());
  }

  // Handle asymmetric keys (public/private)
  if (keyType == KeyType::PUBLIC || keyType == KeyType::PRIVATE) {
    const auto& pkey = data_.GetAsymmetricKey();
    if (!pkey) {
      throw std::runtime_error("Invalid asymmetric key");
    }

    int keyId = EVP_PKEY_id(pkey.get());

    // For curve keys (X25519, X448, Ed25519, Ed448), use raw format if no format specified
    bool isCurveKey = (keyId == EVP_PKEY_X25519 || keyId == EVP_PKEY_X448 || keyId == EVP_PKEY_ED25519 || keyId == EVP_PKEY_ED448);

    // If no format specified and it's a curve key, export as raw
    if (!format.has_value() && !type.has_value() && isCurveKey) {
      if (keyType == KeyType::PUBLIC) {
        auto rawData = pkey.rawPublicKey();
        if (!rawData) {
          throw std::runtime_error("Failed to get raw public key");
        }
        return ToNativeArrayBuffer(std::string(reinterpret_cast<const char*>(rawData.get()), rawData.size()));
      } else {
        auto rawData = pkey.rawPrivateKey();
        if (!rawData) {
          throw std::runtime_error("Failed to get raw private key");
        }
        return ToNativeArrayBuffer(std::string(reinterpret_cast<const char*>(rawData.get()), rawData.size()));
      }
    }

    // For EC keys, handle raw format (uncompressed point)
    if (!format.has_value() && !type.has_value() && keyId == EVP_PKEY_EC && keyType == KeyType::PUBLIC) {
      size_t len = 0;
      if (EVP_PKEY_get_octet_string_param(pkey.get(), OSSL_PKEY_PARAM_PUB_KEY, nullptr, 0, &len) != 1 || len == 0)
        throw std::runtime_error("Failed to get EC public key size");
      std::vector<uint8_t> buf(len);
      if (EVP_PKEY_get_octet_string_param(pkey.get(), OSSL_PKEY_PARAM_PUB_KEY, buf.data(), buf.size(), &len) != 1)
        throw std::runtime_error("Failed to get EC public key");
      return ToNativeArrayBuffer(std::string(reinterpret_cast<const char*>(buf.data()), len));
    }

#if OPENSSL_VERSION_NUMBER >= 0x30500000L
    if (!format.has_value() && !type.has_value()) {
      const char* typeName = EVP_PKEY_get0_type_name(pkey.get());
      if (typeName != nullptr) {
        std::string name(typeName);
        bool isPqcKey = (name.starts_with("ML-KEM-") || name.starts_with("ML-DSA-") || name.starts_with("SLH-DSA-"));
        if (isPqcKey) {
          if (keyType == KeyType::PUBLIC) {
            auto rawData = pkey.rawPublicKey();
            if (!rawData) {
              throw std::runtime_error("Failed to get raw PQC public key");
            }
            return ToNativeArrayBuffer(std::string(reinterpret_cast<const char*>(rawData.get()), rawData.size()));
          } else {
            auto rawData = pkey.rawSeed();
            if (!rawData) {
              throw std::runtime_error("Failed to get raw PQC seed");
            }
            return ToNativeArrayBuffer(std::string(reinterpret_cast<const char*>(rawData.get()), rawData.size()));
          }
        }
      }
    }
#endif

    // Set default format and type if not provided
    auto exportFormat = format.value_or(KFormatType::DER);
    auto exportType = type.value_or(keyType == KeyType::PUBLIC ? KeyEncoding::SPKI : KeyEncoding::PKCS8);

    // If SPKI is requested, export as public key (works for both public and private keys)
    // This allows extracting the public key from a private key
    bool exportAsPublic = (exportType == KeyEncoding::SPKI) || (keyType == KeyType::PUBLIC);

    // Create encoding config
    if (exportAsPublic) {
      ncrypto::EVPKeyPointer::PublicKeyEncodingConfig config(false, static_cast<ncrypto::EVPKeyPointer::PKFormatType>(exportFormat),
                                                             static_cast<ncrypto::EVPKeyPointer::PKEncodingType>(exportType));

      auto result = pkey.writePublicKey(config);
      if (!result) {
        throw std::runtime_error("Failed to export public key");
      }

      auto bio = std::move(result.value);
      BUF_MEM* bptr = bio;
      return ToNativeArrayBuffer(std::string(bptr->data, bptr->length));
    } else {
      ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig config(false, static_cast<ncrypto::EVPKeyPointer::PKFormatType>(exportFormat),
                                                              static_cast<ncrypto::EVPKeyPointer::PKEncodingType>(exportType));

      // Handle cipher and passphrase for encrypted private keys
      if (cipher.has_value()) {
        const EVP_CIPHER* evp_cipher = EVP_get_cipherbyname(cipher.value().c_str());
        if (!evp_cipher) {
          throw std::runtime_error("Unknown cipher: " + cipher.value());
        }
        config.cipher = evp_cipher;
      }

      if (passphrase.has_value()) {
        auto& passphrase_ptr = passphrase.value();
        config.passphrase =
            std::make_optional(ncrypto::DataPointer::Copy(ncrypto::Buffer<const void>{passphrase_ptr->data(), passphrase_ptr->size()}));
      }

      auto result = pkey.writePrivateKey(config);
      if (!result) {
        throw std::runtime_error("Failed to export private key");
      }

      auto bio = std::move(result.value);
      BUF_MEM* bptr = bio;
      return ToNativeArrayBuffer(std::string(bptr->data, bptr->length));
    }
  }

  throw std::runtime_error("Unsupported key type for export");
}

JWK HybridKeyObjectHandle::exportJwk(const JWK& key, bool handleRsaPss) {
  JWK result = key;
  auto keyType = data_.GetKeyType();

  // Handle secret keys (AES, HMAC)
  if (keyType == KeyType::SECRET) {
    auto symKey = data_.GetSymmetricKey();
    result.kty = JWKkty::OCT;
    // RFC 7517 compliant base64url encoding (no padding characters)
    result.k = base64url_encode(reinterpret_cast<const unsigned char*>(symKey->data()), symKey->size());
    return result;
  }

  // Handle asymmetric keys (RSA, EC)
  const auto& pkey = data_.GetAsymmetricKey();
  if (!pkey) {
    throw std::runtime_error("Invalid key for JWK export");
  }

  int keyId = EVP_PKEY_id(pkey.get());

  // Export RSA keys
  if (keyId == EVP_PKEY_RSA || keyId == EVP_PKEY_RSA_PSS) {
    const RSA* rsa = EVP_PKEY_get0_RSA(pkey.get());
    if (!rsa)
      throw std::runtime_error("Failed to get RSA key");

    result.kty = JWKkty::RSA;

    const BIGNUM *n_bn, *e_bn, *d_bn, *p_bn, *q_bn, *dmp1_bn, *dmq1_bn, *iqmp_bn;
    RSA_get0_key(rsa, &n_bn, &e_bn, &d_bn);
    RSA_get0_factors(rsa, &p_bn, &q_bn);
    RSA_get0_crt_params(rsa, &dmp1_bn, &dmq1_bn, &iqmp_bn);

    // Public components (always present)
    if (n_bn)
      result.n = bn_to_base64url(n_bn);
    if (e_bn)
      result.e = bn_to_base64url(e_bn);

    // Private components (only for private keys)
    if (keyType == KeyType::PRIVATE) {
      if (d_bn)
        result.d = bn_to_base64url(d_bn);
      if (p_bn)
        result.p = bn_to_base64url(p_bn);
      if (q_bn)
        result.q = bn_to_base64url(q_bn);
      if (dmp1_bn)
        result.dp = bn_to_base64url(dmp1_bn);
      if (dmq1_bn)
        result.dq = bn_to_base64url(dmq1_bn);
      if (iqmp_bn)
        result.qi = bn_to_base64url(iqmp_bn);
    }

    return result;
  }

  // Export EC keys
  if (keyId == EVP_PKEY_EC) {
    char curve_name_buf[64];
    size_t name_len = 0;
    if (EVP_PKEY_get_utf8_string_param(pkey.get(), OSSL_PKEY_PARAM_GROUP_NAME, curve_name_buf, sizeof(curve_name_buf), &name_len) != 1)
      throw std::runtime_error("Failed to get EC group name");

    std::string curve_name(curve_name_buf, name_len);

    int bits = EVP_PKEY_bits(pkey.get());
    if (bits <= 0)
      throw std::runtime_error("Failed to get EC key size");
    size_t field_size = (static_cast<size_t>(bits) + 7) / 8;

    result.kty = JWKkty::EC;

    // Map OpenSSL curve names to JWK curve names
    if (curve_name == "prime256v1") {
      result.crv = "P-256";
    } else if (curve_name == "secp384r1") {
      result.crv = "P-384";
    } else if (curve_name == "secp521r1") {
      result.crv = "P-521";
    } else {
      result.crv = curve_name;
    }

    BIGNUM* x_bn = nullptr;
    BIGNUM* y_bn = nullptr;
    if (EVP_PKEY_get_bn_param(pkey.get(), OSSL_PKEY_PARAM_EC_PUB_X, &x_bn) != 1 ||
        EVP_PKEY_get_bn_param(pkey.get(), OSSL_PKEY_PARAM_EC_PUB_Y, &y_bn) != 1) {
      BN_free(x_bn);
      BN_free(y_bn);
      throw std::runtime_error("Failed to get EC public key coordinates");
    }
    result.x = bn_to_base64url(x_bn, field_size);
    result.y = bn_to_base64url(y_bn, field_size);
    BN_free(x_bn);
    BN_free(y_bn);

    // Export private key if this is a private key
    if (keyType == KeyType::PRIVATE) {
      BIGNUM* priv_bn = nullptr;
      if (EVP_PKEY_get_bn_param(pkey.get(), OSSL_PKEY_PARAM_PRIV_KEY, &priv_bn) == 1 && priv_bn) {
        result.d = bn_to_base64url(priv_bn, field_size);
        BN_free(priv_bn);
      }
    }

    return result;
  }

  // Export OKP keys (Ed25519, Ed448, X25519, X448) per RFC 8037
  if (keyId == EVP_PKEY_ED25519 || keyId == EVP_PKEY_ED448 || keyId == EVP_PKEY_X25519 || keyId == EVP_PKEY_X448) {
    result.kty = JWKkty::OKP;

    switch (keyId) {
      case EVP_PKEY_ED25519:
        result.crv = "Ed25519";
        break;
      case EVP_PKEY_ED448:
        result.crv = "Ed448";
        break;
      case EVP_PKEY_X25519:
        result.crv = "X25519";
        break;
      case EVP_PKEY_X448:
        result.crv = "X448";
        break;
      default:
        break;
    }

    auto pubKey = pkey.rawPublicKey();
    if (!pubKey) {
      throw std::runtime_error("Failed to get raw public key for OKP JWK export");
    }
    result.x = base64url_encode(reinterpret_cast<const unsigned char*>(pubKey.get()), pubKey.size());

    if (keyType == KeyType::PRIVATE) {
      auto privKey = pkey.rawPrivateKey();
      if (!privKey) {
        throw std::runtime_error("Failed to get raw private key for OKP JWK export");
      }
      result.d = base64url_encode(reinterpret_cast<const unsigned char*>(privKey.get()), privKey.size());
    }

    return result;
  }

#if OPENSSL_VERSION_NUMBER >= 0x30500000L
  // Export AKP keys (ML-DSA, ML-KEM)
  {
    const char* typeName = EVP_PKEY_get0_type_name(pkey.get());
    if (typeName != nullptr) {
      std::string name(typeName);
      bool isPqcKey = (name.starts_with("ML-DSA-") || name.starts_with("ML-KEM-") || name.starts_with("SLH-DSA-"));
      if (isPqcKey) {
        result.kty = JWKkty::AKP;
        result.alg = name;

        auto pubKey = pkey.rawPublicKey();
        if (!pubKey) {
          throw std::runtime_error("Failed to get raw public key for AKP JWK export");
        }
        result.pub = base64url_encode(reinterpret_cast<const unsigned char*>(pubKey.get()), pubKey.size());

        if (keyType == KeyType::PRIVATE) {
          auto seed = pkey.rawSeed();
          if (!seed) {
            throw std::runtime_error("Key does not have an available seed");
          }
          result.priv = base64url_encode(reinterpret_cast<const unsigned char*>(seed.get()), seed.size());
        }

        return result;
      }
    }
  }
#endif

  throw std::runtime_error("Unsupported key type for JWK export");
}

// Returns true if the EVP_PKEY type supports raw public key export
// (CFRG keys: Ed25519, Ed448, X25519, X448; PQC keys: ML-DSA, ML-KEM, SLH-DSA).
static bool supportsRawPublic(int keyId, const char* typeName) {
  if (keyId == EVP_PKEY_ED25519 || keyId == EVP_PKEY_ED448 || keyId == EVP_PKEY_X25519 || keyId == EVP_PKEY_X448) {
    return true;
  }
#if OPENSSL_VERSION_NUMBER >= 0x30500000L
  if (keyId == EVP_PKEY_ML_DSA_44 || keyId == EVP_PKEY_ML_DSA_65 || keyId == EVP_PKEY_ML_DSA_87) {
    return true;
  }
  if (typeName != nullptr) {
    std::string name(typeName);
    if (name.starts_with("ML-KEM-") || name.starts_with("ML-DSA-") || name.starts_with("SLH-DSA-")) {
      return true;
    }
  }
#else
  (void)typeName;
#endif
  return false;
}

// Returns true if the EVP_PKEY type supports raw private key export
// (CFRG keys: Ed25519, Ed448, X25519, X448; SLH-DSA private keys).
static bool supportsRawPrivate(int keyId, const char* typeName) {
  if (keyId == EVP_PKEY_ED25519 || keyId == EVP_PKEY_ED448 || keyId == EVP_PKEY_X25519 || keyId == EVP_PKEY_X448) {
    return true;
  }
#if OPENSSL_VERSION_NUMBER >= 0x30500000L
  if (typeName != nullptr) {
    std::string name(typeName);
    if (name.starts_with("SLH-DSA-")) {
      return true;
    }
  }
#else
  (void)typeName;
#endif
  return false;
}

// Returns true if the EVP_PKEY type supports raw seed export
// (PQC keys: ML-DSA, ML-KEM, SLH-DSA).
static bool supportsRawSeed(int keyId, const char* typeName) {
#if OPENSSL_VERSION_NUMBER >= 0x30500000L
  if (keyId == EVP_PKEY_ML_DSA_44 || keyId == EVP_PKEY_ML_DSA_65 || keyId == EVP_PKEY_ML_DSA_87) {
    return true;
  }
  if (typeName != nullptr) {
    std::string name(typeName);
    if (name.starts_with("ML-KEM-") || name.starts_with("ML-DSA-") || name.starts_with("SLH-DSA-")) {
      return true;
    }
  }
#else
  (void)keyId;
  (void)typeName;
#endif
  return false;
}

std::shared_ptr<ArrayBuffer> HybridKeyObjectHandle::exportRawPublic() {
  auto keyType = data_.GetKeyType();
  if (keyType == KeyType::SECRET) {
    throw std::runtime_error("Raw public key export is not supported for secret keys");
  }

  const auto& pkey = data_.GetAsymmetricKey();
  if (!pkey) {
    throw std::runtime_error("Invalid asymmetric key");
  }

  int keyId = EVP_PKEY_id(pkey.get());
  const char* typeName = EVP_PKEY_get0_type_name(pkey.get());

  if (!supportsRawPublic(keyId, typeName)) {
    throw std::runtime_error("The key type does not support raw public key export");
  }

  auto rawData = pkey.rawPublicKey();
  if (!rawData) {
    throw std::runtime_error("Failed to get raw public key");
  }
  return ToNativeArrayBuffer(reinterpret_cast<const uint8_t*>(rawData.get()), rawData.size());
}

std::shared_ptr<ArrayBuffer> HybridKeyObjectHandle::exportRawPrivate() {
  auto keyType = data_.GetKeyType();
  if (keyType != KeyType::PRIVATE) {
    throw std::runtime_error("Raw private key export requires a private key");
  }

  const auto& pkey = data_.GetAsymmetricKey();
  if (!pkey) {
    throw std::runtime_error("Invalid asymmetric key");
  }

  int keyId = EVP_PKEY_id(pkey.get());
  const char* typeName = EVP_PKEY_get0_type_name(pkey.get());

  if (!supportsRawPrivate(keyId, typeName)) {
    throw std::runtime_error("The key type does not support raw private key export");
  }

  auto rawData = pkey.rawPrivateKey();
  if (!rawData) {
    throw std::runtime_error("Failed to get raw private key");
  }
  return ToNativeArrayBuffer(reinterpret_cast<const uint8_t*>(rawData.get()), rawData.size());
}

std::shared_ptr<ArrayBuffer> HybridKeyObjectHandle::exportRawSeed() {
#if OPENSSL_VERSION_NUMBER >= 0x30500000L
  auto keyType = data_.GetKeyType();
  if (keyType != KeyType::PRIVATE) {
    throw std::runtime_error("Raw seed export requires a private key");
  }

  const auto& pkey = data_.GetAsymmetricKey();
  if (!pkey) {
    throw std::runtime_error("Invalid asymmetric key");
  }

  int keyId = EVP_PKEY_id(pkey.get());
  const char* typeName = EVP_PKEY_get0_type_name(pkey.get());

  if (!supportsRawSeed(keyId, typeName)) {
    throw std::runtime_error("The key type does not support raw seed export");
  }

  auto rawData = pkey.rawSeed();
  if (!rawData) {
    throw std::runtime_error("Key does not have an available seed");
  }
  return ToNativeArrayBuffer(reinterpret_cast<const uint8_t*>(rawData.get()), rawData.size());
#else
  throw std::runtime_error("Raw seed export requires OpenSSL 3.5+");
#endif
}

std::shared_ptr<ArrayBuffer> HybridKeyObjectHandle::exportECPublicRaw(bool compressed) {
  auto keyType = data_.GetKeyType();
  if (keyType == KeyType::SECRET) {
    throw std::runtime_error("EC raw public key export is not supported for secret keys");
  }

  const auto& pkey = data_.GetAsymmetricKey();
  if (!pkey) {
    throw std::runtime_error("Invalid asymmetric key");
  }

  if (EVP_PKEY_id(pkey.get()) != EVP_PKEY_EC) {
    throw std::runtime_error("Key is not an EC key");
  }

  const EC_KEY* ec_key = EVP_PKEY_get0_EC_KEY(pkey.get());
  if (!ec_key) {
    throw std::runtime_error("Failed to get EC key");
  }

  const EC_GROUP* group = EC_KEY_get0_group(ec_key);
  const EC_POINT* point = EC_KEY_get0_public_key(ec_key);
  if (!group || !point) {
    throw std::runtime_error("Failed to get EC public key point");
  }

  point_conversion_form_t form = compressed ? POINT_CONVERSION_COMPRESSED : POINT_CONVERSION_UNCOMPRESSED;

  size_t len = EC_POINT_point2oct(group, point, form, nullptr, 0, nullptr);
  if (len == 0) {
    throw std::runtime_error("Failed to compute EC point size");
  }
  std::vector<uint8_t> buf(len);
  if (EC_POINT_point2oct(group, point, form, buf.data(), buf.size(), nullptr) != len) {
    throw std::runtime_error("Failed to encode EC public key point");
  }
  return ToNativeArrayBuffer(buf.data(), buf.size());
}

std::shared_ptr<ArrayBuffer> HybridKeyObjectHandle::exportECPrivateRaw() {
  auto keyType = data_.GetKeyType();
  if (keyType != KeyType::PRIVATE) {
    throw std::runtime_error("EC raw private key export requires a private key");
  }

  const auto& pkey = data_.GetAsymmetricKey();
  if (!pkey) {
    throw std::runtime_error("Invalid asymmetric key");
  }

  if (EVP_PKEY_id(pkey.get()) != EVP_PKEY_EC) {
    throw std::runtime_error("Key is not an EC key");
  }

  const EC_KEY* ec_key = EVP_PKEY_get0_EC_KEY(pkey.get());
  if (!ec_key) {
    throw std::runtime_error("Failed to get EC key");
  }

  const BIGNUM* priv_bn = EC_KEY_get0_private_key(ec_key);
  if (!priv_bn) {
    throw std::runtime_error("EC key has no private component");
  }

  const EC_GROUP* group = EC_KEY_get0_group(ec_key);
  if (!group) {
    throw std::runtime_error("Failed to get EC group");
  }

  BIGNUM* order = BN_new();
  if (!order) {
    throw std::runtime_error("Failed to allocate BIGNUM");
  }
  if (EC_GROUP_get_order(group, order, nullptr) != 1) {
    BN_free(order);
    throw std::runtime_error("Failed to get EC group order");
  }
  size_t order_size = (BN_num_bits(order) + 7) / 8;
  BN_free(order);

  std::vector<uint8_t> buf(order_size, 0);
  if (BN_bn2binpad(priv_bn, buf.data(), static_cast<int>(order_size)) < 0) {
    throw std::runtime_error("Failed to encode EC private key");
  }
  return ToNativeArrayBuffer(buf.data(), buf.size());
}

AsymmetricKeyType HybridKeyObjectHandle::getAsymmetricKeyType() {
  const auto& pkey = data_.GetAsymmetricKey();
  if (!pkey) {
    throw std::runtime_error("Key is not an asymmetric key");
  }

  int keyType = EVP_PKEY_id(pkey.get());

  switch (keyType) {
    case EVP_PKEY_RSA:
      return AsymmetricKeyType::RSA;
    case EVP_PKEY_RSA_PSS:
      return AsymmetricKeyType::RSA_PSS;
    case EVP_PKEY_DSA:
      return AsymmetricKeyType::DSA;
    case EVP_PKEY_EC:
      return AsymmetricKeyType::EC;
    case EVP_PKEY_DH:
      return AsymmetricKeyType::DH;
    case EVP_PKEY_X25519:
      return AsymmetricKeyType::X25519;
    case EVP_PKEY_X448:
      return AsymmetricKeyType::X448;
    case EVP_PKEY_ED25519:
      return AsymmetricKeyType::ED25519;
    case EVP_PKEY_ED448:
      return AsymmetricKeyType::ED448;
#if OPENSSL_VERSION_NUMBER >= 0x30500000L
    case EVP_PKEY_ML_DSA_44:
      return AsymmetricKeyType::ML_DSA_44;
    case EVP_PKEY_ML_DSA_65:
      return AsymmetricKeyType::ML_DSA_65;
    case EVP_PKEY_ML_DSA_87:
      return AsymmetricKeyType::ML_DSA_87;
#endif
    default:
      break;
  }

#if OPENSSL_VERSION_NUMBER >= 0x30500000L
  // EVP_PKEY_id returns -1 for provider-only key types (e.g. ML-KEM, SLH-DSA)
  // Fall back to string-based type name comparison
  const char* typeName = EVP_PKEY_get0_type_name(pkey.get());
  if (typeName != nullptr) {
    std::string name(typeName);
    if (name == "ML-KEM-512")
      return AsymmetricKeyType::ML_KEM_512;
    if (name == "ML-KEM-768")
      return AsymmetricKeyType::ML_KEM_768;
    if (name == "ML-KEM-1024")
      return AsymmetricKeyType::ML_KEM_1024;
    if (name == "SLH-DSA-SHA2-128s")
      return AsymmetricKeyType::SLH_DSA_SHA2_128S;
    if (name == "SLH-DSA-SHA2-128f")
      return AsymmetricKeyType::SLH_DSA_SHA2_128F;
    if (name == "SLH-DSA-SHA2-192s")
      return AsymmetricKeyType::SLH_DSA_SHA2_192S;
    if (name == "SLH-DSA-SHA2-192f")
      return AsymmetricKeyType::SLH_DSA_SHA2_192F;
    if (name == "SLH-DSA-SHA2-256s")
      return AsymmetricKeyType::SLH_DSA_SHA2_256S;
    if (name == "SLH-DSA-SHA2-256f")
      return AsymmetricKeyType::SLH_DSA_SHA2_256F;
    if (name == "SLH-DSA-SHAKE-128s")
      return AsymmetricKeyType::SLH_DSA_SHAKE_128S;
    if (name == "SLH-DSA-SHAKE-128f")
      return AsymmetricKeyType::SLH_DSA_SHAKE_128F;
    if (name == "SLH-DSA-SHAKE-192s")
      return AsymmetricKeyType::SLH_DSA_SHAKE_192S;
    if (name == "SLH-DSA-SHAKE-192f")
      return AsymmetricKeyType::SLH_DSA_SHAKE_192F;
    if (name == "SLH-DSA-SHAKE-256s")
      return AsymmetricKeyType::SLH_DSA_SHAKE_256S;
    if (name == "SLH-DSA-SHAKE-256f")
      return AsymmetricKeyType::SLH_DSA_SHAKE_256F;
  }
#endif

  throw std::runtime_error("Unsupported asymmetric key type");
}

bool HybridKeyObjectHandle::init(KeyType keyType, const std::variant<std::shared_ptr<ArrayBuffer>, std::string>& key,
                                 std::optional<KFormatType> format, std::optional<KeyEncoding> type,
                                 const std::optional<std::shared_ptr<ArrayBuffer>>& passphrase) {
  // Reset any existing data to prevent state leakage
  data_ = KeyObjectData();

  // get ArrayBuffer from key - always copy to ensure we own the data
  std::shared_ptr<ArrayBuffer> ab;
  if (std::holds_alternative<std::string>(key)) {
    ab = ToNativeArrayBuffer(std::get<std::string>(key));
  } else {
    const auto& abPtr = std::get<std::shared_ptr<ArrayBuffer>>(key);
    ab = ToNativeArrayBuffer(abPtr);
  }

  // Handle raw asymmetric key material - only for special curves with known raw sizes
  std::optional<KFormatType> actualFormat = format;
  if (!actualFormat.has_value() && !type.has_value() && (keyType == KeyType::PUBLIC || keyType == KeyType::PRIVATE)) {
    size_t keySize = ab->size();
    // Only route to initRawKey for exact special curve sizes:
    // X25519/Ed25519: 32 bytes, X448: 56 bytes, Ed448: 57 bytes
    // DER-encoded keys will be much larger and should use standard parsing
    if ((keySize == 32) || (keySize == 56) || (keySize == 57)) {
      return initRawKey(keyType, ab);
    }
    // For larger sizes (DER-encoded keys), fall through to standard parsing
  }

  switch (keyType) {
    case KeyType::SECRET: {
      this->data_ = KeyObjectData::CreateSecret(ab);
      break;
    }
    case KeyType::PUBLIC: {
      auto data = KeyObjectData::GetPublicOrPrivateKey(ab, actualFormat, type, passphrase);
      if (!data)
        return false;
      this->data_ = data.addRefWithType(KeyType::PUBLIC);
      break;
    }
    case KeyType::PRIVATE: {
      if (auto data = KeyObjectData::GetPrivateKey(ab, actualFormat, type, passphrase, false)) {
        this->data_ = std::move(data);
      }
      break;
    }
  }
  return true;
}

std::optional<KeyType> HybridKeyObjectHandle::initJwk(const JWK& keyData, std::optional<NamedCurve> namedCurve) {
  // Reset any existing data
  data_ = KeyObjectData();

  if (!keyData.kty.has_value()) {
    throw std::runtime_error("JWK missing required 'kty' field");
  }

  JWKkty kty = keyData.kty.value();

  // Handle symmetric keys (AES, HMAC)
  if (kty == JWKkty::OCT) {
    if (!keyData.k.has_value()) {
      throw std::runtime_error("JWK oct key missing 'k' field");
    }

    std::string decoded = base64url_decode(keyData.k.value());
    auto keyBuffer = ToNativeArrayBuffer(decoded);
    data_ = KeyObjectData::CreateSecret(keyBuffer);
    return KeyType::SECRET;
  }

  // Handle RSA keys
  if (kty == JWKkty::RSA) {
    bool isPrivate = keyData.d.has_value();

    if (!keyData.n.has_value() || !keyData.e.has_value()) {
      throw std::runtime_error("JWK RSA key missing required 'n' or 'e' fields");
    }

    RSA* rsa = RSA_new();
    if (!rsa)
      throw std::runtime_error("Failed to create RSA key");

    // Set public components
    BIGNUM* n = base64url_to_bn(keyData.n.value());
    BIGNUM* e = base64url_to_bn(keyData.e.value());

    if (!n || !e) {
      RSA_free(rsa);
      throw std::runtime_error("Failed to decode RSA public components");
    }

    if (isPrivate) {
      // Private key
      if (!keyData.d.has_value()) {
        BN_free(n);
        BN_free(e);
        RSA_free(rsa);
        throw std::runtime_error("JWK RSA private key missing 'd' field");
      }

      BIGNUM* d = base64url_to_bn(keyData.d.value());
      if (!d) {
        BN_free(n);
        BN_free(e);
        RSA_free(rsa);
        throw std::runtime_error("Failed to decode RSA 'd' component");
      }

      // Set key components (RSA_set0_key takes ownership)
      if (RSA_set0_key(rsa, n, e, d) != 1) {
        BN_free(n);
        BN_free(e);
        BN_free(d);
        RSA_free(rsa);
        throw std::runtime_error("Failed to set RSA key components");
      }

      // Set optional CRT parameters if present
      if (keyData.p.has_value() && keyData.q.has_value()) {
        BIGNUM* p = base64url_to_bn(keyData.p.value());
        BIGNUM* q = base64url_to_bn(keyData.q.value());
        if (p && q) {
          RSA_set0_factors(rsa, p, q);
        }
      }

      if (keyData.dp.has_value() && keyData.dq.has_value() && keyData.qi.has_value()) {
        BIGNUM* dmp1 = base64url_to_bn(keyData.dp.value());
        BIGNUM* dmq1 = base64url_to_bn(keyData.dq.value());
        BIGNUM* iqmp = base64url_to_bn(keyData.qi.value());
        if (dmp1 && dmq1 && iqmp) {
          RSA_set0_crt_params(rsa, dmp1, dmq1, iqmp);
        }
      }

      // Create EVP_PKEY from RSA
      EVP_PKEY* pkey = EVP_PKEY_new();
      if (!pkey || EVP_PKEY_assign_RSA(pkey, rsa) != 1) {
        RSA_free(rsa);
        if (pkey)
          EVP_PKEY_free(pkey);
        throw std::runtime_error("Failed to create EVP_PKEY from RSA");
      }

      data_ = KeyObjectData::CreateAsymmetric(KeyType::PRIVATE, ncrypto::EVPKeyPointer(pkey));
      return KeyType::PRIVATE;

    } else {
      // Public key
      if (RSA_set0_key(rsa, n, e, nullptr) != 1) {
        BN_free(n);
        BN_free(e);
        RSA_free(rsa);
        throw std::runtime_error("Failed to set RSA public key components");
      }

      EVP_PKEY* pkey = EVP_PKEY_new();
      if (!pkey || EVP_PKEY_assign_RSA(pkey, rsa) != 1) {
        RSA_free(rsa);
        if (pkey)
          EVP_PKEY_free(pkey);
        throw std::runtime_error("Failed to create EVP_PKEY from RSA");
      }

      data_ = KeyObjectData::CreateAsymmetric(KeyType::PUBLIC, ncrypto::EVPKeyPointer(pkey));
      return KeyType::PUBLIC;
    }
  }

  // Handle EC keys
  if (kty == JWKkty::EC) {
    bool isPrivate = keyData.d.has_value();

    if (!keyData.crv.has_value() || !keyData.x.has_value() || !keyData.y.has_value()) {
      throw std::runtime_error("JWK EC key missing required fields (crv, x, y)");
    }

    std::string crv = keyData.crv.value();

    // Map JWK curve names to OpenSSL group names and field sizes
    const char* group_name;
    size_t field_size;
    if (crv == "P-256") {
      group_name = "prime256v1";
      field_size = 32;
    } else if (crv == "P-384") {
      group_name = "secp384r1";
      field_size = 48;
    } else if (crv == "P-521") {
      group_name = "secp521r1";
      field_size = 66;
    } else {
      throw std::runtime_error("Unsupported EC curve: " + crv);
    }

    // Decode public key coordinates
    BIGNUM* x_bn = base64url_to_bn(keyData.x.value());
    BIGNUM* y_bn = base64url_to_bn(keyData.y.value());
    if (!x_bn || !y_bn) {
      BN_free(x_bn);
      BN_free(y_bn);
      throw std::runtime_error("Failed to decode EC public key coordinates");
    }

    // Build uncompressed point: 0x04 || x_padded || y_padded
    std::vector<uint8_t> pub_oct(1 + 2 * field_size, 0);
    pub_oct[0] = 0x04;
    BN_bn2binpad(x_bn, pub_oct.data() + 1, static_cast<int>(field_size));
    BN_bn2binpad(y_bn, pub_oct.data() + 1 + field_size, static_cast<int>(field_size));
    BN_free(x_bn);
    BN_free(y_bn);

    BIGNUM* d_bn = nullptr;
    if (isPrivate) {
      d_bn = base64url_to_bn(keyData.d.value());
      if (!d_bn)
        throw std::runtime_error("Failed to decode EC private key");
    }

    EVP_PKEY* pkey = nullptr;
    try {
      pkey = createEcEvpPkey(group_name, pub_oct.data(), pub_oct.size(), d_bn);
    } catch (...) {
      BN_free(d_bn);
      throw;
    }
    BN_free(d_bn);

    KeyType type = isPrivate ? KeyType::PRIVATE : KeyType::PUBLIC;
    data_ = KeyObjectData::CreateAsymmetric(type, ncrypto::EVPKeyPointer(pkey));
    return type;
  }

  // Handle OKP keys (Ed25519, Ed448, X25519, X448) per RFC 8037
  if (kty == JWKkty::OKP) {
    bool isPrivate = keyData.d.has_value();

    if (!keyData.crv.has_value() || !keyData.x.has_value()) {
      throw std::runtime_error("JWK OKP key missing required fields (crv, x)");
    }

    std::string crv = keyData.crv.value();

    int evpType;
    if (crv == "Ed25519") {
      evpType = EVP_PKEY_ED25519;
    } else if (crv == "Ed448") {
      evpType = EVP_PKEY_ED448;
    } else if (crv == "X25519") {
      evpType = EVP_PKEY_X25519;
    } else if (crv == "X448") {
      evpType = EVP_PKEY_X448;
    } else {
      throw std::runtime_error("Unsupported OKP curve: " + crv);
    }

    if (isPrivate) {
      std::string privBytes = base64url_decode(keyData.d.value());
      EVP_PKEY* pkey =
          EVP_PKEY_new_raw_private_key(evpType, nullptr, reinterpret_cast<const unsigned char*>(privBytes.data()), privBytes.size());
      if (!pkey) {
        throw std::runtime_error("Failed to create OKP private key from JWK");
      }
      data_ = KeyObjectData::CreateAsymmetric(KeyType::PRIVATE, ncrypto::EVPKeyPointer(pkey));
      return KeyType::PRIVATE;
    } else {
      std::string pubBytes = base64url_decode(keyData.x.value());
      EVP_PKEY* pkey =
          EVP_PKEY_new_raw_public_key(evpType, nullptr, reinterpret_cast<const unsigned char*>(pubBytes.data()), pubBytes.size());
      if (!pkey) {
        throw std::runtime_error("Failed to create OKP public key from JWK");
      }
      data_ = KeyObjectData::CreateAsymmetric(KeyType::PUBLIC, ncrypto::EVPKeyPointer(pkey));
      return KeyType::PUBLIC;
    }
  }

#if OPENSSL_VERSION_NUMBER >= 0x30500000L
  // Handle AKP keys (ML-DSA, ML-KEM)
  if (kty == JWKkty::AKP) {
    if (!keyData.alg.has_value()) {
      throw std::runtime_error("JWK AKP key missing 'alg' field");
    }
    if (!keyData.pub.has_value()) {
      throw std::runtime_error("JWK AKP key missing 'pub' field");
    }

    const std::string& alg = keyData.alg.value();
    int nid = 0;
    if (alg == "ML-DSA-44")
      nid = EVP_PKEY_ML_DSA_44;
    else if (alg == "ML-DSA-65")
      nid = EVP_PKEY_ML_DSA_65;
    else if (alg == "ML-DSA-87")
      nid = EVP_PKEY_ML_DSA_87;
    else if (alg == "ML-KEM-512")
      nid = EVP_PKEY_ML_KEM_512;
    else if (alg == "ML-KEM-768")
      nid = EVP_PKEY_ML_KEM_768;
    else if (alg == "ML-KEM-1024")
      nid = EVP_PKEY_ML_KEM_1024;
    else if (alg == "SLH-DSA-SHA2-128s")
      nid = EVP_PKEY_SLH_DSA_SHA2_128S;
    else if (alg == "SLH-DSA-SHA2-128f")
      nid = EVP_PKEY_SLH_DSA_SHA2_128F;
    else if (alg == "SLH-DSA-SHA2-192s")
      nid = EVP_PKEY_SLH_DSA_SHA2_192S;
    else if (alg == "SLH-DSA-SHA2-192f")
      nid = EVP_PKEY_SLH_DSA_SHA2_192F;
    else if (alg == "SLH-DSA-SHA2-256s")
      nid = EVP_PKEY_SLH_DSA_SHA2_256S;
    else if (alg == "SLH-DSA-SHA2-256f")
      nid = EVP_PKEY_SLH_DSA_SHA2_256F;
    else if (alg == "SLH-DSA-SHAKE-128s")
      nid = EVP_PKEY_SLH_DSA_SHAKE_128S;
    else if (alg == "SLH-DSA-SHAKE-128f")
      nid = EVP_PKEY_SLH_DSA_SHAKE_128F;
    else if (alg == "SLH-DSA-SHAKE-192s")
      nid = EVP_PKEY_SLH_DSA_SHAKE_192S;
    else if (alg == "SLH-DSA-SHAKE-192f")
      nid = EVP_PKEY_SLH_DSA_SHAKE_192F;
    else if (alg == "SLH-DSA-SHAKE-256s")
      nid = EVP_PKEY_SLH_DSA_SHAKE_256S;
    else if (alg == "SLH-DSA-SHAKE-256f")
      nid = EVP_PKEY_SLH_DSA_SHAKE_256F;
    else
      throw std::runtime_error("Unsupported JWK AKP \"alg\": " + alg);

    bool isPrivate = keyData.priv.has_value();
    ncrypto::EVPKeyPointer pkey;

    if (isPrivate) {
      std::string seedBytes = base64url_decode(keyData.priv.value());
      ncrypto::Buffer<const unsigned char> buf{
          .data = reinterpret_cast<const unsigned char*>(seedBytes.data()),
          .len = seedBytes.size(),
      };
      pkey = ncrypto::EVPKeyPointer::NewRawSeed(nid, buf);
      if (!pkey) {
        throw std::runtime_error("Invalid JWK AKP key");
      }

      // Verify the pub field matches the public key derived from the seed.
      std::string pubBytes = base64url_decode(keyData.pub.value());
      auto derivedPub = pkey.rawPublicKey();
      if (!derivedPub || derivedPub.size() != pubBytes.size() || CRYPTO_memcmp(derivedPub.get(), pubBytes.data(), pubBytes.size()) != 0) {
        throw std::runtime_error("Invalid JWK AKP key");
      }

      data_ = KeyObjectData::CreateAsymmetric(KeyType::PRIVATE, std::move(pkey));
      return KeyType::PRIVATE;
    } else {
      std::string pubBytes = base64url_decode(keyData.pub.value());
      ncrypto::Buffer<const unsigned char> buf{
          .data = reinterpret_cast<const unsigned char*>(pubBytes.data()),
          .len = pubBytes.size(),
      };
      pkey = ncrypto::EVPKeyPointer::NewRawPublic(nid, buf);
      if (!pkey) {
        throw std::runtime_error("Invalid JWK AKP key");
      }
      data_ = KeyObjectData::CreateAsymmetric(KeyType::PUBLIC, std::move(pkey));
      return KeyType::PUBLIC;
    }
  }
#endif

  throw std::runtime_error("Unsupported JWK key type");
}

KeyDetail HybridKeyObjectHandle::keyDetail() {
  const auto& pkey_ptr = data_.GetAsymmetricKey();
  if (!pkey_ptr) {
    return KeyDetail{};
  }

  EVP_PKEY* pkey = pkey_ptr.get();
  int keyType = EVP_PKEY_base_id(pkey);

  if (keyType == EVP_PKEY_RSA) {
    // Extract RSA key details
    int modulusLength = EVP_PKEY_bits(pkey);

    // Extract public exponent (typically 65537 = 0x10001)
    const RSA* rsa = EVP_PKEY_get0_RSA(pkey);
    if (rsa) {
      const BIGNUM* e_bn = nullptr;
      RSA_get0_key(rsa, nullptr, &e_bn, nullptr);
      if (e_bn) {
        unsigned long exponent_val = BN_get_word(e_bn);
        return KeyDetail(std::nullopt, static_cast<double>(exponent_val), static_cast<double>(modulusLength), std::nullopt, std::nullopt,
                         std::nullopt, std::nullopt);
      }
    }

    // Fallback if we couldn't extract the exponent
    return KeyDetail(std::nullopt, std::nullopt, static_cast<double>(modulusLength), std::nullopt, std::nullopt, std::nullopt,
                     std::nullopt);
  }

  if (keyType == EVP_PKEY_EC) {
    char curve_name[64];
    size_t name_len = 0;
    if (EVP_PKEY_get_utf8_string_param(pkey, OSSL_PKEY_PARAM_GROUP_NAME, curve_name, sizeof(curve_name), &name_len) == 1) {
      return KeyDetail(std::nullopt, std::nullopt, std::nullopt, std::nullopt, std::nullopt, std::nullopt,
                       std::string(curve_name, name_len));
    }
  }

  return KeyDetail{};
}

bool HybridKeyObjectHandle::initRawKey(KeyType keyType, std::shared_ptr<ArrayBuffer> keyData) {
  // For asymmetric keys (x25519/x448/ed25519/ed448), we need to determine the curve type
  // Based on key size: x25519=32 bytes, x448=56 bytes, ed25519=32 bytes, ed448=57 bytes
  int curveId = -1;
  size_t keySize = keyData->size();

  if (keySize == 32) {
    // Could be x25519 or ed25519 - for now assume x25519 based on test context
    curveId = EVP_PKEY_X25519;
  } else if (keySize == 56) {
    curveId = EVP_PKEY_X448;
  } else if (keySize == 57) {
    curveId = EVP_PKEY_ED448;
  } else {
    throw std::runtime_error("Invalid key size: expected 32, 56, or 57 bytes for curve keys");
  }

  ncrypto::Buffer<const unsigned char> buffer{.data = reinterpret_cast<const unsigned char*>(keyData->data()), .len = keyData->size()};

  ncrypto::EVPKeyPointer pkey;
  if (keyType == KeyType::PRIVATE) {
    pkey = ncrypto::EVPKeyPointer::NewRawPrivate(curveId, buffer);
  } else if (keyType == KeyType::PUBLIC) {
    pkey = ncrypto::EVPKeyPointer::NewRawPublic(curveId, buffer);
  } else {
    throw std::runtime_error("Raw keys are only supported for asymmetric key types");
  }

  if (!pkey) {
    throw std::runtime_error("Failed to create key from raw data");
  }

  this->data_ = KeyObjectData::CreateAsymmetric(keyType, std::move(pkey));
  return true;
}

bool HybridKeyObjectHandle::initECRaw(const std::string& namedCurve, const std::shared_ptr<ArrayBuffer>& keyData) {
  // Reset any existing data
  data_ = KeyObjectData();

  // Map curve name to NID (same logic as HybridEcKeyPair::GetCurveFromName)
  int nid = 0;
  if (namedCurve == "prime256v1" || namedCurve == "P-256") {
    nid = NID_X9_62_prime256v1;
  } else if (namedCurve == "secp384r1" || namedCurve == "P-384") {
    nid = NID_secp384r1;
  } else if (namedCurve == "secp521r1" || namedCurve == "P-521") {
    nid = NID_secp521r1;
  } else if (namedCurve == "secp256k1") {
    nid = NID_secp256k1;
  } else {
    // Try standard OpenSSL name resolution
    nid = OBJ_txt2nid(namedCurve.c_str());
  }

  if (nid == 0) {
    throw std::runtime_error("Unknown curve: " + namedCurve);
  }

  // Get the OpenSSL group name for this curve
  const char* group_name = OBJ_nid2sn(nid);
  if (!group_name) {
    throw std::runtime_error("Failed to get curve name for NID");
  }

  EVP_PKEY* pkey = createEcEvpPkey(group_name, keyData->data(), keyData->size());
  this->data_ = KeyObjectData::CreateAsymmetric(KeyType::PUBLIC, ncrypto::EVPKeyPointer(pkey));
  return true;
}

bool HybridKeyObjectHandle::initPqcRaw(const std::string& algorithmName, const std::shared_ptr<ArrayBuffer>& keyData, bool isPublic) {
#if OPENSSL_VERSION_NUMBER >= 0x30500000L
  data_ = KeyObjectData();

  int nid = 0;
  if (algorithmName == "ML-KEM-512")
    nid = EVP_PKEY_ML_KEM_512;
  else if (algorithmName == "ML-KEM-768")
    nid = EVP_PKEY_ML_KEM_768;
  else if (algorithmName == "ML-KEM-1024")
    nid = EVP_PKEY_ML_KEM_1024;
  else if (algorithmName == "ML-DSA-44")
    nid = EVP_PKEY_ML_DSA_44;
  else if (algorithmName == "ML-DSA-65")
    nid = EVP_PKEY_ML_DSA_65;
  else if (algorithmName == "ML-DSA-87")
    nid = EVP_PKEY_ML_DSA_87;
  else if (algorithmName == "SLH-DSA-SHA2-128s")
    nid = EVP_PKEY_SLH_DSA_SHA2_128S;
  else if (algorithmName == "SLH-DSA-SHA2-128f")
    nid = EVP_PKEY_SLH_DSA_SHA2_128F;
  else if (algorithmName == "SLH-DSA-SHA2-192s")
    nid = EVP_PKEY_SLH_DSA_SHA2_192S;
  else if (algorithmName == "SLH-DSA-SHA2-192f")
    nid = EVP_PKEY_SLH_DSA_SHA2_192F;
  else if (algorithmName == "SLH-DSA-SHA2-256s")
    nid = EVP_PKEY_SLH_DSA_SHA2_256S;
  else if (algorithmName == "SLH-DSA-SHA2-256f")
    nid = EVP_PKEY_SLH_DSA_SHA2_256F;
  else if (algorithmName == "SLH-DSA-SHAKE-128s")
    nid = EVP_PKEY_SLH_DSA_SHAKE_128S;
  else if (algorithmName == "SLH-DSA-SHAKE-128f")
    nid = EVP_PKEY_SLH_DSA_SHAKE_128F;
  else if (algorithmName == "SLH-DSA-SHAKE-192s")
    nid = EVP_PKEY_SLH_DSA_SHAKE_192S;
  else if (algorithmName == "SLH-DSA-SHAKE-192f")
    nid = EVP_PKEY_SLH_DSA_SHAKE_192F;
  else if (algorithmName == "SLH-DSA-SHAKE-256s")
    nid = EVP_PKEY_SLH_DSA_SHAKE_256S;
  else if (algorithmName == "SLH-DSA-SHAKE-256f")
    nid = EVP_PKEY_SLH_DSA_SHAKE_256F;
  else
    throw std::runtime_error("Unknown PQC algorithm: " + algorithmName);

  ncrypto::Buffer<const unsigned char> buffer{.data = reinterpret_cast<const unsigned char*>(keyData->data()), .len = keyData->size()};

  ncrypto::EVPKeyPointer pkey;
  if (isPublic) {
    pkey = ncrypto::EVPKeyPointer::NewRawPublic(nid, buffer);
  } else {
    pkey = ncrypto::EVPKeyPointer::NewRawSeed(nid, buffer);
  }

  if (!pkey) {
    return false;
  }

  auto keyType = isPublic ? KeyType::PUBLIC : KeyType::PRIVATE;
  this->data_ = KeyObjectData::CreateAsymmetric(keyType, std::move(pkey));
  return true;
#else
  throw std::runtime_error("PQC raw key import requires OpenSSL 3.5+");
#endif
}

// Map a string asymmetricKeyType to an EVP_PKEY NID for OKP/PQC keys.
// Returns 0 if the type is not a known OKP or PQC type.
static int evpNidForAsymmetricKeyType(const std::string& asymmetricKeyType) {
  if (asymmetricKeyType == "ed25519")
    return EVP_PKEY_ED25519;
  if (asymmetricKeyType == "ed448")
    return EVP_PKEY_ED448;
  if (asymmetricKeyType == "x25519")
    return EVP_PKEY_X25519;
  if (asymmetricKeyType == "x448")
    return EVP_PKEY_X448;
#if OPENSSL_VERSION_NUMBER >= 0x30500000L
  if (asymmetricKeyType == "ml-dsa-44")
    return EVP_PKEY_ML_DSA_44;
  if (asymmetricKeyType == "ml-dsa-65")
    return EVP_PKEY_ML_DSA_65;
  if (asymmetricKeyType == "ml-dsa-87")
    return EVP_PKEY_ML_DSA_87;
  if (asymmetricKeyType == "ml-kem-512")
    return EVP_PKEY_ML_KEM_512;
  if (asymmetricKeyType == "ml-kem-768")
    return EVP_PKEY_ML_KEM_768;
  if (asymmetricKeyType == "ml-kem-1024")
    return EVP_PKEY_ML_KEM_1024;
  if (asymmetricKeyType == "slh-dsa-sha2-128s")
    return EVP_PKEY_SLH_DSA_SHA2_128S;
  if (asymmetricKeyType == "slh-dsa-sha2-128f")
    return EVP_PKEY_SLH_DSA_SHA2_128F;
  if (asymmetricKeyType == "slh-dsa-sha2-192s")
    return EVP_PKEY_SLH_DSA_SHA2_192S;
  if (asymmetricKeyType == "slh-dsa-sha2-192f")
    return EVP_PKEY_SLH_DSA_SHA2_192F;
  if (asymmetricKeyType == "slh-dsa-sha2-256s")
    return EVP_PKEY_SLH_DSA_SHA2_256S;
  if (asymmetricKeyType == "slh-dsa-sha2-256f")
    return EVP_PKEY_SLH_DSA_SHA2_256F;
  if (asymmetricKeyType == "slh-dsa-shake-128s")
    return EVP_PKEY_SLH_DSA_SHAKE_128S;
  if (asymmetricKeyType == "slh-dsa-shake-128f")
    return EVP_PKEY_SLH_DSA_SHAKE_128F;
  if (asymmetricKeyType == "slh-dsa-shake-192s")
    return EVP_PKEY_SLH_DSA_SHAKE_192S;
  if (asymmetricKeyType == "slh-dsa-shake-192f")
    return EVP_PKEY_SLH_DSA_SHAKE_192F;
  if (asymmetricKeyType == "slh-dsa-shake-256s")
    return EVP_PKEY_SLH_DSA_SHAKE_256S;
  if (asymmetricKeyType == "slh-dsa-shake-256f")
    return EVP_PKEY_SLH_DSA_SHAKE_256F;
#endif
  return 0;
}

bool HybridKeyObjectHandle::initRawPublic(const std::string& asymmetricKeyType, const std::shared_ptr<ArrayBuffer>& keyData,
                                          const std::optional<std::string>& namedCurve) {
  data_ = KeyObjectData();

  if (asymmetricKeyType == "ec") {
    if (!namedCurve.has_value()) {
      throw std::runtime_error("namedCurve is required for EC raw public key import");
    }
    return initECRaw(namedCurve.value(), keyData);
  }

  int nid = evpNidForAsymmetricKeyType(asymmetricKeyType);
  if (nid == 0) {
    throw std::runtime_error("Invalid asymmetricKeyType for raw public key import: " + asymmetricKeyType);
  }

  ncrypto::Buffer<const unsigned char> buffer{.data = reinterpret_cast<const unsigned char*>(keyData->data()), .len = keyData->size()};
  auto pkey = ncrypto::EVPKeyPointer::NewRawPublic(nid, buffer);
  if (!pkey) {
    throw std::runtime_error("Failed to create raw public key");
  }
  this->data_ = KeyObjectData::CreateAsymmetric(KeyType::PUBLIC, std::move(pkey));
  return true;
}

bool HybridKeyObjectHandle::initRawPrivate(const std::string& asymmetricKeyType, const std::shared_ptr<ArrayBuffer>& keyData,
                                           const std::optional<std::string>& namedCurve) {
  data_ = KeyObjectData();

  if (asymmetricKeyType == "ec") {
    if (!namedCurve.has_value()) {
      throw std::runtime_error("namedCurve is required for EC raw private key import");
    }

    int nid = 0;
    const std::string& curve = namedCurve.value();
    if (curve == "prime256v1" || curve == "P-256")
      nid = NID_X9_62_prime256v1;
    else if (curve == "secp384r1" || curve == "P-384")
      nid = NID_secp384r1;
    else if (curve == "secp521r1" || curve == "P-521")
      nid = NID_secp521r1;
    else if (curve == "secp256k1")
      nid = NID_secp256k1;
    else
      nid = OBJ_txt2nid(curve.c_str());

    if (nid == 0) {
      throw std::runtime_error("Unknown curve: " + curve);
    }

    auto ec_key = std::unique_ptr<EC_KEY, decltype(&EC_KEY_free)>(EC_KEY_new_by_curve_name(nid), EC_KEY_free);
    if (!ec_key) {
      throw std::runtime_error("Failed to create EC_KEY");
    }
    const EC_GROUP* group = EC_KEY_get0_group(ec_key.get());

    BIGNUM* order = BN_new();
    if (!order || EC_GROUP_get_order(group, order, nullptr) != 1) {
      if (order)
        BN_free(order);
      throw std::runtime_error("Failed to get EC group order");
    }
    size_t order_size = (BN_num_bits(order) + 7) / 8;
    BN_free(order);

    if (keyData->size() != order_size) {
      throw std::runtime_error("Invalid EC private key length");
    }

    BIGNUM* priv_bn = BN_bin2bn(reinterpret_cast<const unsigned char*>(keyData->data()), static_cast<int>(keyData->size()), nullptr);
    if (!priv_bn) {
      throw std::runtime_error("Failed to decode EC private key");
    }

    if (EC_KEY_set_private_key(ec_key.get(), priv_bn) != 1) {
      BN_free(priv_bn);
      throw std::runtime_error("Failed to set EC private key");
    }

    auto pub_point = std::unique_ptr<EC_POINT, decltype(&EC_POINT_free)>(EC_POINT_new(group), EC_POINT_free);
    if (!pub_point || EC_POINT_mul(group, pub_point.get(), priv_bn, nullptr, nullptr, nullptr) != 1 ||
        EC_KEY_set_public_key(ec_key.get(), pub_point.get()) != 1) {
      BN_free(priv_bn);
      throw std::runtime_error("Failed to derive EC public key");
    }
    BN_free(priv_bn);

    EVP_PKEY* pkey = EVP_PKEY_new();
    if (!pkey || EVP_PKEY_assign_EC_KEY(pkey, ec_key.get()) != 1) {
      if (pkey)
        EVP_PKEY_free(pkey);
      throw std::runtime_error("Failed to create EVP_PKEY from EC_KEY");
    }
    ec_key.release();

    this->data_ = KeyObjectData::CreateAsymmetric(KeyType::PRIVATE, ncrypto::EVPKeyPointer(pkey));
    return true;
  }

  int nid = evpNidForAsymmetricKeyType(asymmetricKeyType);
  if (nid == 0) {
    throw std::runtime_error("Invalid asymmetricKeyType for raw private key import: " + asymmetricKeyType);
  }

  ncrypto::Buffer<const unsigned char> buffer{.data = reinterpret_cast<const unsigned char*>(keyData->data()), .len = keyData->size()};
  auto pkey = ncrypto::EVPKeyPointer::NewRawPrivate(nid, buffer);
  if (!pkey) {
    throw std::runtime_error("Failed to create raw private key");
  }
  this->data_ = KeyObjectData::CreateAsymmetric(KeyType::PRIVATE, std::move(pkey));
  return true;
}

bool HybridKeyObjectHandle::initRawSeed(const std::string& asymmetricKeyType, const std::shared_ptr<ArrayBuffer>& keyData) {
#if OPENSSL_VERSION_NUMBER >= 0x30500000L
  data_ = KeyObjectData();

  int nid = evpNidForAsymmetricKeyType(asymmetricKeyType);
  if (nid == 0) {
    throw std::runtime_error("Invalid asymmetricKeyType for raw seed import: " + asymmetricKeyType);
  }

  ncrypto::Buffer<const unsigned char> buffer{.data = reinterpret_cast<const unsigned char*>(keyData->data()), .len = keyData->size()};
  auto pkey = ncrypto::EVPKeyPointer::NewRawSeed(nid, buffer);
  if (!pkey) {
    throw std::runtime_error("Failed to create key from raw seed");
  }
  this->data_ = KeyObjectData::CreateAsymmetric(KeyType::PRIVATE, std::move(pkey));
  return true;
#else
  throw std::runtime_error("Raw seed import requires OpenSSL 3.5+");
#endif
}

bool HybridKeyObjectHandle::keyEquals(const std::shared_ptr<HybridKeyObjectHandleSpec>& other) {
  auto otherHandle = std::dynamic_pointer_cast<HybridKeyObjectHandle>(other);
  if (!otherHandle)
    return false;

  const auto& otherData = otherHandle->getKeyObjectData();
  if (data_.GetKeyType() != otherData.GetKeyType())
    return false;

  if (data_.GetKeyType() == KeyType::SECRET) {
    auto thisKey = data_.GetSymmetricKey();
    auto otherKey = otherData.GetSymmetricKey();
    if (thisKey->size() != otherKey->size())
      return false;
    return CRYPTO_memcmp(thisKey->data(), otherKey->data(), thisKey->size()) == 0;
  }

  const auto& thisPkey = data_.GetAsymmetricKey();
  const auto& otherPkey = otherData.GetAsymmetricKey();
  if (!thisPkey || !otherPkey)
    return false;
  return EVP_PKEY_eq(thisPkey.get(), otherPkey.get()) == 1;
}

double HybridKeyObjectHandle::getSymmetricKeySize() {
  return static_cast<double>(data_.GetSymmetricKeySize());
}

bool HybridKeyObjectHandle::checkEcKeyData() {
  const auto& pkey = data_.GetAsymmetricKey();
  if (!pkey || EVP_PKEY_id(pkey.get()) != EVP_PKEY_EC) {
    return false;
  }
  auto ctx = pkey.newCtx();
  if (!ctx) {
    return false;
  }
  return data_.GetKeyType() == KeyType::PRIVATE ? ctx.privateCheck() : ctx.publicCheck();
}

} // namespace margelo::nitro::crypto
