#include <gtest/gtest.h>

#include <chrono>
#include <thread>
#include <vector>

#include <mission_adapters/mission_manager.h>

#include "mission_test_utils.h"

namespace
{
using namespace mission_adapters;
using mission_test::makeMission;
using mission_test::makeMissions;

TEST(MissionManagerTest, ManagerRunsMissionLifecycle)
{
    MissionManager manager;

    manager.submit(std::vector<std::shared_ptr<Mission>>{makeMission(1.0, 2.0)});
    EXPECT_EQ(manager.state(), MissionState::QUEUED);
    EXPECT_TRUE(manager.hasMission());

    auto mission = manager.nextMission();
    ASSERT_NE(mission, nullptr);
    EXPECT_NE(mission->id, kInvalidMissionId) << "submit must return a MissionId";
    EXPECT_EQ(manager.state(), MissionState::RUNNING);

    EXPECT_TRUE(manager.onNavigationDone(mission->id));
    EXPECT_EQ(manager.state(), MissionState::COMPLETED);
    EXPECT_FALSE(manager.hasMission());
}

TEST(MissionManagerTest, ManagerHandlesPauseResumeCancelAndFailure)
{
    MissionManager manager;

    manager.submit(std::vector<std::shared_ptr<Mission>>{makeMission(1.0, 1.0)});
    manager.pause();
    EXPECT_EQ(manager.state(), MissionState::PAUSED);

    manager.resume();
    EXPECT_EQ(manager.state(), MissionState::QUEUED);

    manager.cancel();
    EXPECT_EQ(manager.state(), MissionState::CANCELLED);
    EXPECT_FALSE(manager.hasMission());

    manager.submit(std::vector<std::shared_ptr<Mission>>{makeMission(2.0, 2.0)});
    const auto mission = manager.nextMission();
    ASSERT_NE(mission, nullptr);
    EXPECT_TRUE(manager.onNavigationFailed(mission->id));
    EXPECT_EQ(manager.state(), MissionState::FAILED);
    EXPECT_FALSE(manager.hasMission());
}

TEST(MissionManagerTest, NavigationResultIsIgnoredOutsideRunningState)
{
    MissionManager manager;

    manager.submit(std::vector<std::shared_ptr<Mission>>{makeMission(1.0, 1.0)});
    const MissionId queued_id = manager.nextMission()->id;

    manager.emergency();
    EXPECT_FALSE(manager.onNavigationDone(queued_id));
    EXPECT_EQ(manager.state(), MissionState::EMERGENCY);

    EXPECT_FALSE(manager.onNavigationFailed(queued_id));
    EXPECT_EQ(manager.state(), MissionState::EMERGENCY);

    manager.clearEmergency();
    manager.submit(std::vector<std::shared_ptr<Mission>>{makeMission(2.0, 2.0)});
    manager.cancel();
    EXPECT_FALSE(manager.onNavigationDone(queued_id));
    EXPECT_EQ(manager.state(), MissionState::CANCELLED);
}

TEST(MissionManagerTest, EmergencyClearsAndBlocksNewMissionsUntilCleared)
{
    MissionManager manager;

    manager.submit(std::vector<std::shared_ptr<Mission>>{makeMission(1.0, 1.0)});
    manager.emergency();
    EXPECT_EQ(manager.state(), MissionState::EMERGENCY);
    EXPECT_FALSE(manager.hasMission());

    manager.submit(std::vector<std::shared_ptr<Mission>>{makeMission(2.0, 2.0)});
    EXPECT_EQ(manager.state(), MissionState::EMERGENCY);
    EXPECT_FALSE(manager.hasMission());

    manager.clearEmergency();
    manager.submit(std::vector<std::shared_ptr<Mission>>{makeMission(3.0, 3.0)});
    EXPECT_EQ(manager.state(), MissionState::QUEUED);
    EXPECT_TRUE(manager.hasMission());
}

// ─────────────────────────────────────────────────────────────────────────────
// A1 — submit rỗng không được đụng vào hàng đợi đang chạy.
//
// Một order lỗi từ fleet manager (convert() trả rỗng) không phải là lệnh "huỷ mọi thứ". Nếu nó xoá
// queue thì chặng đang chạy mất chủ: robot vẫn đi tới goal cũ vì không ai bảo navigation dừng, còn
// mission layer thì kẹt ở RUNNING mà không còn mission nào để hoàn tất.
// ─────────────────────────────────────────────────────────────────────────────

TEST(MissionManagerTest, EmptySubmitLeavesRunningMissionUntouched)
{
    MissionManager manager;

    manager.submit(makeMissions(2));

    const auto running = manager.nextMission();
    ASSERT_NE(running, nullptr);
    ASSERT_EQ(manager.state(), MissionState::RUNNING);

    // Order lỗi -> convert() rỗng -> submit rỗng.
    manager.submit({});

    EXPECT_EQ(manager.state(), MissionState::RUNNING);
    EXPECT_TRUE(manager.hasMission()) << "an empty submit wiped the running mission and the queue";
    EXPECT_EQ(manager.currentMissionId(), running->id) << "the running mission was replaced";
    EXPECT_EQ(manager.takePendingCancel(), kInvalidMissionId)
        << "an empty submit must not ask navigation to stop";

    // Chặng thứ hai vẫn phải còn trong hàng đợi.
    EXPECT_TRUE(manager.onNavigationDone(running->id));
    EXPECT_EQ(manager.state(), MissionState::QUEUED);
}

// ─────────────────────────────────────────────────────────────────────────────
// A2 — outcome phải mang MissionId.
//
// Không có ID thì kết quả của chặng cũ (đến trễ, sau khi order đã bị thay) được ghi nhận cho chặng
// mới: mission mới "hoàn thành" trong khi robot chưa hề chạy nó.
// ─────────────────────────────────────────────────────────────────────────────

TEST(MissionManagerTest, StaleOutcomeOfPreemptedMissionIsRejected)
{
    MissionManager manager;

    manager.submit(makeMissions(1));
    const auto first = manager.nextMission();
    ASSERT_NE(first, nullptr);

    // Order mới thay order cũ trong lúc chặng đầu đang chạy.
    manager.submit(makeMissions(1));
    const auto second = manager.nextMission();
    ASSERT_NE(second, nullptr);
    ASSERT_NE(first->id, second->id);

    // Outcome của chặng ĐẦU về muộn, sau khi chặng hai đã bắt đầu.
    EXPECT_FALSE(manager.onNavigationDone(first->id));
    EXPECT_EQ(manager.state(), MissionState::RUNNING) << "a late outcome completed the wrong "
                                                         "mission";
    EXPECT_EQ(manager.currentMissionId(), second->id);

    // Outcome đúng của chặng hai vẫn được nhận bình thường.
    EXPECT_TRUE(manager.onNavigationDone(second->id));
    EXPECT_EQ(manager.state(), MissionState::COMPLETED);
}

TEST(MissionManagerTest, MissionIdsAreUniqueAndMonotonic)
{
    MissionManager manager;

    manager.submit(makeMissions(3));

    MissionId previous = kInvalidMissionId;
    for (int i = 0; i < 3; ++i)
    {
        const auto mission = manager.nextMission();
        ASSERT_NE(mission, nullptr) << "leg number " << i;
        EXPECT_GT(mission->id, previous);
        previous = mission->id;

        ASSERT_TRUE(manager.onNavigationDone(mission->id));
    }

    EXPECT_EQ(manager.state(), MissionState::COMPLETED);
}

TEST(MissionManagerTest, RunningMissionIsDispatchedOnlyOnce)
{
    MissionManager manager;

    manager.submit(makeMissions(2));

    const auto first = manager.nextMission();
    ASSERT_NE(first, nullptr);

    // M6: trong lúc RUNNING không có mission mới nào để giao.
    EXPECT_EQ(manager.nextMission(), nullptr);
    EXPECT_EQ(manager.nextMission(), nullptr);

    ASSERT_TRUE(manager.onNavigationDone(first->id));
    const auto second = manager.nextMission();
    ASSERT_NE(second, nullptr);
    EXPECT_NE(second->id, first->id);
}

// ─────────────────────────────────────────────────────────────────────────────
// A3 — mission layer phải có đường bảo navigation dừng.
// ─────────────────────────────────────────────────────────────────────────────

TEST(MissionManagerTest, CancelRequestsNavigationStopOfRunningMission)
{
    MissionManager manager;

    manager.submit(makeMissions(2));
    const auto running = manager.nextMission();
    ASSERT_NE(running, nullptr);

    manager.cancel();

    EXPECT_EQ(manager.takePendingCancel(), running->id);
    EXPECT_EQ(manager.takePendingCancel(), kInvalidMissionId) << "the stop request was taken twice";
    EXPECT_EQ(manager.state(), MissionState::CANCELLED);
    EXPECT_FALSE(manager.hasMission());
}

TEST(MissionManagerTest, EmergencyRequestsNavigationStopOfRunningMission)
{
    MissionManager manager;

    manager.submit(makeMissions(1));
    const auto running = manager.nextMission();
    ASSERT_NE(running, nullptr);

    manager.emergency();

    EXPECT_EQ(manager.takePendingCancel(), running->id);
    EXPECT_EQ(manager.state(), MissionState::EMERGENCY);
}

TEST(MissionManagerTest, PreemptRequestsNavigationStopOfReplacedMission)
{
    MissionManager manager;

    manager.submit(makeMissions(1));
    const auto first = manager.nextMission();
    ASSERT_NE(first, nullptr);

    manager.submit(makeMissions(1));

    EXPECT_EQ(manager.takePendingCancel(), first->id)
        << "a new order replaced the old one without telling navigation to stop the old leg";
    EXPECT_EQ(manager.state(), MissionState::QUEUED);
}

TEST(MissionManagerTest, SubmitWhilePausedKeepsRobotStopped)
{
    MissionManager manager;

    manager.submit(makeMissions(1));
    manager.pause();

    manager.submit(makeMissions(1));

    EXPECT_EQ(manager.state(), MissionState::PAUSED) << "a new order started running on its own "
                                                        "while PAUSED";
    EXPECT_EQ(manager.nextMission(), nullptr);

    manager.resume();
    EXPECT_EQ(manager.state(), MissionState::QUEUED);
    EXPECT_NE(manager.nextMission(), nullptr);
}

// ─────────────────────────────────────────────────────────────────────────────
// M8 — một chặng hỏng thì phần còn lại của hàng đợi đi đâu.
// ─────────────────────────────────────────────────────────────────────────────

TEST(MissionManagerTest, FailureClearsRemainingQueueByDefault)
{
    MissionManager manager;   // clear_queue_on_failure mặc định = true

    manager.submit(makeMissions(3));
    const auto first = manager.nextMission();
    ASSERT_NE(first, nullptr);

    ASSERT_TRUE(manager.onNavigationFailed(first->id));

    // Tuyến tuần tự: không tới được chặng 1 thì chặng 2 nằm sau nó cũng không còn ý nghĩa.
    EXPECT_EQ(manager.state(), MissionState::FAILED);
    EXPECT_FALSE(manager.hasMission());
}

TEST(MissionManagerTest, FailureKeepsQueueWhenConfigured)
{
    MissionConfig config;
    config.clear_queue_on_failure = false;   // các mission trong hàng đợi độc lập với nhau
    MissionManager manager(config);

    manager.submit(makeMissions(3));
    const auto first = manager.nextMission();
    ASSERT_NE(first, nullptr);

    ASSERT_TRUE(manager.onNavigationFailed(first->id));

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

    const auto second = manager.nextMission();
    ASSERT_NE(second, nullptr);
    EXPECT_NE(second->id, first->id);
}

TEST(MissionManagerTest, LastMissionFailureEndsInFailedEvenWhenQueueKept)
{
    MissionConfig config;
    config.clear_queue_on_failure = false;
    MissionManager manager(config);

    manager.submit(makeMissions(1));
    const auto only = manager.nextMission();
    ASSERT_NE(only, nullptr);

    ASSERT_TRUE(manager.onNavigationFailed(only->id));
    EXPECT_EQ(manager.state(), MissionState::FAILED);
}

// ─────────────────────────────────────────────────────────────────────────────
// M7 — mission_timeout là lưới cuối khi navigation không bao giờ báo kết quả về.
// ─────────────────────────────────────────────────────────────────────────────

TEST(MissionManagerTest, TimeoutDisabledByDefaultLetsMissionRunOn)
{
    MissionManager manager;

    manager.submit(makeMissions(1));
    ASSERT_NE(manager.nextMission(), nullptr);

    // waitForWork phải chặn (không có việc); wakeUp là đường ra duy nhất.
    std::thread waker([&manager] {
        std::this_thread::sleep_for(std::chrono::milliseconds(80));
        manager.wakeUp();
    });

    EXPECT_FALSE(manager.waitForWork());
    waker.join();

    EXPECT_EQ(manager.state(), MissionState::RUNNING) << "the mission was cancelled although the "
                                                         "timeout is off";
}

TEST(MissionManagerTest, TimeoutFailsMissionAndRequestsNavigationStop)
{
    MissionConfig config;
    config.mission_timeout = 0.15;   // [s]
    MissionManager manager(config);

    manager.submit(makeMissions(1));
    const auto running = manager.nextMission();
    ASSERT_NE(running, nullptr);

    const auto started = std::chrono::steady_clock::now();

    // waitForWork tự thức dậy đúng lúc hết hạn và biến quá hạn thành việc cần làm.
    ASSERT_TRUE(manager.waitForWork());

    const double elapsed =
        std::chrono::duration<double>(std::chrono::steady_clock::now() - started).count();
    EXPECT_GE(elapsed, 0.15) << "the leg was cancelled before it expired";

    EXPECT_EQ(manager.state(), MissionState::FAILED);
    EXPECT_EQ(manager.takePendingCancel(), running->id)
        << "it expired but navigation was not told to stop — the robot keeps driving to the old "
           "goal";
}

TEST(MissionManagerTest, TimeoutIsMeasuredPerMissionNotPerQueue)
{
    MissionConfig config;
    config.mission_timeout = 0.2;   // [s]
    config.clear_queue_on_failure = false;
    MissionManager manager(config);

    manager.submit(makeMissions(2));

    const auto first = manager.nextMission();
    ASSERT_NE(first, nullptr);

    // Chặng đầu xong sớm; mốc thời gian phải được gieo lại cho chặng hai.
    std::this_thread::sleep_for(std::chrono::milliseconds(120));
    ASSERT_TRUE(manager.onNavigationDone(first->id));

    const auto second = manager.nextMission();
    ASSERT_NE(second, nullptr);

    // Nếu mốc không được gieo lại, chặng hai sẽ hết hạn gần như tức thì.
    const auto started = std::chrono::steady_clock::now();
    ASSERT_TRUE(manager.waitForWork());
    const double elapsed =
        std::chrono::duration<double>(std::chrono::steady_clock::now() - started).count();

    EXPECT_GE(elapsed, 0.19) << "the second leg inherited the clock of the first one";
    EXPECT_EQ(manager.takePendingCancel(), second->id);
}

}  // namespace

int main(int argc, char** argv)
{
    testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}
