#include <doctest/doctest.h>

#include "fgc/TestRunner.h"
#include "fgc/mock/MockImuSource.h"
#include "fgc/mock/MockMotorController.h"

#include <chrono>
#include <thread>

using namespace fgc;

namespace {
// A tiny, fast profile so a full run completes in well under a second.
TestProfile fastProfile() {
    TestProfile p;
    p.name = "test";
    p.params["reps"] = 2;
    p.params["home_begin_timeout_ms"] = 80;
    p.params["home_timeout_ms"] = 2000;
    p.params["settle_timeout_ms"] = 2000;
    p.params["encoder_diag_reps"] = 1;
    p.params["hold_window_ms"] = 50;
    p.params["balance_steps"] = 3;
    p.params["imu_sample_window_ms"] = 80;
    p.params["imu_drift_window_ms"] = 80;
    p.text["backlash_step_counts"] = "500";
    return p;
}

Geometry unitGeometry() {
    Geometry g;
    g.yaw.counts_per_deg = 1000;  g.yaw.zero_count = 0;  g.yaw.min_deg = -90; g.yaw.max_deg = 90;
    g.pitch.counts_per_deg = 1000; g.pitch.zero_count = 0; g.pitch.min_deg = 0; g.pitch.max_deg = 60;
    return g;
}

std::optional<TestReport> runToCompletion(TestRunner& tr, int max_ms = 8000) {
    REQUIRE(tr.start());
    const auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(max_ms);
    while (std::chrono::steady_clock::now() < deadline) {
        if (auto rep = tr.takeReport()) return rep;
        std::this_thread::sleep_for(std::chrono::milliseconds(20));
    }
    return std::nullopt;
}
}  // namespace

TEST_CASE("resolveTestSelection maps subsystem/test tokens") {
    uint32_t m = 0;
    std::string err;
    CHECK(resolveTestSelection("", "", m, err));
    CHECK(m == T_ALL);
    CHECK(resolveTestSelection("gimbal", "", m, err));
    CHECK(m == T_GIMBAL_ALL);
    CHECK(resolveTestSelection("gimbal", "homing", m, err));
    CHECK(m == T_GIMBAL_HOMING);
    CHECK(resolveTestSelection("host", "disk", m, err));
    CHECK(m == T_HOST_DISK);
    CHECK_FALSE(resolveTestSelection("bogus", "", m, err));
    CHECK_FALSE(resolveTestSelection("gimbal", "bogus", m, err));
}

TEST_CASE("TestRunner host-only run completes and produces sections") {
    MockMotorController motor;
    TestRunner tr(motor, nullptr, unitGeometry(), T_HOST_ALL, fastProfile(), ".");
    auto rep = runToCompletion(tr);
    REQUIRE(rep.has_value());
    CHECK_FALSE(tr.running());
    CHECK(rep->find("host_memory", "ram_avail_mb") != nullptr);
    CHECK(rep->find("host_disk", "free_gb") != nullptr);
}

TEST_CASE("TestRunner full run with mocks completes with all selected sections") {
    MockMotorController motor;
    MockImuSource imu;
    imu.start();
    TestRunner tr(motor, &imu, unitGeometry(), T_ALL, fastProfile(), ".");
    auto rep = runToCompletion(tr);
    REQUIRE(rep.has_value());

    auto has = [&](const char* id) {
        for (const auto& s : rep->sections) if (s.id == id) return true;
        return false;
    };
    CHECK(has("homing"));
    CHECK(has("encoder"));
    CHECK(has("friction"));
    CHECK(has("backlash"));
    CHECK(has("balance"));
    CHECK(has("imu_config"));
    CHECK(has("imu_health"));
    CHECK(has("imu_drift"));
    CHECK(has("host_thermal"));
}

TEST_CASE("TestRunner is cancellable") {
    MockMotorController motor;
    MockImuSource imu;
    imu.start();
    TestProfile p = fastProfile();
    p.params["imu_drift_window_ms"] = 5000;  // long enough to cancel mid-run
    TestRunner tr(motor, &imu, unitGeometry(), T_ALL, p, ".");
    REQUIRE(tr.start());
    std::this_thread::sleep_for(std::chrono::milliseconds(50));
    tr.cancel();
    const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(6);
    while (tr.running() && std::chrono::steady_clock::now() < deadline)
        std::this_thread::sleep_for(std::chrono::milliseconds(20));
    CHECK_FALSE(tr.running());
}
