/**
 * Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "datasystem/common/rdma/urma_dlopen_util.h"

#include <climits>
#include <dlfcn.h>
#include <link.h>

#include "securec.h"

#include "datasystem/common/log/log.h"

namespace {
bool g_init = false;
void *g_urma = nullptr;
void *g_urmaUbAgg = nullptr;

const char *urmaLibs[] = { "liburma.so.0", "liburma.so" };
const char *urmaUbAggLibs[] = { "/usr/lib64/urma/liburma_ubagg.so.0", "/usr/lib64/urma/liburma_ubagg.so" };
enum class UrmaLibType { URMA, UB_AGG };

template <size_t N>
void *TryLoadLib(const char *const (&candidates)[N])
{
    void *handle = nullptr;
    for (size_t i = 0; i < N; ++i) {
        const char *candidate = candidates[i];
        handle = dlopen(candidate, RTLD_LAZY | RTLD_GLOBAL);
        if (handle) {
            char origin[PATH_MAX] = {};
            const bool hasOrigin = (dlinfo(handle, RTLD_DI_ORIGIN, origin) == 0 && origin[0] != '\0');
            LOG(INFO) << "[UrmaDlopen] dlopen succeeded for: " << candidate
                      << (hasOrigin ? std::string(", origin path: ") + origin : "");
            break;
        } else {
            LOG(ERROR) << "[UrmaDlopen] dlopen failed for: " << candidate << " -> " << dlerror();
        }
    }
    return handle;
}

template <UrmaLibType LibType>
void *LoadUrmaSymbol(const char *name)
{
    void *handle = (LibType == UrmaLibType::URMA) ? g_urma : g_urmaUbAgg;
    if (!handle) {
        const char *libName = (LibType == UrmaLibType::URMA) ? "liburma" : "liburma_ubagg";
        LOG(INFO) << "[UrmaDlopen] " << libName << " handle is null before loading symbol: " << name;
        return nullptr;
    }
    void *sym = dlsym(handle, name);
    if (!sym) {
        LOG(ERROR) << "[UrmaDlopen] dlsym failed for " << name << ": " << dlerror();
    }
    return sym;
}

template <typename Fn, UrmaLibType LibType = UrmaLibType::URMA>
Fn LoadFn(const char *name)
{
    void *sym = LoadUrmaSymbol<LibType>(name);
    if (!sym) {
        return nullptr;
    }
    Fn fn = nullptr;
    int ret = memcpy_s(&fn, sizeof(Fn), &sym, sizeof(void *));
    if (ret != 0) {
        LOG(ERROR) << "[UrmaDlopen] memcpy_s failed while casting URMA symbol: " << ret;
        return nullptr;
    }
    return fn;
}

template <UrmaLibType LibType, typename Ret, typename Fn, typename... Args>
Ret CallRet(const char *name, Ret fallback, Args... args)
{
    auto fn = LoadFn<Fn, LibType>(name);
    if (!fn) {
        return fallback;
    }
    return fn(args...);
}

template <typename Fn, typename... Args>
void CallVoid(const char *name, Args... args)
{
    auto fn = LoadFn<Fn>(name);
    if (!fn) {
        return;
    }
    fn(args...);
}

template <typename Fn, typename... Args>
void *CallPtr(const char *name, Args... args)
{
    auto fn = LoadFn<Fn>(name);
    if (!fn) {
        return nullptr;
    }
    return fn(args...);
}
}  // namespace

namespace datasystem {
namespace urma_dlopen {

bool Init()
{
    if (g_init) {
        return true;
    }
    g_urma = TryLoadLib(urmaLibs);
    if (!g_urma) {
        LOG(ERROR) << "[UrmaDlopen] Failed to load liburma: " << dlerror();
        Cleanup();
        return false;
    }
    g_urmaUbAgg = TryLoadLib(urmaUbAggLibs);
    if (!g_urmaUbAgg) {
        LOG(ERROR) << "[UrmaDlopen] Failed to load liburma_ubagg: " << dlerror();
        Cleanup();
        return false;
    }
    g_init = true;
    return true;
}

bool IsAvailable()
{
    return g_init;
}

void Cleanup()
{
    if (g_urma) {
        dlclose(g_urma);
        g_urma = nullptr;
    }
    if (g_urmaUbAgg) {
        dlclose(g_urmaUbAgg);
        g_urmaUbAgg = nullptr;
    }
    g_init = false;
}

}  // namespace urma_dlopen
}  // namespace datasystem

static constexpr urma_status_t kUrmaDlopenErrorStatus = static_cast<urma_status_t>(-1);

urma_status_t ds_urma_init(const urma_init_attr_t *attr)
{
    return CallRet<UrmaLibType::URMA, urma_status_t, decltype(&ds_urma_init)>("urma_init", kUrmaDlopenErrorStatus,
                                                                              attr);
}

urma_status_t ds_urma_uninit(void)
{
    return CallRet<UrmaLibType::URMA, urma_status_t, decltype(&ds_urma_uninit)>("urma_uninit", kUrmaDlopenErrorStatus);
}

urma_status_t ds_urma_register_log_func(urma_log_cb_t log_cb)
{
    return CallRet<UrmaLibType::URMA, urma_status_t, decltype(&ds_urma_register_log_func)>(
        "urma_register_log_func", kUrmaDlopenErrorStatus, log_cb);
}

urma_status_t ds_urma_unregister_log_func(void)
{
    return CallRet<UrmaLibType::URMA, urma_status_t, decltype(&ds_urma_unregister_log_func)>("urma_unregister_log_func",
                                                                                             kUrmaDlopenErrorStatus);
}

urma_device_t **ds_urma_get_device_list(int *dev_num)
{
    return static_cast<urma_device_t **>(CallPtr<decltype(&ds_urma_get_device_list)>("urma_get_device_list", dev_num));
}

urma_device_t *ds_urma_get_device_by_name(char *name)
{
    return static_cast<urma_device_t *>(
        CallPtr<decltype(&ds_urma_get_device_by_name)>("urma_get_device_by_name", name));
}

urma_status_t ds_urma_query_device(urma_device_t *device, urma_device_attr_t *attr)
{
    return CallRet<UrmaLibType::URMA, urma_status_t, decltype(&ds_urma_query_device)>(
        "urma_query_device", kUrmaDlopenErrorStatus, device, attr);
}

urma_eid_info_t *ds_urma_get_eid_list(urma_device_t *device, uint32_t *eid_count)
{
    return static_cast<urma_eid_info_t *>(
        CallPtr<decltype(&ds_urma_get_eid_list)>("urma_get_eid_list", device, eid_count));
}

void ds_urma_free_eid_list(urma_eid_info_t *eid_list)
{
    CallVoid<decltype(&ds_urma_free_eid_list)>("urma_free_eid_list", eid_list);
}

urma_context_t *ds_urma_create_context(urma_device_t *device, uint32_t eid_index)
{
    return static_cast<urma_context_t *>(
        CallPtr<decltype(&ds_urma_create_context)>("urma_create_context", device, eid_index));
}

urma_status_t ds_urma_delete_context(urma_context_t *context)
{
    return CallRet<UrmaLibType::URMA, urma_status_t, decltype(&ds_urma_delete_context)>(
        "urma_delete_context", kUrmaDlopenErrorStatus, context);
}

urma_status_t ds_urma_set_context_opt(urma_context_t *context, urma_opt_name_t opt_name, const void *opt_value,
                                      size_t opt_len)
{
    return CallRet<UrmaLibType::URMA, urma_status_t, decltype(&ds_urma_set_context_opt)>(
        "urma_set_context_opt", kUrmaDlopenErrorStatus, context, opt_name, opt_value, opt_len);
}

urma_status_t ds_urma_user_ctl(urma_context_t *ctx, urma_user_ctl_in_t *in, urma_user_ctl_out_t *out)
{
    return CallRet<UrmaLibType::URMA, urma_status_t, decltype(&ds_urma_user_ctl)>("urma_user_ctl",
                                                                                  kUrmaDlopenErrorStatus, ctx, in, out);
}

urma_jfce_t *ds_urma_create_jfce(urma_context_t *context)
{
    return static_cast<urma_jfce_t *>(CallPtr<decltype(&ds_urma_create_jfce)>("urma_create_jfce", context));
}

urma_status_t ds_urma_delete_jfce(urma_jfce_t *jfce)
{
    return CallRet<UrmaLibType::URMA, urma_status_t, decltype(&ds_urma_delete_jfce)>("urma_delete_jfce",
                                                                                     kUrmaDlopenErrorStatus, jfce);
}

urma_jfc_t *ds_urma_create_jfc(urma_context_t *context, const urma_jfc_cfg_t *config)
{
    return static_cast<urma_jfc_t *>(CallPtr<decltype(&ds_urma_create_jfc)>("urma_create_jfc", context, config));
}

urma_status_t ds_urma_delete_jfc(urma_jfc_t *jfc)
{
    return CallRet<UrmaLibType::URMA, urma_status_t, decltype(&ds_urma_delete_jfc)>("urma_delete_jfc",
                                                                                    kUrmaDlopenErrorStatus, jfc);
}

urma_status_t ds_urma_rearm_jfc(urma_jfc_t *jfc, bool enable_events)
{
    return CallRet<UrmaLibType::URMA, urma_status_t, decltype(&ds_urma_rearm_jfc)>(
        "urma_rearm_jfc", kUrmaDlopenErrorStatus, jfc, enable_events);
}

urma_jfr_t *ds_urma_create_jfr(urma_context_t *context, const urma_jfr_cfg_t *config)
{
    return static_cast<urma_jfr_t *>(CallPtr<decltype(&ds_urma_create_jfr)>("urma_create_jfr", context, config));
}

urma_status_t ds_urma_delete_jfr(urma_jfr_t *jfr)
{
    return CallRet<UrmaLibType::URMA, urma_status_t, decltype(&ds_urma_delete_jfr)>(
        "urma_delete_jfr", kUrmaDlopenErrorStatus, jfr);
}

urma_jetty_t *ds_urma_create_jetty(urma_context_t *context, urma_jetty_cfg_t *config)
{
    return static_cast<urma_jetty_t *>(CallPtr<decltype(&ds_urma_create_jetty)>("urma_create_jetty", context, config));
}

urma_status_t ds_urma_delete_jetty(urma_jetty_t *jetty)
{
    return CallRet<UrmaLibType::URMA, urma_status_t, decltype(&ds_urma_delete_jetty)>(
        "urma_delete_jetty", kUrmaDlopenErrorStatus, jetty);
}

urma_status_t ds_urma_modify_jetty(urma_jetty_t *jetty, urma_jetty_attr_t *attr)
{
    return CallRet<UrmaLibType::URMA, urma_status_t, decltype(&ds_urma_modify_jetty)>(
        "urma_modify_jetty", kUrmaDlopenErrorStatus, jetty, attr);
}

urma_target_seg_t *ds_urma_register_seg(urma_context_t *context, const urma_seg_cfg_t *config)
{
    return static_cast<urma_target_seg_t *>(
        CallPtr<decltype(&ds_urma_register_seg)>("urma_register_seg", context, config));
}

int ds_urma_wait_jfc(urma_jfce_t *jfce, int max_events, int timeout_ms, urma_jfc_t **ev_jfc)
{
    return CallRet<UrmaLibType::URMA, int, decltype(&ds_urma_wait_jfc)>(
        "urma_wait_jfc", -1, jfce, max_events, timeout_ms, ev_jfc);
}

int ds_urma_poll_jfc(urma_jfc_t *jfc, int max_cr, urma_cr_t *complete_records)
{
    return CallRet<UrmaLibType::URMA, int, decltype(&ds_urma_poll_jfc)>(
        "urma_poll_jfc", -1, jfc, max_cr, complete_records);
}

void ds_urma_ack_jfc(urma_jfc_t **ev_jfc, uint32_t *ack_cnt, int num)
{
    CallVoid<decltype(&ds_urma_ack_jfc)>("urma_ack_jfc", ev_jfc, ack_cnt, num);
}

urma_target_jetty_t *ds_urma_import_jetty(urma_context_t *context, urma_rjetty_t *remote_jetty,
                                          urma_token_t *token)
{
    return static_cast<urma_target_jetty_t *>(
        CallPtr<decltype(&ds_urma_import_jetty)>("urma_import_jetty", context, remote_jetty, token));
}

urma_status_t ds_urma_unimport_jetty(urma_target_jetty_t *tjetty)
{
    return CallRet<UrmaLibType::URMA, urma_status_t, decltype(&ds_urma_unimport_jetty)>(
        "urma_unimport_jetty", kUrmaDlopenErrorStatus, tjetty);
}

urma_status_t ds_urma_post_jetty_send_wr(urma_jetty_t *jetty, urma_jfs_wr_t *wr, urma_jfs_wr_t **bad_wr)
{
    return CallRet<UrmaLibType::URMA, urma_status_t, decltype(&ds_urma_post_jetty_send_wr)>(
        "urma_post_jetty_send_wr", kUrmaDlopenErrorStatus, jetty, wr, bad_wr);
}

urma_target_seg_t *ds_urma_import_seg(urma_context_t *context, urma_seg_t *seg, urma_token_t *token, int flags,
                                      urma_import_seg_flag_t import_flag)
{
    return static_cast<urma_target_seg_t *>(
        CallPtr<decltype(&ds_urma_import_seg)>("urma_import_seg", context, seg, token, flags, import_flag));
}

urma_status_t ds_urma_unregister_seg(urma_target_seg_t *seg)
{
    return CallRet<UrmaLibType::URMA, urma_status_t, decltype(&ds_urma_unregister_seg)>("urma_unregister_seg",
                                                                                        kUrmaDlopenErrorStatus, seg);
}

urma_status_t ds_urma_unimport_seg(urma_target_seg_t *seg)
{
    return CallRet<UrmaLibType::URMA, urma_status_t, decltype(&ds_urma_unimport_seg)>("urma_unimport_seg",
                                                                                      kUrmaDlopenErrorStatus, seg);
}

urma_status_t ds_urma_get_async_event(urma_context_t *context, urma_async_event_t *event)
{
    return CallRet<UrmaLibType::URMA, urma_status_t, decltype(&ds_urma_get_async_event)>(
        "urma_get_async_event", kUrmaDlopenErrorStatus, context, event);
}

void ds_urma_ack_async_event(urma_async_event_t *event)
{
    CallVoid<decltype(&ds_urma_ack_async_event)>("urma_ack_async_event", event);
}

urma_status_t ds_urma_start_perf(void)
{
    return CallRet<UrmaLibType::URMA, urma_status_t, decltype(&ds_urma_start_perf)>("urma_start_perf",
                                                                                    kUrmaDlopenErrorStatus);
}

urma_status_t ds_urma_stop_perf(void)
{
    return CallRet<UrmaLibType::URMA, urma_status_t, decltype(&ds_urma_stop_perf)>("urma_stop_perf",
                                                                                   kUrmaDlopenErrorStatus);
}

urma_status_t ds_urma_get_perf_info(char *perf_buf, uint32_t *length)
{
    return CallRet<UrmaLibType::URMA, urma_status_t, decltype(&ds_urma_get_perf_info)>(
        "urma_get_perf_info", kUrmaDlopenErrorStatus, perf_buf, length);
}