// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
// SPDX-License-Identifier: MIT

#include "opendht_c.h"

#include <opendht.h>
#include <opendht/log.h>

using ValueSp = std::shared_ptr<dht::Value>;
using PrivkeySp = std::shared_ptr<dht::crypto::PrivateKey>;
using PubkeySp = std::shared_ptr<const dht::crypto::PublicKey>;
using CertSp = std::shared_ptr<dht::crypto::Certificate>;

#ifdef __cplusplus
extern "C" {
#endif

#include <errno.h>

const char*
dht_version()
{
    return dht::version();
}

// dht::InfoHash

inline dht_infohash
dht_infohash_to_c(const dht::InfoHash& h)
{
    dht_infohash ret;
    *reinterpret_cast<dht::InfoHash*>(&ret) = h;
    return ret;
}

inline dht_pkid
dht_pkid_to_c(const dht::PkId& h)
{
    dht_pkid ret;
    *reinterpret_cast<dht::PkId*>(&ret) = h;
    return ret;
}

const char*
dht_infohash_print(const dht_infohash* h)
{
    return reinterpret_cast<const dht::InfoHash*>(h)->to_c_str();
}

void
dht_infohash_zero(dht_infohash* h)
{
    *reinterpret_cast<dht::InfoHash*>(h) = dht::InfoHash {};
}

void
dht_infohash_random(dht_infohash* h)
{
    *reinterpret_cast<dht::InfoHash*>(h) = dht::InfoHash::getRandom();
}

void
dht_infohash_get(dht_infohash* h, const uint8_t* dat, size_t dat_size)
{
    *reinterpret_cast<dht::InfoHash*>(h) = dht::InfoHash::get(dat, dat_size);
}

void
dht_infohash_get_from_string(dht_infohash* h, const char* dat)
{
    *reinterpret_cast<dht::InfoHash*>(h) = dht::InfoHash::get((const uint8_t*) dat, (size_t) strlen(dat));
}

bool
dht_infohash_is_zero(const dht_infohash* h)
{
    return !static_cast<bool>(*reinterpret_cast<const dht::InfoHash*>(h));
}

void
dht_infohash_from_hex(dht_infohash* h, const char* dat)
{
    *h = dht_infohash_to_c(dht::InfoHash(std::string_view(dat, HASH_LEN * 2)));
}

void
dht_infohash_from_hex_null(dht_infohash* h, const char* dat)
{
    *h = dht_infohash_to_c(dht::InfoHash(std::string_view(dat)));
}

const char*
dht_pkid_print(const dht_pkid* h)
{
    return reinterpret_cast<const dht::PkId*>(h)->to_c_str();
}

// dht::Blob
void
dht_blob_delete(dht_blob* data)
{
    delete reinterpret_cast<dht::Blob*>(data);
}

dht_data_view
dht_blob_get_data(const dht_blob* data)
{
    dht_data_view view;
    view.data = reinterpret_cast<const dht::Blob*>(data)->data();
    view.size = reinterpret_cast<const dht::Blob*>(data)->size();
    return view;
}

// dht::Value
dht_data_view
dht_value_get_data(const dht_value* data)
{
    const ValueSp& vsp(*reinterpret_cast<const ValueSp*>(data));
    dht_data_view view;
    view.data = vsp->data.data();
    view.size = vsp->data.size();
    return view;
}

dht_value_id
dht_value_get_id(const dht_value* data)
{
    const ValueSp& vsp(*reinterpret_cast<const ValueSp*>(data));
    return vsp->id;
}

dht_publickey*
dht_value_get_owner(const dht_value* data)
{
    const ValueSp& vsp(*reinterpret_cast<const ValueSp*>(data));
    return vsp->owner ? reinterpret_cast<dht_publickey*>(new PubkeySp(vsp->owner)) : nullptr;
}

dht_infohash
dht_value_get_recipient(const dht_value* data)
{
    const ValueSp& vsp(*reinterpret_cast<const ValueSp*>(data));
    return dht_infohash_to_c(vsp->recipient);
}

const char*
dht_value_get_user_type(const dht_value* data)
{
    const ValueSp& vsp(*reinterpret_cast<const ValueSp*>(data));
    return vsp->user_type.c_str();
}

void
dht_value_set_user_type(dht_value* data, const char* user_type)
{
    (*reinterpret_cast<ValueSp*>(data))->user_type = user_type;
}

dht_value*
dht_value_with_id_new(const uint8_t* data, size_t size, uint64_t value_id)
{
    return reinterpret_cast<dht_value*>(
        new ValueSp(std::make_shared<dht::Value>(dht::ValueType::USER_DATA.id, data, size, value_id)));
}

dht_value*
dht_value_new(const uint8_t* data, size_t size)
{
    return dht_value_with_id_new(data, size, 0);
}

dht_value*
dht_value_new_from_string(const char* str)
{
    ValueSp value = std::make_shared<dht::Value>((const uint8_t*) str, strlen(str));
    value->user_type = "text/plain";
    return reinterpret_cast<dht_value*>(new ValueSp(std::move(value)));
}

dht_value*
dht_value_ref(const dht_value* v)
{
    return reinterpret_cast<dht_value*>(new ValueSp(*reinterpret_cast<const ValueSp*>(v)));
}

void
dht_value_unref(dht_value* v)
{
    delete reinterpret_cast<ValueSp*>(v);
}

// dht::crypto::PublicKey
dht_publickey*
dht_publickey_import(const uint8_t* dat, size_t dat_size)
{
    try {
        return reinterpret_cast<dht_publickey*>(
            new PubkeySp(std::make_shared<const dht::crypto::PublicKey>(dat, dat_size)));
    } catch (const dht::crypto::CryptoException& e) {
        return nullptr;
    }
}

void
dht_publickey_delete(dht_publickey* pk)
{
    delete reinterpret_cast<PubkeySp*>(pk);
}

int
dht_publickey_export(const dht_publickey* pk, char* out, size_t* outlen)
{
    const auto& pkey = *reinterpret_cast<const PubkeySp*>(pk);
    return pkey->pack((uint8_t*) out, outlen);
}

dht_infohash
dht_publickey_get_id(const dht_publickey* pk)
{
    const auto& pkey = *reinterpret_cast<const PubkeySp*>(pk);
    return dht_infohash_to_c(pkey->getId());
}

dht_pkid
dht_publickey_get_long_id(const dht_publickey* pk)
{
    const auto& pkey = *reinterpret_cast<const PubkeySp*>(pk);
    return dht_pkid_to_c(pkey->getLongId());
}

bool
dht_publickey_check_signature(
    const dht_publickey* pk, const char* data, size_t data_size, const char* signature, size_t signature_size)
{
    const auto& pkey = *reinterpret_cast<const PubkeySp*>(pk);
    return pkey->checkSignature((const uint8_t*) data, data_size, (const uint8_t*) signature, signature_size);
}

dht_blob*
dht_publickey_encrypt(const dht_publickey* pk, const char* data, size_t data_size)
{
    const auto& pkey = *reinterpret_cast<const PubkeySp*>(pk);
    try {
        auto rdata = std::make_unique<dht::Blob>();
        *rdata = pkey->encrypt((const uint8_t*) data, data_size);
        return (dht_blob*) rdata.release();
    } catch (...) {
        return nullptr;
    }
}

// dht::crypto::PrivateKey
dht_privatekey*
dht_privatekey_generate(unsigned key_length_bits)
{
    if (key_length_bits == 0)
        key_length_bits = 4096;
    return reinterpret_cast<dht_privatekey*>(
        new PrivkeySp(std::make_shared<dht::crypto::PrivateKey>(dht::crypto::PrivateKey::generate(key_length_bits))));
}

dht_privatekey*
dht_privatekey_import(const uint8_t* dat, size_t dat_size, const char* password)
{
    try {
        return reinterpret_cast<dht_privatekey*>(
            new PrivkeySp(std::make_shared<dht::crypto::PrivateKey>(dat, dat_size, password)));
    } catch (const dht::crypto::CryptoException& e) {
        return nullptr;
    }
}

int
dht_privatekey_export(const dht_privatekey* k, char* out, size_t* out_size, const char* password)
{
    if (!out or !out_size or !*out_size)
        return -1;
    const auto& key = *reinterpret_cast<const PrivkeySp*>(k);
    return key->serialize((uint8_t*) out, out_size, password);
}

dht_publickey*
dht_privatekey_get_publickey(const dht_privatekey* k)
{
    const auto& key = *reinterpret_cast<const PrivkeySp*>(k);
    return reinterpret_cast<dht_publickey*>(new PubkeySp(key->getSharedPublicKey()));
}

dht_blob*
dht_privatekey_decrypt(const dht_privatekey* k, const char* data, size_t data_size)
{
    const auto& key = *reinterpret_cast<const PrivkeySp*>(k);
    try {
        auto rdata = std::make_unique<dht::Blob>();
        *rdata = key->decrypt((const uint8_t*) data, data_size);
        return (dht_blob*) rdata.release();
    } catch (...) {
        return nullptr;
    }
}

void
dht_privatekey_delete(dht_privatekey* pk)
{
    delete reinterpret_cast<PrivkeySp*>(pk);
}

// dht::crypto::Certificate
dht_certificate*
dht_certificate_import(const uint8_t* dat, size_t dat_size)
{
    try {
        return reinterpret_cast<dht_certificate*>(new CertSp(std::make_shared<dht::crypto::Certificate>(dat, dat_size)));
    } catch (const dht::crypto::CryptoException& e) {
        return nullptr;
    }
}

void
dht_certificate_delete(dht_certificate* c)
{
    delete reinterpret_cast<CertSp*>(c);
}

dht_infohash
dht_certificate_get_id(const dht_certificate* c)
{
    const auto& cert = *reinterpret_cast<const CertSp*>(c);
    return dht_infohash_to_c(cert->getId());
}

dht_pkid
dht_certificate_get_long_id(const dht_certificate* c)
{
    const auto& cert = *reinterpret_cast<const CertSp*>(c);
    return dht_pkid_to_c(cert->getLongId());
}

dht_publickey*
dht_certificate_get_publickey(const dht_certificate* c)
{
    const auto& cert = *reinterpret_cast<const CertSp*>(c);
    return reinterpret_cast<dht_publickey*>(new PubkeySp(cert->getSharedPublicKey()));
}

#ifdef __cplusplus
} // extern "C"
// Function with internal C++ linkage
#endif

// dht::crypto::Identity
inline dht::crypto::Identity
dht_identity_from_c(const dht_identity* cid)
{
    dht::crypto::Identity id {};
    if (cid and cid->privatekey)
        id.first = *reinterpret_cast<const PrivkeySp*>(cid->privatekey);
    if (cid and cid->certificate)
        id.second = *reinterpret_cast<const CertSp*>(cid->certificate);
    return id;
}

inline dht_identity
dht_identity_to_c(const dht::crypto::Identity& id)
{
    dht_identity cid {};
    cid.privatekey = id.first ? reinterpret_cast<dht_privatekey*>(new PrivkeySp(id.first)) : NULL;
    cid.certificate = id.second ? reinterpret_cast<dht_certificate*>(new CertSp(id.second)) : NULL;
    return cid;
}

#ifdef __cplusplus
extern "C" {
#endif

OPENDHT_C_PUBLIC dht_identity
dht_identity_generate(const char* common_name, const dht_identity* ca)
{
    return dht_identity_to_c(dht::crypto::generateIdentity(common_name, dht_identity_from_c(ca)));
}

OPENDHT_C_PUBLIC void
dht_identity_delete(dht_identity* id)
{
    if (id->certificate) {
        dht_certificate_delete(id->certificate);
        id->certificate = NULL;
    }
    if (id->privatekey) {
        dht_privatekey_delete(id->privatekey);
        id->privatekey = NULL;
    }
}

// config
void
dht_runner_config_default(dht_runner_config* config)
{
    memset(config, 0, sizeof(dht_runner_config));
    config->threaded = true;
}

// dht::DhtRunner
dht_runner*
dht_runner_new(void)
{
    return reinterpret_cast<dht_runner*>(new dht::DhtRunner);
}

void
dht_runner_delete(dht_runner* runner)
{
    delete reinterpret_cast<dht::DhtRunner*>(runner);
}

int
dht_runner_run(dht_runner* r, in_port_t port)
{
    auto runner = reinterpret_cast<dht::DhtRunner*>(r);
    try {
        runner->run(port, {}, true);
    } catch (...) {
        return ENOTCONN;
    }
    return 0;
}

int
dht_runner_run_config(dht_runner* r, in_port_t port, const dht_runner_config* conf)
{
    auto runner = reinterpret_cast<dht::DhtRunner*>(r);
    try {
        dht::DhtRunner::Config config;
        config.dht_config.node_config.is_bootstrap = conf->dht_config.node_config.is_bootstrap;
        config.dht_config.node_config.maintain_storage = conf->dht_config.node_config.maintain_storage;
        config.dht_config.node_config.node_id = *reinterpret_cast<const dht::InfoHash*>(
            &conf->dht_config.node_config.node_id);
        config.dht_config.node_config.network = conf->dht_config.node_config.network;
        config.dht_config.node_config.persist_path = conf->dht_config.node_config.persist_path
                                                         ? std::string(conf->dht_config.node_config.persist_path)
                                                         : std::string {};

        if (conf->dht_config.id.privatekey)
            config.dht_config.id.first = *reinterpret_cast<const PrivkeySp*>(conf->dht_config.id.privatekey);

        if (conf->dht_config.id.certificate)
            config.dht_config.id.second = *reinterpret_cast<const CertSp*>(conf->dht_config.id.certificate);

        config.threaded = conf->threaded;
        config.proxy_server = conf->proxy_server ? std::string(conf->proxy_server) : std::string {};
        config.push_node_id = conf->push_node_id ? std::string(conf->push_node_id) : std::string {};
        config.push_token = conf->push_token ? std::string(conf->push_token) : std::string {};
        config.push_topic = conf->push_topic ? std::string(conf->push_topic) : std::string {};
        config.push_platform = conf->push_platform ? std::string(conf->push_platform) : std::string {};
        config.proxy_user_agent = conf->proxy_user_agent ? std::string(conf->proxy_user_agent) : std::string {};
        config.peer_discovery = conf->peer_discovery;
        config.peer_publish = conf->peer_publish;
        if (conf->server_ca)
            config.server_ca = *reinterpret_cast<const CertSp*>(conf->server_ca);
        config.client_identity = dht_identity_from_c(&conf->client_identity);

        dht::DhtRunner::Context context;
        if (conf->log) {
            context.logger = dht::log::getStdLogger();
        }
        runner->run(port, config, std::move(context));
    } catch (...) {
        return ENOTCONN;
    }
    return 0;
}

void
dht_runner_ping(dht_runner* r, struct sockaddr* addr, socklen_t addr_len, dht_done_cb done_cb, void* cb_user_data)
{
    auto runner = reinterpret_cast<dht::DhtRunner*>(r);
    if (done_cb) {
        runner->bootstrap(dht::SockAddr(addr, addr_len),
                          [done_cb, cb_user_data](bool ok) { done_cb(ok, cb_user_data); });
    } else {
        runner->bootstrap(dht::SockAddr(addr, addr_len));
    }
}

void
dht_runner_bootstrap(dht_runner* r, const char* host, const char* service)
{
    auto runner = reinterpret_cast<dht::DhtRunner*>(r);
    if (service)
        runner->bootstrap(host, service);
    else
        runner->bootstrap(host);
}

void
dht_runner_get(dht_runner* r, const dht_infohash* h, dht_get_cb cb, dht_done_cb done_cb, void* cb_user_data)
{
    auto runner = reinterpret_cast<dht::DhtRunner*>(r);
    auto hash = reinterpret_cast<const dht::InfoHash*>(h);
    runner->get(
        *hash,
        [cb, cb_user_data](std::shared_ptr<dht::Value> value) {
            return cb(reinterpret_cast<const dht_value*>(&value), cb_user_data);
        },
        [done_cb, cb_user_data](bool ok) {
            if (done_cb)
                done_cb(ok, cb_user_data);
        });
}

struct ScopeGuardCb
{
    ScopeGuardCb(dht_shutdown_cb cb, void* data)
        : onDestroy(cb)
        , userData(data)
    {}

    ~ScopeGuardCb()
    {
        if (onDestroy)
            onDestroy((void*) userData);
    }

private:
    const dht_shutdown_cb onDestroy;
    void const* userData;
};

dht_op_token*
dht_runner_listen(dht_runner* r, const dht_infohash* h, dht_value_cb cb, dht_shutdown_cb done_cb, void* cb_user_data)
{
    auto runner = reinterpret_cast<dht::DhtRunner*>(r);
    auto hash = reinterpret_cast<const dht::InfoHash*>(h);
    auto fret = new std::future<size_t>;
    *fret = runner->listen(*hash,
                           [cb,
                            cb_user_data,
                            guard = done_cb ? std::make_shared<ScopeGuardCb>(done_cb, cb_user_data)
                                            : std::shared_ptr<ScopeGuardCb> {}](
                               const std::vector<std::shared_ptr<dht::Value>>& values, bool expired) {
                               for (const auto& value : values) {
                                   if (not cb(reinterpret_cast<const dht_value*>(&value), expired, cb_user_data))
                                       return false;
                               }
                               return true;
                           });
    return (dht_op_token*) fret;
}

void
dht_runner_cancel_listen(dht_runner* r, const dht_infohash* h, dht_op_token* t)
{
    auto runner = reinterpret_cast<dht::DhtRunner*>(r);
    auto hash = reinterpret_cast<const dht::InfoHash*>(h);
    auto token = reinterpret_cast<std::future<size_t>*>(t);
    runner->cancelListen(*hash, std::move(*token));
}

void
dht_op_token_delete(dht_op_token* token)
{
    delete reinterpret_cast<std::future<size_t>*>(token);
}

void
dht_runner_put(
    dht_runner* r, const dht_infohash* h, const dht_value* v, dht_done_cb done_cb, void* cb_user_data, bool permanent)
{
    auto runner = reinterpret_cast<dht::DhtRunner*>(r);
    auto hash = reinterpret_cast<const dht::InfoHash*>(h);
    auto value = reinterpret_cast<const ValueSp*>(v);
    runner->put(
        *hash,
        *value,
        [done_cb, cb_user_data](bool ok) {
            if (done_cb)
                done_cb(ok, cb_user_data);
        },
        dht::time_point::max(),
        permanent);
}

void
dht_runner_put_signed(
    dht_runner* r, const dht_infohash* h, const dht_value* v, dht_done_cb done_cb, void* cb_user_data, bool permanent)
{
    auto runner = reinterpret_cast<dht::DhtRunner*>(r);
    auto hash = reinterpret_cast<const dht::InfoHash*>(h);
    auto value = reinterpret_cast<const ValueSp*>(v);
    runner->putSigned(
        *hash,
        *value,
        [done_cb, cb_user_data](bool ok) {
            if (done_cb)
                done_cb(ok, cb_user_data);
        },
        permanent);
}

void
dht_runner_put_encrypted(dht_runner* r,
                         const dht_infohash* h,
                         const dht_pkid* to,
                         const dht_value* v,
                         dht_done_cb done_cb,
                         void* cb_user_data,
                         bool permanent)
{
    auto runner = reinterpret_cast<dht::DhtRunner*>(r);
    auto hash = reinterpret_cast<const dht::InfoHash*>(h);
    auto toHash = reinterpret_cast<const dht::PkId*>(to);
    auto value = reinterpret_cast<const ValueSp*>(v);
    runner->putEncrypted(
        *hash,
        *toHash,
        *value,
        [done_cb, cb_user_data](bool ok) {
            if (done_cb)
                done_cb(ok, cb_user_data);
        },
        permanent);
}

void
dht_runner_cancel_put(dht_runner* r, const dht_infohash* h, dht_value_id value_id)
{
    auto runner = reinterpret_cast<dht::DhtRunner*>(r);
    auto hash = reinterpret_cast<const dht::InfoHash*>(h);
    runner->cancelPut(*hash, value_id);
}

void
dht_runner_shutdown(dht_runner* r, dht_shutdown_cb done_cb, void* cb_user_data)
{
    auto runner = reinterpret_cast<dht::DhtRunner*>(r);
    runner->shutdown([done_cb, cb_user_data]() {
        if (done_cb)
            done_cb(cb_user_data);
    });
}

bool
dht_runner_is_running(const dht_runner* r)
{
    if (not r)
        return false;
    auto runner = reinterpret_cast<const dht::DhtRunner*>(r);
    return runner->isRunning();
}

in_port_t
dht_runner_get_bound_port(const dht_runner* r, sa_family_t af)
{
    auto runner = reinterpret_cast<const dht::DhtRunner*>(r);
    return runner->getBoundPort(af);
}

dht_infohash
dht_runner_get_node_id(const dht_runner* r)
{
    auto runner = reinterpret_cast<const dht::DhtRunner*>(r);
    dht_infohash ret;
    *reinterpret_cast<dht::InfoHash*>(&ret) = runner->getNodeId();
    return ret;
}

dht_infohash
dht_runner_get_id(const dht_runner* r)
{
    auto runner = reinterpret_cast<const dht::DhtRunner*>(r);
    dht_infohash ret;
    *reinterpret_cast<dht::InfoHash*>(&ret) = runner->getId();
    return ret;
}

struct sockaddr**
dht_runner_get_public_address(const dht_runner* r)
{
    auto runner = reinterpret_cast<const dht::DhtRunner*>(r);
    auto addrs = const_cast<dht::DhtRunner*>(runner)->getPublicAddress();
    if (addrs.empty())
        return nullptr;
    auto ret = (struct sockaddr**) malloc(sizeof(struct sockaddr*) * (addrs.size() + 1));
    for (size_t i = 0; i < addrs.size(); i++)
        ret[i] = addrs[i].release();
    ret[addrs.size()] = nullptr;
    return ret;
}

#ifdef __cplusplus
}
#endif
