#include <mission_adapters/plugin_registry.h>

#include <utility>

#include <boost/dll/import.hpp>
#include <boost/system/system_error.hpp>
#include <yaml-cpp/yaml.h>

#include <robot/robot.h>

namespace mission_adapters
{
    PluginRegistry::~PluginRegistry()
    {
        clear();
    }

    void PluginRegistry::clear()
    {
        // Adapter phải chết trước factory: factory là thứ giữ .so còn nạp, thả ngược thứ tự sẽ gỡ
        // thư viện trong khi vẫn còn object của nó.
        adapters_.clear();
        factories_.clear();
    }

    bool PluginRegistry::registerAdapter(const MissionSourceAdapter::Ptr& adapter)
    {
        if (!adapter)
        {
            robot::log_error("PluginRegistry: adapter null");
            return false;
        }

        const std::string schema_name = adapter->schema();
        if (schema_name.empty())
        {
            robot::log_error("PluginRegistry: adapter declares an empty schema");
            return false;
        }

        const auto existing = adapters_.find(schema_name);
        if (existing != adapters_.end())
        {
            // Hai nguồn cùng schema thì việc định tuyến trở nên phụ thuộc thứ tự nạp — từ chối thay
            // vì im lặng ghi đè.
            robot::log_error("PluginRegistry: schema '%s' is already registered by another adapter",
                             schema_name.c_str());
            return false;
        }

        adapters_.emplace(schema_name, adapter);
        return true;
    }

    MissionSourceAdapter* PluginRegistry::find(const std::string& schema) const
    {
        const auto it = adapters_.find(schema);
        return it == adapters_.end() ? nullptr : it->second.get();
    }

    std::vector<std::string> PluginRegistry::schemas() const
    {
        std::vector<std::string> result;
        result.reserve(adapters_.size());
        for (const auto& entry : adapters_)
            result.push_back(entry.first);

        return result;
    }

    bool PluginRegistry::loadOne(const std::string& name, const std::string& type,
                                 robot::NodeHandle& nh)
    {
        robot::PluginLoaderHelper loader(nh);
        const std::string library_path = loader.findLibraryPath(type);

        if (library_path.empty())
        {
            robot::log_error("PluginRegistry: no library found for '%s' — check the key "
                             "'%s/library_path' in the YAML and that the .so file exists",
                             type.c_str(), type.c_str());
            return false;
        }

        std::function<MissionSourceAdapter::Ptr()> factory;

        try
        {
            factory = boost::dll::import_alias<MissionSourceAdapter::Ptr()>(
                library_path, type, boost::dll::load_mode::append_decorations);
        }
        catch (const boost::system::system_error& ex)
        {
            // Sai tên symbol hoặc file không nạp được. Bắt tại đây để một plugin hỏng không giết cả
            // tiến trình — nhưng vẫn báo lỗi để không ai tưởng nguồn này đang chạy.
            robot::log_error("PluginRegistry: could not load symbol '%s' from '%s': %s",
                             type.c_str(), library_path.c_str(), ex.what());
            return false;
        }
        catch (const std::exception& ex)
        {
            robot::log_error("PluginRegistry: error while loading '%s': %s",
                             type.c_str(), ex.what());
            return false;
        }

        MissionSourceAdapter::Ptr adapter;
        try
        {
            adapter = factory();
        }
        catch (const std::exception& ex)
        {
            robot::log_error("PluginRegistry: factory of '%s' threw an exception: %s",
                             type.c_str(), ex.what());
            return false;
        }

        if (!adapter)
        {
            robot::log_error("PluginRegistry: factory of '%s' returned null", type.c_str());
            return false;
        }

        if (!adapter->configure(name, nh))
        {
            robot::log_error("PluginRegistry: '%s' (instance '%s') configure() failed",
                             type.c_str(), name.c_str());
            return false;
        }

        if (!registerAdapter(adapter))
            return false;

        // Chỉ giữ factory sau khi adapter đã vào bảng: nếu đăng ký hỏng thì cũng không giữ .so lại.
        factories_.push_back(std::move(factory));

        robot::log_info("PluginRegistry: loaded '%s' (instance '%s') for schema '%s'",
                        type.c_str(), name.c_str(), adapter->schema().c_str());
        return true;
    }

    bool PluginRegistry::loadFromConfig(robot::NodeHandle& nh, const std::string& ns)
    {
        const std::string key = ns.empty() ? std::string("mission_sources")
                                           : ns + "/mission_sources";

        YAML::Node sources;
        if (!nh.getParam(key, sources) || !sources.IsSequence() || sources.size() == 0)
        {
            robot::log_error("PluginRegistry: '%s' is missing or is not a list — no mission source "
                             "was loaded", key.c_str());
            return false;
        }

        bool all_ok = true;

        for (size_t i = 0; i < sources.size(); ++i)
        {
            const YAML::Node& entry = sources[i];

            if (!entry.IsMap() || !entry["type"])
            {
                robot::log_error("PluginRegistry: '%s[%zu]' is missing the 'type' key",
                                 key.c_str(), i);
                all_ok = false;
                continue;
            }

            const std::string type = entry["type"].as<std::string>();
            const std::string name = entry["name"] ? entry["name"].as<std::string>() : type;

            if (!loadOne(name, type, nh))
                all_ok = false;
        }

        return all_ok;
    }

} // namespace mission_adapters
