/*********************************************************************
 *
 * Kiểm đường nạp plugin thật: YAML -> library_path -> Boost.DLL -> schema.
 *
 * Test này cần PNKX_NAV_CORE_CONFIG_DIR trỏ vào test/config của gói:
 *
 *   PNKX_NAV_CORE_CONFIG_DIR=src/AMR_T800/Test/mission_adapters/test/config \
 *     ./devel/lib/mission_adapters/plugin_registry_test
 *
 *********************************************************************/
#include <gtest/gtest.h>

#include <cstdlib>
#include <memory>
#include <string>
#include <vector>

#include <robot/robot.h>

#include <mission_adapters/mission_adapters.h>
#include <mission_adapters/plugin_registry.h>

#include "mission_test_utils.h"

namespace
{
using namespace mission_adapters;

/**
 * @brief Nguồn mission thứ ba, viết hoàn toàn trong test.
 *
 * Sự tồn tại của nó là bài kiểm tra thật cho tính plugin: thêm một loại nguồn mới mà không sửa file
 * nào trong `src/` của gói.
 */
class DummySourceAdapter : public MissionSourceAdapter
{
public:
    static constexpr const char* kSchema = "test.dummy";

    bool configure(const std::string& name, robot::NodeHandle& nh) override
    {
        (void)nh;
        name_ = name;
        return true;
    }

    std::string schema() const override { return kSchema; }

    bool validate(const MissionRequest& request, std::string& reason) const override
    {
        if (request.raw_payload.empty())
        {
            reason = "raw_payload is empty";
            return false;
        }
        return true;
    }

    ConversionResult convert(const MissionRequest& request) override
    {
        (void)request;
        auto mission = std::make_shared<Mission>();
        mission->type = MissionType::SIMPLE_GOAL;

        ConversionResult result;
        result.missions.push_back(mission);
        return result;
    }

private:
    std::string name_;
};

class PluginRegistryTest : public ::testing::Test
{
protected:
    robot::NodeHandle nh;
};

// ── Đường nạp bình thường ────────────────────────────────────────────────────────────────────────

TEST_F(PluginRegistryTest, LoadsBothSourcesFromConfig)
{
    PluginRegistry registry;

    ASSERT_TRUE(registry.loadFromConfig(nh))
        << "could not load the plugin — check PNKX_NAV_CORE_CONFIG_DIR and devel/lib";

    EXPECT_EQ(registry.size(), 2u);
    EXPECT_NE(registry.find(schema::kPoseStamped), nullptr);
    EXPECT_NE(registry.find(schema::kVda5050Order), nullptr);
    EXPECT_EQ(registry.find("does.not.exist"), nullptr);
}

TEST_F(PluginRegistryTest, LoadedAdapterConvertsRealPayload)
{
    PluginRegistry registry;
    ASSERT_TRUE(registry.loadFromConfig(nh));

    auto* adapter = registry.find(schema::kPoseStamped);
    ASSERT_NE(adapter, nullptr);

    const auto request = MissionRequest::fromPose(mission_test::makeGoal(3.0, 4.0));

    std::string reason;
    ASSERT_TRUE(adapter->validate(request, reason)) << reason;

    const auto result = adapter->convert(request);
    ASSERT_EQ(result.missions.size(), 1u);
    EXPECT_DOUBLE_EQ(result.missions.front()->goal.pose.position.x, 3.0);
}

// ── Các cách hỏng, đều phải báo lỗi rõ ràng chứ không crash ──────────────────────────────────────

TEST_F(PluginRegistryTest, MissingLibraryPathKeyFailsCleanly)
{
    PluginRegistry registry;

    EXPECT_FALSE(registry.loadFromConfig(nh, "registry_test_missing_library_path"));
    EXPECT_EQ(registry.size(), 0u);
}

TEST_F(PluginRegistryTest, MissingLibraryFileFailsCleanly)
{
    PluginRegistry registry;

    EXPECT_FALSE(registry.loadFromConfig(nh, "registry_test_missing_library_file"));
    EXPECT_EQ(registry.size(), 0u);
}

TEST_F(PluginRegistryTest, WrongSymbolNameFailsCleanly)
{
    PluginRegistry registry;

    // Thư viện có thật, symbol thì không: import_alias ném system_error, registry phải nuốt và báo.
    EXPECT_FALSE(registry.loadFromConfig(nh, "registry_test_wrong_symbol"));
    EXPECT_EQ(registry.size(), 0u);
}

TEST_F(PluginRegistryTest, DuplicateSchemaIsRejected)
{
    PluginRegistry registry;

    // Hai instance cùng khai schema "geometry.pose_stamped": cái thứ hai bị từ chối thay vì ghi đè
    // im lặng, vì định tuyến khi đó sẽ phụ thuộc thứ tự nạp.
    EXPECT_FALSE(registry.loadFromConfig(nh, "registry_test_duplicate_schema"));
    EXPECT_EQ(registry.size(), 1u);
}

TEST_F(PluginRegistryTest, EntryWithoutTypeFailsCleanly)
{
    PluginRegistry registry;

    EXPECT_FALSE(registry.loadFromConfig(nh, "registry_test_entry_without_type"));
    EXPECT_EQ(registry.size(), 0u);
}

TEST_F(PluginRegistryTest, MissingSourceListFailsCleanly)
{
    PluginRegistry registry;

    EXPECT_FALSE(registry.loadFromConfig(nh, "registry_test_empty"));
    EXPECT_EQ(registry.size(), 0u);
}

// ── Đăng ký trực tiếp (nguồn biên dịch thẳng vào host, hoặc test) ───────────────────────────────

TEST_F(PluginRegistryTest, RejectsNullAndDuplicateManualRegistration)
{
    PluginRegistry registry;

    EXPECT_FALSE(registry.registerAdapter(nullptr));

    auto first = std::make_shared<DummySourceAdapter>();
    EXPECT_TRUE(registry.registerAdapter(first));

    auto second = std::make_shared<DummySourceAdapter>();
    EXPECT_FALSE(registry.registerAdapter(second)) << "two adapters with the same schema were both "
                                                      "accepted";

    EXPECT_EQ(registry.size(), 1u);
}

/**
 * Nguồn thứ ba đi hết đường: đăng ký -> EventProcessor định tuyến theo schema -> mission vào hàng
 * đợi. Không có dòng nào trong `src/` của gói biết tới DummySourceAdapter.
 */
TEST_F(PluginRegistryTest, ThirdPartyAdapterFlowsThroughCoreUnchanged)
{
    PluginRegistry registry;
    ASSERT_TRUE(registry.registerAdapter(std::make_shared<DummySourceAdapter>()));

    MissionManager manager;
    EventProcessor processor(manager, registry);
    processor.start();

    processor.submitRequest(
        MissionRequest::fromRaw(DummySourceAdapter::kSchema, "{\"job\":\"pick\"}"));

    EXPECT_TRUE(mission_test::waitForState(manager, MissionState::QUEUED,
                                           std::chrono::milliseconds(500)));

    // Payload không hợp lệ -> validate() chặn -> hàng đợi không đổi (A1).
    processor.submitRequest(MissionRequest::fromRaw(DummySourceAdapter::kSchema, ""));
    std::this_thread::sleep_for(std::chrono::milliseconds(80));

    EXPECT_EQ(manager.state(), MissionState::QUEUED);
    EXPECT_TRUE(manager.hasMission());

    processor.stop();
}

/**
 * @brief Plugin hỏng: sinh mission không goal và cũng không action.
 *
 * Core phải chặn, vì chặng như vậy không có việc gì để làm và sẽ không bao giờ báo kết quả về —
 * mission layer kẹt RUNNING vĩnh viễn.
 */
class EmptyMissionAdapter : public MissionSourceAdapter
{
public:
    static constexpr const char* kSchema = "test.empty_mission";

    bool configure(const std::string&, robot::NodeHandle&) override { return true; }
    std::string schema() const override { return kSchema; }
    bool validate(const MissionRequest&, std::string&) const override { return true; }

    ConversionResult convert(const MissionRequest&) override
    {
        auto mission = std::make_shared<Mission>();
        mission->has_goal = false;   // và actions rỗng

        ConversionResult result;
        result.missions.push_back(mission);
        return result;
    }
};

TEST_F(PluginRegistryTest, MissionWithoutGoalAndWithoutActionIsRejected)
{
    PluginRegistry registry;
    ASSERT_TRUE(registry.registerAdapter(std::make_shared<EmptyMissionAdapter>()));

    MissionManager manager;
    EventProcessor processor(manager, registry);

    processor.start();
    processor.submitRequest(MissionRequest::fromRaw(EmptyMissionAdapter::kSchema, "payload"));
    std::this_thread::sleep_for(std::chrono::milliseconds(80));
    processor.stop();

    EXPECT_EQ(manager.state(), MissionState::IDLE);
    EXPECT_FALSE(manager.hasMission());
}

TEST_F(PluginRegistryTest, UnknownSchemaIsIgnoredNotCrashing)
{
    PluginRegistry registry;
    MissionManager manager;
    EventProcessor processor(manager, registry);

    processor.start();
    processor.submitRequest(MissionRequest::fromRaw("nobody.handles.this", "payload"));
    std::this_thread::sleep_for(std::chrono::milliseconds(80));
    processor.stop();

    EXPECT_EQ(manager.state(), MissionState::IDLE);
    EXPECT_FALSE(manager.hasMission());
}

}  // namespace

int main(int argc, char** argv)
{
    // Test tự trỏ vào cây config và thư mục .so của chính nó, để chạy được cả qua ctest lẫn khi gọi
    // thẳng binary (đúng cách test_costmap đang làm). overwrite = 0 nên biến môi trường do người
    // chạy đặt vẫn thắng.
#ifdef MISSION_ADAPTERS_TEST_CONFIG_DIR
    setenv("PNKX_NAV_CORE_CONFIG_DIR", MISSION_ADAPTERS_TEST_CONFIG_DIR, 0);
#endif
#ifdef MISSION_ADAPTERS_TEST_LIBRARY_DIR
    setenv("PNKX_NAV_CORE_LIBRARY_PATH", MISSION_ADAPTERS_TEST_LIBRARY_DIR, 0);
#endif

    testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}
