#include <gtest/gtest.h>

#include <cstdlib>

#include <cmath>
#include <string>

#include <mission_adapters/mission_request.h>

#include "goal_source_adapter.h"
#include "mission_test_utils.h"
#include "vda5050_source_adapter.h"

namespace
{
using namespace mission_adapters;
using mission_plugins::GoalSourceAdapter;
using mission_plugins::VDA5050SourceAdapter;
using mission_test::makeAction;
using mission_test::makeGoal;
using mission_test::makeOrder;

class AdapterTest : public ::testing::Test
{
protected:
    void SetUp() override
    {
        robot::NodeHandle nh;
        ASSERT_TRUE(goal_adapter.configure("goal_src", nh));
        ASSERT_TRUE(order_adapter.configure("vda5050_src", nh));
    }

    /// Chuyển một order, trả về danh sách mission (bỏ phần mode).
    std::vector<std::shared_ptr<Mission>> convertOrder(const robot_protocol_msgs::Order& order)
    {
        return order_adapter.convert(MissionRequest::fromOrder(order)).missions;
    }

    /// Chuyển một order và giữ nguyên cả ConversionResult để kiểm mode.
    ConversionResult convertOrderFull(const robot_protocol_msgs::Order& order)
    {
        return order_adapter.convert(MissionRequest::fromOrder(order));
    }

    GoalSourceAdapter goal_adapter;
    VDA5050SourceAdapter order_adapter;
};

// ── Schema ──────────────────────────────────────────────────────────────────────────────────────

TEST_F(AdapterTest, AdaptersDeclareDistinctSchemas)
{
    EXPECT_EQ(goal_adapter.schema(), schema::kPoseStamped);
    EXPECT_EQ(order_adapter.schema(), schema::kVda5050Order);
    EXPECT_NE(goal_adapter.schema(), order_adapter.schema());
}

// ── GoalSourceAdapter ───────────────────────────────────────────────────────────────────────────

TEST_F(AdapterTest, GoalAdapterCreatesSingleMission)
{
    const auto missions = goal_adapter.convert(MissionRequest::fromPose(makeGoal(5.5, 9.1))).missions;

    ASSERT_EQ(missions.size(), 1u);
    EXPECT_EQ(missions.front()->type, MissionType::SIMPLE_GOAL);
    EXPECT_EQ(missions.front()->motion_hint, "position");
    EXPECT_DOUBLE_EQ(missions.front()->goal.pose.position.x, 5.5);
    EXPECT_DOUBLE_EQ(missions.front()->goal.pose.position.y, 9.1);
}

TEST_F(AdapterTest, GoalAdapterRejectsMissingPayload)
{
    MissionRequest request;
    request.schema = schema::kPoseStamped;

    std::string reason;
    EXPECT_FALSE(goal_adapter.validate(request, reason));
    EXPECT_FALSE(reason.empty());
}

TEST_F(AdapterTest, GoalAdapterRejectsNonFiniteGoal)
{
    auto goal = makeGoal(1.0, 1.0);
    goal.pose.position.x = std::numeric_limits<double>::quiet_NaN();
    goal.pose.orientation.w = 1.0;

    std::string reason;
    EXPECT_FALSE(goal_adapter.validate(MissionRequest::fromPose(goal), reason))
        << "a goal containing NaN slipped through the boundary into navigation";
}

TEST_F(AdapterTest, GoalAdapterRejectsZeroQuaternion)
{
    auto goal = makeGoal(1.0, 1.0);

    std::string reason;
    ASSERT_TRUE(goal_adapter.validate(MissionRequest::fromPose(goal), reason)) << reason;

    // Host quên set orientation: quaternion toàn 0 không phải "hướng bất kỳ", nó là dữ liệu hỏng.
    goal.pose.orientation.w = 0.0;
    EXPECT_FALSE(goal_adapter.validate(MissionRequest::fromPose(goal), reason));
}

// ── VDA5050SourceAdapter ────────────────────────────────────────────────────────────────────────

TEST_F(AdapterTest, EmptyOrderIsRejectedByValidate)
{
    robot_protocol_msgs::Order order;

    std::string reason;
    EXPECT_FALSE(order_adapter.validate(MissionRequest::fromOrder(order), reason));
    EXPECT_TRUE(convertOrder(order).empty());
}

TEST_F(AdapterTest, OrderWithoutActionsCreatesOneTailMission)
{
    const auto missions = convertOrder(makeOrder(4));

    ASSERT_EQ(missions.size(), 1u);
    EXPECT_EQ(missions.front()->nodes.size(), 4u);
    EXPECT_EQ(missions.front()->edges.size(), 3u);
}

TEST_F(AdapterTest, InvalidOrderWithMissingEdgesIsRejected)
{
    auto order = makeOrder(4);
    order.edges.pop_back();

    std::string reason;
    EXPECT_FALSE(order_adapter.validate(MissionRequest::fromOrder(order), reason));
    EXPECT_TRUE(convertOrder(order).empty());
}

TEST_F(AdapterTest, OrderSplitsAtNodeAction)
{
    auto order = makeOrder(5);
    order.nodes[2].actions.push_back(makeAction("dock"));

    const auto missions = convertOrder(order);

    ASSERT_EQ(missions.size(), 2u);
    EXPECT_EQ(missions[0]->nodes.size(), 3u);
    EXPECT_EQ(missions[1]->nodes.size(), 3u);
}

TEST_F(AdapterTest, OrderCollectsAndSortsActions)
{
    auto order = makeOrder(2);
    order.edges[0].actions.push_back(makeAction("edge"));
    order.nodes[1].actions.push_back(makeAction("node"));

    const auto missions = convertOrder(order);

    ASSERT_EQ(missions.size(), 1u);
    ASSERT_EQ(missions.front()->actions.size(), 2u);
    EXPECT_EQ(missions.front()->actions[0].type, ActionType::EDGE_ACTION);
    EXPECT_EQ(missions.front()->actions[1].type, ActionType::NODE_ACTION);
}

// ── A4: conformance VDA5050 ─────────────────────────────────────────────────────────────────────

TEST_F(AdapterTest, MissionCarriesGoalAndStartFromNodes)
{
    auto order = makeOrder(3);
    order.nodes[2].nodePosition.x = 7.0;
    order.nodes[2].nodePosition.y = 8.0;
    order.nodes[2].nodePosition.theta = 0.0;

    const auto missions = convertOrder(order);

    ASSERT_EQ(missions.size(), 1u);
    // Mission phải self-contained: consumer không phải tự đoán goal từ nodes.back().
    EXPECT_DOUBLE_EQ(missions.front()->goal.pose.position.x, 7.0);
    EXPECT_DOUBLE_EQ(missions.front()->goal.pose.position.y, 8.0);
    EXPECT_DOUBLE_EQ(missions.front()->goal.pose.orientation.w, 1.0);
    EXPECT_EQ(missions.front()->goal.header.frame_id, "map");

    EXPECT_DOUBLE_EQ(missions.front()->start.pose.position.x, 0.0);
}

TEST_F(AdapterTest, NodeThetaBecomesQuaternion)
{
    auto order = makeOrder(2);
    order.nodes[1].nodePosition.theta = M_PI;   // [rad]

    const auto missions = convertOrder(order);

    ASSERT_EQ(missions.size(), 1u);
    // theta = pi -> quay 180 độ quanh z: (z, w) = (1, 0).
    EXPECT_NEAR(missions.front()->goal.pose.orientation.z, 1.0, 1e-9);
    EXPECT_NEAR(missions.front()->goal.pose.orientation.w, 0.0, 1e-9);
}

TEST_F(AdapterTest, HorizonNodesAreNeverDispatched)
{
    auto order = makeOrder(5);
    // Chỉ 3 node đầu là base; hai node cuối là horizon (fleet manager chưa cho phép đi).
    order.nodes[3].released = false;
    order.nodes[4].released = false;
    order.edges[3].released = false;

    const auto missions = convertOrder(order);

    ASSERT_EQ(missions.size(), 1u);
    EXPECT_EQ(missions.front()->nodes.size(), 3u) << "the robot was handed the horizon part that "
                                                     "is not released yet";
    EXPECT_DOUBLE_EQ(missions.front()->goal.pose.position.x, 2.0);
}

TEST_F(AdapterTest, OrderWithoutReleasedFlagIsTreatedAsFullBase)
{
    auto order = makeOrder(3);
    for (auto& node : order.nodes) node.released = false;
    for (auto& edge : order.edges) edge.released = false;

    // Host không điền `released`: chạy cả order còn hơn đứng im không dấu hiệu.
    const auto missions = convertOrder(order);

    ASSERT_EQ(missions.size(), 1u);
    EXPECT_EQ(missions.front()->nodes.size(), 3u);
}

TEST_F(AdapterTest, StaleOrderUpdateIdIsRejected)
{
    ASSERT_FALSE(convertOrder(makeOrder(3, "order_A", 2)).empty());

    std::string reason;

    // Cùng orderId, orderUpdateId cũ hơn -> phát lại/đến trễ, không được chạy lại tuyến đường.
    EXPECT_FALSE(order_adapter.validate(MissionRequest::fromOrder(makeOrder(3, "order_A", 1)),
                                        reason));
    EXPECT_FALSE(order_adapter.validate(MissionRequest::fromOrder(makeOrder(3, "order_A", 2)),
                                        reason));

    // Cùng orderId, update mới hơn -> chấp nhận.
    EXPECT_TRUE(order_adapter.validate(MissionRequest::fromOrder(makeOrder(4, "order_A", 3)),
                                       reason)) << reason;

    // orderId khác -> order mới, không so orderUpdateId.
    EXPECT_TRUE(order_adapter.validate(MissionRequest::fromOrder(makeOrder(3, "order_B", 0)),
                                       reason)) << reason;
}

TEST_F(AdapterTest, OrderUpdateAppendsOnlyNewlyReleasedSegment)
{
    // Base ban đầu: 3 node released, 2 node horizon.
    auto order = makeOrder(5, "order_A", 0);
    order.nodes[3].released = false;
    order.nodes[4].released = false;
    order.edges[3].released = false;

    const auto first = convertOrderFull(order);
    ASSERT_EQ(first.missions.size(), 1u);
    EXPECT_EQ(first.mode, SubmitMode::kReplace);
    EXPECT_EQ(first.missions.front()->nodes.size(), 3u);

    // Fleet manager release nốt horizon.
    auto update = makeOrder(5, "order_A", 1);
    const auto second = convertOrderFull(update);

    ASSERT_EQ(second.missions.size(), 1u);
    EXPECT_EQ(second.mode, SubmitMode::kAppend)
        << "an order update replaced the whole queue -> the robot cancels and redoes the leg it is "
           "on";

    // Chặng mới bắt đầu từ node cuối của phần đã chạy, không lặp lại phần cũ.
    EXPECT_EQ(second.missions.front()->nodes.size(), 3u);
    EXPECT_DOUBLE_EQ(second.missions.front()->start.pose.position.x, 2.0);
    EXPECT_DOUBLE_EQ(second.missions.front()->goal.pose.position.x, 4.0);
}

TEST_F(AdapterTest, OrderUpdateWithoutNewNodesProducesNoWork)
{
    ASSERT_FALSE(convertOrder(makeOrder(3, "order_A", 0)).empty());

    // Update không release thêm node nào: không có việc mới, và cũng không được xoá gì.
    const auto result = convertOrderFull(makeOrder(3, "order_A", 1));
    EXPECT_TRUE(result.empty());
}

TEST_F(AdapterTest, NewOrderIdReplacesQueue)
{
    ASSERT_FALSE(convertOrder(makeOrder(3, "order_A", 0)).empty());

    const auto result = convertOrderFull(makeOrder(2, "order_B", 0));

    ASSERT_FALSE(result.empty());
    EXPECT_EQ(result.mode, SubmitMode::kReplace);
}

// ── D8: goal optional, action đi nguyên vẹn ─────────────────────────────────────────────────────

TEST_F(AdapterTest, ActionAtFirstNodeBecomesGoallessMission)
{
    auto order = makeOrder(3);
    order.nodes[0].actions.push_back(makeAction("pick_at_start"));

    const auto missions = convertOrder(order);

    ASSERT_EQ(missions.size(), 2u);

    // Chặng đầu: action ngay tại chỗ đứng, không có quãng đường nào để đi.
    EXPECT_FALSE(missions[0]->has_goal);
    ASSERT_EQ(missions[0]->actions.size(), 1u);
    EXPECT_EQ(missions[0]->actions.front().action.actionId, "pick_at_start");

    // Chặng sau: di chuyển bình thường.
    EXPECT_TRUE(missions[1]->has_goal);
    EXPECT_DOUBLE_EQ(missions[1]->goal.pose.position.x, 2.0);
}

TEST_F(AdapterTest, MovingMissionsAlwaysHaveGoal)
{
    const auto missions = convertOrder(makeOrder(3));

    ASSERT_EQ(missions.size(), 1u);
    EXPECT_TRUE(missions.front()->has_goal);
}

TEST_F(AdapterTest, ActionsPassThroughUnchangedAndInOrder)
{
    auto order = makeOrder(3);
    order.edges[0].sequenceId = 1;
    order.edges[0].actions.push_back(makeAction("beep"));
    order.edges[1].sequenceId = 3;
    order.edges[1].actions.push_back(makeAction("horn"));
    order.nodes[2].sequenceId = 4;
    order.nodes[2].actions.push_back(makeAction("lift"));

    const auto missions = convertOrder(order);

    ASSERT_EQ(missions.size(), 1u);
    const auto& actions = missions.front()->actions;

    ASSERT_EQ(actions.size(), 3u);
    EXPECT_EQ(actions[0].action.actionId, "beep");
    EXPECT_EQ(actions[1].action.actionId, "horn");
    EXPECT_EQ(actions[2].action.actionId, "lift");

    // Mission layer không diễn giải actionType — nó chỉ chuyển tiếp.
    EXPECT_EQ(actions[2].action.actionType, "TEST");
    EXPECT_EQ(actions[2].type, ActionType::NODE_ACTION);
}

TEST_F(AdapterTest, NonFiniteNodePositionIsRejected)
{
    auto order = makeOrder(3);
    order.nodes[1].nodePosition.x = std::numeric_limits<double>::infinity();

    std::string reason;
    EXPECT_FALSE(order_adapter.validate(MissionRequest::fromOrder(order), reason));
}

}  // namespace

// ── Compound action: mở rộng thành chuỗi chặng ──────────────────────────────────────────────────

namespace
{
/// @brief Gắn một action có tham số vào node.
void addAction(robot_protocol_msgs::Node& node, const std::string& type,
               const std::string& param_key = "", const std::string& param_value = "")
{
    robot_protocol_msgs::Action action;
    action.actionType = type;
    action.actionId = type + "_id";

    if (!param_key.empty())
    {
        robot_protocol_msgs::ActionParameter p;
        p.key = param_key;
        p.value = param_value;
        action.actionParameters.push_back(p);
    }
    node.actions.push_back(action);
}
}  // namespace

TEST_F(AdapterTest, CompoundActionExpandsIntoASequenceOfLegs)
{
    auto order = makeOrder(2);
    addAction(order.nodes[1], "charge", "goal_frame", "charger_02_goal");

    const auto missions = convertOrder(order);

    // nav -> n1 | DetectCharger | docking | startCharging
    ASSERT_EQ(missions.size(), 4u);

    EXPECT_TRUE(missions[0]->has_goal);
    EXPECT_TRUE(missions[0]->actions.empty()) << "action compound phải được GỠ khỏi chặng nav";
    EXPECT_EQ(missions[0]->motion_hint, "position");

    ASSERT_EQ(missions[1]->actions.size(), 1u);
    EXPECT_EQ(missions[1]->actions[0].action.actionType, "DetectCharger");
    EXPECT_FALSE(missions[1]->has_goal);

    EXPECT_TRUE(missions[2]->has_goal);
    EXPECT_EQ(missions[2]->motion_hint, "docking");
    EXPECT_EQ(missions[2]->goal_frame, "charger_02_goal") << "frame phải lấy từ actionParameters";
    EXPECT_EQ(missions[2]->marker, "charger") << "marker phải chọn override planner độc lập TF";

    ASSERT_EQ(missions[3]->actions.size(), 1u);
    EXPECT_EQ(missions[3]->actions[0].action.actionType, "startCharging");
    EXPECT_FALSE(missions[3]->has_goal);
}

TEST_F(AdapterTest, GeneratedActionsCarryTheOriginalParameters)
{
    auto order = makeOrder(2);
    addAction(order.nodes[1], "charge", "goal_frame", "charger_02_goal");

    const auto missions = convertOrder(order);
    ASSERT_EQ(missions.size(), 4u);

    // Handler dò cần biết dò trạm nào; nó là chỗ duy nhất hiểu ý nghĩa các tham số đó.
    const auto& detect = missions[1]->actions[0].action;
    ASSERT_EQ(detect.actionParameters.size(), 1u);
    EXPECT_EQ(detect.actionParameters[0].key, "goal_frame");
    EXPECT_EQ(detect.actionParameters[0].value, "charger_02_goal");

    // actionId suy từ id gốc để truy vết được trong log.
    EXPECT_NE(detect.actionId.find("charge_id"), std::string::npos);
}

TEST_F(AdapterTest, PlainActionsKeepTheirJsonOrderAroundACompound)
{
    auto order = makeOrder(2);
    addAction(order.nodes[1], "MutedOn");
    addAction(order.nodes[1], "charge", "goal_frame", "charger_goal");
    addAction(order.nodes[1], "MutedOff");

    const auto missions = convertOrder(order);

    // nav+MutedOn | Detect | docking | startCharging | MutedOff
    ASSERT_EQ(missions.size(), 5u);

    ASSERT_EQ(missions[0]->actions.size(), 1u);
    EXPECT_EQ(missions[0]->actions[0].action.actionType, "MutedOn");

    // MutedOff phải nằm SAU chuỗi charge. Gom hết action thường vào chặng nav sẽ bật lại cảm biến
    // an toàn trước khi robot lùi vào trạm — đúng thứ MutedOn sinh ra để tránh.
    ASSERT_EQ(missions[4]->actions.size(), 1u);
    EXPECT_EQ(missions[4]->actions[0].action.actionType, "MutedOff");
    EXPECT_FALSE(missions[4]->has_goal);
}

TEST_F(AdapterTest, ActionOutsideTheTableRunsAsAPlainAction)
{
    auto order = makeOrder(2);
    addAction(order.nodes[1], "MutedOn");

    const auto missions = convertOrder(order);

    ASSERT_EQ(missions.size(), 1u) << "action thường không được mở rộng";
    ASSERT_EQ(missions[0]->actions.size(), 1u);
    EXPECT_EQ(missions[0]->actions[0].action.actionType, "MutedOn");
}

TEST_F(AdapterTest, RelativeMoveStepBecomesALegWithADistance)
{
    auto order = makeOrder(2);
    addAction(order.nodes[1], "UnDockFromStation");

    const auto missions = convertOrder(order);
    ASSERT_EQ(missions.size(), 2u);

    EXPECT_TRUE(missions[1]->has_goal);
    EXPECT_EQ(missions[1]->motion_hint, "go_straight");
    EXPECT_DOUBLE_EQ(missions[1]->relative_distance, -1.0);
    EXPECT_TRUE(missions[1]->goal_frame.empty());
}

TEST_F(AdapterTest, FixedFrameStepDoesNotNeedAnyParameter)
{
    auto order = makeOrder(2);
    addAction(order.nodes[1], "FixedDock");

    const auto missions = convertOrder(order);
    ASSERT_EQ(missions.size(), 2u);
    EXPECT_EQ(missions[1]->goal_frame, "dock_target");
    EXPECT_EQ(missions[1]->marker, "trolley");
}

TEST_F(AdapterTest, MissingStructuralParameterDropsTheWholeOrder)
{
    auto order = makeOrder(2);
    addAction(order.nodes[1], "charge");   // thiếu goal_frame

    // Bỏ CẢ order: một order chạy nửa vời — robot tới trạm sạc rồi không sạc — nguy hiểm hơn là
    // không chạy. Và hàng đợi đang chạy không bị đụng tới (A1).
    EXPECT_TRUE(convertOrder(order).empty());
}

TEST(CompoundConfig, RejectsAStepWithTwoKeywords)
{
    robot::NodeHandle nh;
    VDA5050SourceAdapter adapter;
    EXPECT_FALSE(adapter.configure("vda5050_bad_step", nh))
        << "không đúng một từ khoá thì không có cách diễn giải nào hiển nhiên đúng";
}

TEST(CompoundConfig, RejectsARecursiveCompound)
{
    robot::NodeHandle nh;
    VDA5050SourceAdapter adapter;
    EXPECT_FALSE(adapter.configure("vda5050_recursive", nh))
        << "expander sẽ mở rộng chính output của mình";
}

TEST(CompoundConfig, RejectsInvalidOrActionOnlyProfiles)
{
    robot::NodeHandle nh;
    VDA5050SourceAdapter adapter;
    EXPECT_FALSE(adapter.configure("vda5050_bad_profile", nh));
}

int main(int argc, char** argv)
{
    // Bảng `compound_actions` nằm trong cây config test. `overwrite = 0` để shell vẫn override được
    // khi cần chạy tay với cây khác — cùng cách `plugin_registry_test` làm.
#ifdef MISSION_ADAPTERS_TEST_CONFIG_DIR
    setenv("PNKX_NAV_CORE_CONFIG_DIR", MISSION_ADAPTERS_TEST_CONFIG_DIR, 0);
#endif

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