#include "engine_crypto.h"

#include <openssl/hmac.h>
#include <openssl/sha.h>
#include <openssl/evp.h>
#include <openssl/buffer.h>
#include <sodium.h>
#include <fstream>
#include <iostream>
#include <cstring>
#include <fcntl.h>
#include <unistd.h>
#include <sys/syscall.h>

#ifndef SYS_getrandom
#define SYS_getrandom 318
#endif

namespace node669427 {

CryptoEngine::CryptoEngine() {
    if (sodium_init() < 0) {
        std::cerr << "[Crypto] libsodium initialization failed" << std::endl;
        exit(1);
    }
}

CryptoEngine::~CryptoEngine() {
    std::lock_guard<std::mutex> lock(key_mutex_);
    if (!hmac_key_.empty()) {
        sodium_memzero(&hmac_key_[0], hmac_key_.size());
    }
}

bool CryptoEngine::loadKey(const std::string& key_path) {
    std::ifstream f(key_path);
    if (!f.is_open()) {
        std::cerr << "[Crypto] Cannot open key file " << key_path << std::endl;
        return false;
    }
    std::string key_data;
    std::getline(f, key_data);
    
    std::lock_guard<std::mutex> lock(key_mutex_);
    hmac_key_ = key_data;
    sodium_memzero(&key_data[0], key_data.size()); // Clear temp
    return !hmac_key_.empty();
}

bool CryptoEngine::loadKeyFromBuffer(const std::string& key_data) {
    std::lock_guard<std::mutex> lock(key_mutex_);
    hmac_key_ = key_data;
    return !hmac_key_.empty();
}

std::string CryptoEngine::hmacSha256(const std::string& data) const {
    std::lock_guard<std::mutex> lock(key_mutex_);
    return hmacSha256(hmac_key_, data);
}

std::string CryptoEngine::hmacSha256(const std::string& key, const std::string& data) {
    unsigned char hash[EVP_MAX_MD_SIZE];
    unsigned int length = 0;
    
    // Standard HMAC EVP
    HMAC(EVP_sha256(), key.data(), static_cast<int>(key.size()),
         reinterpret_cast<const unsigned char*>(data.data()), data.size(),
         hash, &length);
         
    std::ostringstream ss;
    for (unsigned int i = 0; i < length; ++i) {
        ss << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(hash[i]);
    }
    return ss.str();
}

std::string CryptoEngine::sha256(const std::string& data) {
    unsigned char hash[SHA256_DIGEST_LENGTH];
    EVP_MD_CTX* ctx = EVP_MD_CTX_new();
    EVP_DigestInit_ex(ctx, EVP_sha256(), nullptr);
    EVP_DigestUpdate(ctx, data.data(), data.size());
    unsigned int length = 0;
    EVP_DigestFinal_ex(ctx, hash, &length);
    EVP_MD_CTX_free(ctx);
    
    std::ostringstream ss;
    for (unsigned int i = 0; i < length; ++i) {
        ss << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(hash[i]);
    }
    return ss.str();
}

std::string CryptoEngine::generateNonce(size_t len) {
    std::vector<uint8_t> buf(len);
    // Use secure getrandom
    long ret = syscall(SYS_getrandom, buf.data(), len, 0);
    if (ret != static_cast<long>(len)) {
        // Fallback to /dev/urandom
        int fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC);
        if (fd >= 0) {
            read(fd, buf.data(), len);
            close(fd);
        } else {
            // Absolute last resort
            randombytes_buf(buf.data(), len);
        }
    }
    return bytesToHex(buf);
}

std::string CryptoEngine::base64Encode(const std::string& data) {
    BIO* bio = BIO_new(BIO_s_mem());
    BIO* b64 = BIO_new(BIO_f_base64());
    bio = BIO_push(b64, bio);
    BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL);
    BIO_write(bio, data.data(), static_cast<int>(data.size()));
    BIO_flush(bio);
    BUF_MEM* buffer_ptr;
    BIO_get_mem_ptr(bio, &buffer_ptr);
    std::string result(buffer_ptr->data, buffer_ptr->length);
    BIO_free_all(bio);
    return result;
}

std::string CryptoEngine::base64Decode(const std::string& data) {
    BIO* bio = BIO_new_mem_buf(data.data(), static_cast<int>(data.size()));
    BIO* b64 = BIO_new(BIO_f_base64());
    bio = BIO_push(b64, bio);
    BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL);
    std::string result(data.size(), '\0');
    int decoded = BIO_read(bio, &result[0], static_cast<int>(data.size()));
    BIO_free_all(bio);
    if (decoded > 0) {
        result.resize(decoded);
    } else {
        result.clear();
    }
    return result;
}

std::string CryptoEngine::bytesToHex(const std::vector<uint8_t>& bytes) {
    std::ostringstream ss;
    for (auto b : bytes) {
        ss << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(b);
    }
    return ss.str();
}

std::vector<uint8_t> CryptoEngine::hexToBytes(const std::string& hex) {
    std::vector<uint8_t> bytes;
    for (size_t i = 0; i + 1 < hex.size(); i += 2) {
        std::string byte_str = hex.substr(i, 2);
        bytes.push_back(static_cast<uint8_t>(std::stoi(byte_str, nullptr, 16)));
    }
    return bytes;
}

bool CryptoEngine::verifySignature(const std::string& data, const std::string& signature) const {
    std::string expected = hmacSha256(data);
    if (expected.size() != signature.size()) return false;
    
    // MANDATORY: Constant-time comparison
    return sodium_memcmp(expected.data(), signature.data(), expected.size()) == 0;
}

std::string CryptoEngine::signRequest(const std::string& timestamp,
                                      const std::string& nonce,
                                      const std::string& method,
                                      const std::string& path,
                                      const std::string& payload) const {
    // CRY-02 canonical: ts.nonce.METHOD.path.body
    return hmacSha256(timestamp + "." + nonce + "." + method + "." + path + "." + payload);
}

} // namespace node669427
