/*
 * Copyright (c) 2025 Huawei Technologies Co., Ltd.
 * openUBMC is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *         http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */

#include <mc/exception.h>
#include <mc/filesystem.h>
#include <mc/log.h>

#ifndef __ZEPHYR__
#include <dlfcn.h>
#endif

#include <devmon-common/device_driver.h>
#include <devmon-common/driver_abi.h>
#include <devmon-common/driver_source.h>
#include <devmon-common/model_privilege.h>

namespace devmon {
device_driver::device_driver(const std::string& driver_name) : m_driver_name(driver_name)
{}

device_driver::~device_driver()
{
    unload();
}

std::vector<driver_load_info> device_driver::s_load_info_table;
std::mutex                    device_driver::s_load_info_mutex;

mc::string dump_device_driver(const device_driver_t& driver, driver_handle_t handle)
{
    if (driver.dump == nullptr) {
        return {};
    }

    const char* dump_cstr = driver.dump(handle);
    return dump_cstr != nullptr ? mc::string(dump_cstr) : mc::string();
}

void device_driver::load(const std::string& driver_path)
{
    std::string driver_file_name = "lib" + m_driver_name + ".so";
    load_file(driver_path + "/" + driver_file_name);
}

void device_driver::load_file(const std::string& library_file)
{
    load_file(library_file, {});
}

void device_driver::load_file(const std::string& library_file, const driver_load_meta& meta)
{
#ifdef __ZEPHYR__
    // Zephyr 镜像静态链接、无动态加载器;驱动经 device_driver_factory::
    // set_device_driver 在 SYS_INIT 期静态注册(见 libs/devmon/src/devmon.cpp)。
    (void)library_file;
    (void)meta;
    MC_THROW(mc::system_exception, "Dynamic driver loading is not supported on Zephyr for driver: ${name}",
             ("name", m_driver_name));
#else
    MC_ASSERT_THROW(m_driver_handle == nullptr, mc::invalid_arg_exception, "Driver already loaded: ${name}",
                    ("name", m_driver_name));

    auto start = std::chrono::steady_clock::now();

    driver_load_info info;
    info.success      = false;
    info.load_time_ms = 0.0;
    info.device_count = 0;
    info.driver_name  = mc::filesystem::path(library_file).filename().string();
    info.source       = meta.source.empty() ? "image" : meta.source;
    info.load_path    = meta.load_path;
    if (info.load_path.empty()) {
        info.load_path = mc::filesystem::path(library_file).parent_path().string();
        if (!info.load_path.empty() && info.load_path.back() != '/') {
            info.load_path += "/";
        }
    }
    info.checksum_result      = meta.checksum_result.empty() ? "na" : meta.checksum_result;
    info.gold_checksum_result = meta.gold_checksum_result;
    info.temp_checksum_result = meta.temp_checksum_result;

    ilog("Trying to load driver from: ${file}", ("file", library_file));

    auto* driver_handle = dlopen(library_file.c_str(), RTLD_LAZY);
    if (driver_handle == nullptr) {
        std::string error_msg = dlerror();
        info.error_msg        = error_msg;
        info.success          = false;
        info.load_time_ms = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start).count();

        record_load_info(std::move(info));
        MC_THROW(mc::system_exception, "Failed to load driver: ${error}", ("error", error_msg));
    }

    m_driver_handle = driver_handle;

    bool        registration_failed = false;
    std::string registration_error;
    try {
        register_driver();
        info.success      = true;
        info.device_count = m_devices.size();
    } catch (const std::exception& e) {
        registration_error  = e.what();
        registration_failed = true;
    } catch (...) {
        registration_error  = "unknown driver registration error";
        registration_failed = true;
    }

    info.load_time_ms = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start).count();

    if (registration_failed) {
        info.error_msg = registration_error;
        unload();
    }
    record_load_info(std::move(info));
    if (registration_failed) {
        MC_THROW(mc::bad_function_call_exception, "Failed to register driver: ${error}", ("error", registration_error));
    }

    dlog("Driver ${name} loaded successfully from ${file}", ("name", m_driver_name)("file", library_file));
#endif
}

void device_driver::unload()
{
#ifndef __ZEPHYR__
    // 调用者保证同一实例串行访问。先清空成员,使后续串行 unload 不会再次关闭句柄;
    // 普通指针的读取和赋值并非原子交换,不能据此保证并发 load/unload 安全。
    void* handle    = m_driver_handle;
    m_driver_handle = nullptr;

    if (handle == nullptr) {
        ilog("Driver ${name} not loaded", ("name", m_driver_name));
        return;
    }

    // 必须在 dlclose 前清空设备列表。device_driver_t 的 device_name
    // 和函数指针(ctor/init/start/stop)指向 .so 内部地址,
    // dlclose 后这些指针立即成为悬空指针,后续 m_devices 析构会触发非法访问。
    m_devices.clear();

    dlclose(handle);
#endif
}

void device_driver::register_driver()
{
#ifdef __ZEPHYR__
    // 不可达:Zephyr 上 load_file 恒抛异常,不会持有 m_driver_handle。
    MC_THROW(mc::bad_function_call_exception,
             "register_driver via dlsym is not supported on Zephyr for driver: ${name}", ("name", m_driver_name));
#else
    register_device_driver_func func =
        reinterpret_cast<register_device_driver_func>(dlsym(m_driver_handle, "register_device_driver"));
    MC_ASSERT_THROW(func, mc::bad_function_call_exception, "Failed to get export_device_driver function: ${error}",
                    ("error", dlerror()));

    device_driver_t* devices       = nullptr;
    uint8_t          devices_count = 0;
    status_t         ret           = func(&devices, &devices_count);
    MC_ASSERT_THROW(ret == STATUS_OK, mc::bad_function_call_exception, "Failed to get device driver manager: ${error}",
                    ("error", ret));

    m_devices.clear();
    m_devices.reserve(devices_count);
    for (uint8_t i = 0; i < devices_count; i++) {
        m_devices.emplace_back(std::make_shared<device_driver_t>(devices[i]));
    }
#endif
}

device_driver_t& device_driver::get_device(std::string& device_name)
{
    MC_ASSERT_THROW(!m_devices.empty(), mc::invalid_arg_exception, "Device driver not loaded: ${name}",
                    ("name", m_driver_name));

    for (auto& device : m_devices) {
        if (device->device_name == device_name) {
            return *device;
        }
    }

    MC_THROW(mc::invalid_arg_exception, "Device driver not found: ${name}", ("name", device_name));
}

const device_drivers& device_driver::get_devices() const
{
    return m_devices;
}

void device_driver::set_device_driver(device_drivers* device_drivers)
{
    m_devices = *device_drivers;
}

std::string_view device_driver::get_name()
{
    return m_driver_name;
}

std::vector<driver_load_info> device_driver::get_load_info_snapshot()
{
    std::lock_guard<std::mutex> lock(s_load_info_mutex);
    return s_load_info_table;
}

void device_driver::record_load_info(driver_load_info&& info)
{
    // 同名 driver_name 只保留最新一条记录
    std::lock_guard<std::mutex> lock(s_load_info_mutex);
    for (auto& existing : s_load_info_table) {
        if (existing.driver_name == info.driver_name) {
            existing = std::move(info);
            return;
        }
    }
    s_load_info_table.push_back(std::move(info));
}

void device_driver::remove_load_info(const std::string& driver_name)
{
    std::lock_guard<std::mutex> lock(s_load_info_mutex);
    s_load_info_table.erase(std::remove_if(s_load_info_table.begin(), s_load_info_table.end(),
                                           [&driver_name](const driver_load_info& info) {
                                               return info.driver_name == driver_name;
                                           }),
                            s_load_info_table.end());
}

device_driver_factory& device_driver_factory::get_instance()
{
    static device_driver_factory instance;
    return instance;
}

void device_driver_factory::set_driver_path(const std::string& driver_path)
{
    std::lock_guard<std::mutex> lock(m_mutex);
    m_driver_path = driver_path;
}

void device_driver_factory::set_flash_driver_paths(const std::string& gold_path, const std::string& temp_path)
{
    std::lock_guard<std::mutex> lock(m_mutex);
    m_gold_driver_path     = gold_path;
    m_temp_driver_path     = temp_path;
    m_flash_paths_explicit = !gold_path.empty() || !temp_path.empty();
}

std::string device_driver_factory::make_driver_cache_key(std::string_view driver_name, driver_abi_version version) const
{
    return std::string(driver_name) + "@" + std::string(to_string(version));
}

void device_driver_factory::get_driver_path(std::string& driver_path) const
{
    std::lock_guard<std::mutex> lock(m_mutex);
    driver_path = m_driver_path;
}

bool device_driver_factory::is_production_driver_path(const std::string& driver_path) const
{
    std::string normalized = driver_path;
    while (!normalized.empty() && normalized.back() == '/') {
        normalized.pop_back();
    }
    return normalized == "/opt/bmc/drivers";
}

void device_driver_factory::get_flash_driver_paths(std::string& gold_path, std::string& temp_path) const
{
    std::lock_guard<std::mutex> lock(m_mutex);
    if (m_flash_paths_explicit) {
        gold_path = m_gold_driver_path;
        temp_path = m_temp_driver_path;
    } else if (is_production_driver_path(m_driver_path)) {
        gold_path = driver_source::k_default_gold_directory;
        temp_path = driver_source::k_default_temp_directory;
    } else {
        gold_path.clear();
        temp_path.clear();
    }
}

device_driver_factory::driver_ptr device_driver_factory::find_driver(const std::string& cache_key) const
{
    std::lock_guard<std::mutex> lock(m_mutex);
    auto                        it = m_drivers.find(cache_key);
    return it != m_drivers.end() ? it->second : nullptr;
}

bool device_driver_factory::remove_driver_by_key(const std::string& cache_key)
{
    std::lock_guard<std::mutex> lock(m_mutex);
    auto                        it = m_drivers.find(cache_key);
    if (it != m_drivers.end()) {
        m_drivers.erase(it);
        return true;
    }
    return false;
}

void device_driver_factory::set_or_create_driver(const std::string& cache_key, const std::string& driver_name,
                                                 device_drivers* device_drivers)
{
    std::lock_guard<std::mutex> lock(m_mutex);
    auto                        it = m_drivers.find(cache_key);
    if (it != m_drivers.end()) {
        it->second->set_device_driver(device_drivers);
        return;
    }
    driver_ptr driver = std::make_shared<device_driver>(driver_name);
    driver->set_device_driver(device_drivers);
    m_drivers.emplace(cache_key, driver);
}

std::vector<std::string> device_driver_factory::build_driver_candidates(std::string_view   driver_name,
                                                                        driver_abi_version version) const
{
    const std::string library_name(driver_name);
    std::string       driver_path;
    get_driver_path(driver_path);

    const std::string path_separator = (!driver_path.empty() && driver_path.back() == '/') ? "" : "/";
    const std::string base_path      = driver_path + path_separator + "lib";
    if (version == driver_abi_version::abi_v1) {
        return {
            base_path + "Csr1" + library_name + ".so",
            base_path + library_name + ".so",
        };
    }
    return {base_path + library_name + ".so"};
}

std::shared_ptr<device_driver> device_driver_factory::get_device_driver(const std::string& driver_name)
{
    try {
        return get_device_driver(driver_name, driver_abi_version::abi_v2);
    } catch (const std::exception& e) {
        MC_THROW(mc::invalid_arg_exception, "${error}", ("error", e.what()));
    }
}

std::shared_ptr<device_driver> device_driver_factory::get_device_driver(const std::string& driver_name,
                                                                        driver_abi_version abi_version)
{
    const std::string cache_key = make_driver_cache_key(driver_name, abi_version);
    auto              cached    = find_driver(cache_key);
    if (cached) {
        return cached;
    }

    driver_ptr         selected_driver;
    std::exception_ptr selected_error;
    driver_load_meta   no_candidate_meta;
    const auto         selection_result = try_load_selected_driver(cache_key, driver_name, abi_version, selected_driver,
                                                                   selected_error, no_candidate_meta);
    if (selection_result == selected_driver_load_result::loaded) {
        return selected_driver;
    }
    if (selection_result == selected_driver_load_result::failed && selected_error) {
        std::rethrow_exception(selected_error);
    }

    std::exception_ptr last_error;
    for (const auto& candidate : build_driver_candidates(driver_name, abi_version)) {
        try {
            driver_ptr driver = std::make_shared<device_driver>(driver_name);
            driver->load_file(candidate, no_candidate_meta);
            driver_ptr cached_driver;
            bool       inserted = false;
            {
                std::lock_guard<std::mutex> lock(m_mutex);
                auto                        insert_result = m_drivers.emplace(cache_key, driver);
                cached_driver                             = insert_result.first->second;
                inserted                                  = insert_result.second;
            }
            if (!inserted) {
                driver->unload();
                ilog("Reused cached driver ${name}@${abi} after concurrent load",
                     ("name", driver_name)("abi", to_string(abi_version)));
                return cached_driver;
            }
            // .so 已 dlopen,反射元数据随之注册。此时补一次全量权限/属性标志绑定,
            // 覆盖按需加载、晚于 hwproxy on_start 注册的驱动(幂等,见 apply_model_privileges)。
            apply_model_privileges();
            ilog("Loaded driver ${name}@${abi} from ${file}",
                 ("name", driver_name)("abi", to_string(abi_version))("file", candidate));
            return cached_driver;
        } catch (...) {
            wlog("unexpected exception loading driver file ${file}", ("file", candidate));
            last_error = std::current_exception();
        }
    }

    if (last_error) {
        std::rethrow_exception(last_error);
    }
    MC_THROW(mc::invalid_arg_exception, "Driver not found: ${name}@${abi}",
             ("name", driver_name)("abi", to_string(abi_version)));
}

device_driver_factory::selected_driver_load_result
device_driver_factory::try_load_selected_driver(const std::string& cache_key, const std::string& driver_name,
                                                driver_abi_version abi_version, driver_ptr& out_driver,
                                                std::exception_ptr& last_error, driver_load_meta& no_candidate_meta)
{
    std::string image_dir;
    std::string gold_dir;
    std::string temp_dir;
    get_driver_path(image_dir);
    get_flash_driver_paths(gold_dir, temp_dir);

    std::vector<std::string> names;
    if (abi_version == driver_abi_version::abi_v1) {
        names.push_back("Csr1" + driver_name);
    }
    names.push_back(driver_name);

    auto load_selection = [this, &cache_key, &driver_name, &out_driver,
                           &last_error](const driver_source::selection& selection) {
        if (!selection.file.has_value()) {
            return false;
        }

        const auto& selected = *selection.file;
        try {
            driver_ptr       driver = std::make_shared<device_driver>(driver_name);
            driver_load_meta meta{driver_source::location_name(selected.source), selected.directory,
                                  selected.source == driver_source::location::image ? "na" : "pass",
                                  selection.gold_checksum, selection.temp_checksum};
            driver->load_file(selected.path, meta);

            driver_ptr cached_driver;
            bool       inserted = false;
            {
                std::lock_guard<std::mutex> lock(m_mutex);
                auto                        result = m_drivers.emplace(cache_key, driver);
                cached_driver                      = result.first->second;
                inserted                           = result.second;
            }

            out_driver = cached_driver;
            if (!inserted) {
                driver->unload();
            } else {
                apply_model_privileges();
            }
            return true;
        } catch (const std::exception& e) {
            elog("Failed to load driver ${name}: ${error}", ("name", driver_name)("error", e.what()));
            last_error = std::current_exception();
            return false;
        } catch (...) {
            elog("Failed to load driver ${name}: unknown error", ("name", driver_name));
            last_error = std::current_exception();
            return false;
        }
    };

    bool attempted = false;
    for (const auto& name : names) {
        const auto primary = driver_source::select_driver_file(name, image_dir, gold_dir, temp_dir);
        if (!primary.file.has_value()) {
            no_candidate_meta = {"image", "", "na", primary.gold_checksum, primary.temp_checksum};
            continue;
        }
        attempted = true;

        if (load_selection(primary)) {
            return selected_driver_load_result::loaded;
        }

        const auto source = primary.file->source;
        if (source == driver_source::location::gold) {
            auto temp          = driver_source::select_driver_file(name, "", "", temp_dir);
            temp.gold_checksum = primary.gold_checksum;
            if (temp.file.has_value()) {
                attempted = true;
            }
            if (load_selection(temp)) {
                return selected_driver_load_result::loaded;
            }

            auto image          = driver_source::select_driver_file(name, image_dir, "", "");
            image.gold_checksum = primary.gold_checksum;
            image.temp_checksum = temp.temp_checksum;
            if (image.file.has_value()) {
                attempted = true;
            }
            if (load_selection(image)) {
                return selected_driver_load_result::loaded;
            }
        } else if (source == driver_source::location::temp) {
            auto image          = driver_source::select_driver_file(name, image_dir, "", "");
            image.gold_checksum = primary.gold_checksum;
            image.temp_checksum = primary.temp_checksum;
            if (image.file.has_value()) {
                attempted = true;
            }
            if (load_selection(image)) {
                return selected_driver_load_result::loaded;
            }
        }
    }
    return attempted ? selected_driver_load_result::failed : selected_driver_load_result::no_candidate;
}

void device_driver_factory::set_device_driver(const std::string& driver_name, device_drivers* device_drivers)
{
    set_or_create_driver(driver_name, driver_name, device_drivers);
    set_or_create_driver(make_driver_cache_key(driver_name, driver_abi_version::abi_v1), driver_name, device_drivers);
    set_or_create_driver(make_driver_cache_key(driver_name, driver_abi_version::abi_v2), driver_name, device_drivers);
}

void device_driver_factory::set_device_driver(const std::string& driver_name, driver_abi_version abi_version,
                                              device_drivers* device_drivers)
{
    set_or_create_driver(make_driver_cache_key(driver_name, abi_version), driver_name, device_drivers);
}

void device_driver_factory::remove_device_driver(const std::string& driver_name)
{
    // 清理 s_load_info_table 中该驱动的所有加载记录,防止长期运行累积
    device_driver::remove_load_info("lib" + driver_name + ".so");
    device_driver::remove_load_info("libCsr1" + driver_name + ".so");
    remove_driver_by_key(driver_name);
    remove_driver_by_key(make_driver_cache_key(driver_name, driver_abi_version::abi_v1));
    remove_driver_by_key(make_driver_cache_key(driver_name, driver_abi_version::abi_v2));
}

void device_driver_factory::remove_device_driver(const std::string& driver_name, driver_abi_version abi_version)
{
    remove_driver_by_key(make_driver_cache_key(driver_name, abi_version));
}

} // namespace devmon