    /**
 * @file test_cli_config.cpp
 * @brief RM 子工程命令行配置解析单元测试。
 *
 * @author mashiro
 * @date 2026-07-09
 * @last_modified 2026-07-09
 *
 * @details
 * 本文件测试 `rm_json_runner`、`rm_drag_teach_record` 和 `rm_trajectory_convert`
 * 三个入口程序的 CLI 参数解析逻辑。测试只构造 `argc/argv`，不读取真实 JSON 或 CSV，
 * 不连接 RM75，也不执行任何运动。
 *
 * 设计原则：
 * - 每个测试只验证一个清晰行为；
 * - 失败输出明确错误文本；
 * - 不依赖绝对路径；
 * - 不访问真实硬件或网络。
 *
 * @dependencies
 * - rm_control/config/json_runner_cli_config.h
 * - rm_control/config/drag_teach_cli_config.h
 * - rm_control/config/trajectory_convert_cli_config.h
 */
#include "rm_control/config/drag_teach_cli_config.h"
#include "rm_control/config/json_runner_cli_config.h"
#include "rm_control/config/trajectory_convert_cli_config.h"

#include <iostream>
#include <string>

namespace {

class CliConfigTestSuite {
public:
    int Run() {
        testJsonRunnerValidateOnlyArguments();
        testJsonRunnerRejectsUnknownOption();
        testDragTeachRecordArguments();
        testDragTeachLiveSafetyArguments();
        testTrajectoryConvertDefaultOutput();
        return failures_;
    }

private:
    int failures_ = 0;

/**
     * @brief 记录一个布尔断言结果。
     *
     * @author mashiro
     * @date 2026-07-09
     * @last_modified 2026-07-09
     *
     * @details
     * 当前 RM 子工程测试保持零外部依赖，不使用 GoogleTest。该函数提供最小断言能力：
     * 条件不满足时累加失败计数并输出错误说明，主函数最终根据失败计数返回退出码。
     *
     * @param[in] condition 被验证的布尔条件。
     * @param[in] message 断言失败时输出的错误说明。
     *
     * @return 无返回值。
     *
     * @throws 本函数不主动抛出异常；输出流异常由运行环境处理。
     *
     * @note
     * 该函数只用于测试代码，不应被生产代码依赖。
     *
     * @warning
     * 失败计数属于当前测试套件实例，不使用可变全局状态。
     *
     * @thread_safety
     * 非线程安全；当前测试单线程顺序执行。
     *
     * @par 边界条件
     * 空 message 仍会输出通用失败前缀。
     *
     * @par 后续维护
     * 若未来引入 GoogleTest，应删除该轻量断言函数并迁移到标准断言宏。
     */
    void expect(bool condition, const std::string &message) {
        if (!condition) {
            ++failures_;
            std::cerr << "[FAIL] " << message << '\n';
        }
    }

    /**
     * @brief 测试 JSON runner 的 validate-only 参数解析。
     *
     * @author mashiro
     * @date 2026-07-09
     * @last_modified 2026-07-09
     *
     * @details
     * 该测试构造 `rm_json_runner config.json --validate-only` 参数，确认解析结果包含
     * JSON 文件路径并设置 validate-only 标志。该行为是新 JSON 文件安全验证的第一步。
     *
     * @param 无输入参数。
     *
     * @return 无返回值；失败通过 `expect()` 记录。
     *
     * @throws 本函数不主动抛出异常。
     *
     * @note
     * 测试不检查文件是否存在，因为 CLI 层不负责文件读取。
     *
     * @warning
     * 如果未来改变 validate-only 参数名称，需要同步更新该测试。
     *
     * @thread_safety
     * 非线程安全；依赖测试全局失败计数。
     *
     * @par 边界条件
     * 参数数组使用普通字符串常量，模拟最常见调用路径。
     *
     * @par 后续维护
     * 保持该测试覆盖“不连接硬件前就能解析参数”的安全路径。
     */
    void testJsonRunnerValidateOnlyArguments() {
        char program[] = "rm_json_runner";
        char command_file[] = "config.json";
        char validate_only[] = "--validate-only";
        char *argv[] = {program, command_file, validate_only};

        const auto config = rm_control::config::ParseJsonRunnerCliArguments(3, argv);
        expect(config.ok, "json runner validate-only arguments should parse");
        if (!config.ok) {
            return;
        }
        expect(config.value.command_file_path == "config.json", "json runner command path parsed");
        expect(config.value.validate_only, "json runner validate-only flag parsed");
    }

    /**
     * @brief 测试 JSON runner 拒绝未知选项。
     *
     * @author mashiro
     * @date 2026-07-09
     * @last_modified 2026-07-09
     *
     * @details
     * 该测试确认 CLI 层会拒绝未知选项，避免用户拼错安全相关参数后程序继续进入
     * JSON 加载或硬件连接阶段。
     *
     * @param 无输入参数。
     *
     * @return 无返回值；失败通过 `expect()` 记录。
     *
     * @throws 本函数不主动抛出异常。
     *
     * @note
     * 不访问文件系统。
     *
     * @warning
     * 后续新增选项时应只放开明确支持的选项。
     *
     * @thread_safety
     * 非线程安全；依赖测试全局失败计数。
     *
     * @par 边界条件
     * 未知选项放在第二业务参数位置。
     *
     * @par 后续维护
     * 保持错误路径测试，防止 CLI 解析过度宽松。
     */
    void testJsonRunnerRejectsUnknownOption() {
        char program[] = "rm_json_runner";
        char command_file[] = "config.json";
        char unknown_option[] = "--unsafe";
        char *argv[] = {program, command_file, unknown_option};

        const auto config = rm_control::config::ParseJsonRunnerCliArguments(3, argv);
        expect(!config.ok, "json runner should reject unknown option");
    }

    /**
     * @brief 测试拖动示教 CLI 的位置参数和选项解析。
     *
     * @author mashiro
     * @date 2026-07-09
     * @last_modified 2026-07-09
     *
     * @details
     * 该测试覆盖 session name、录制时长、采样周期、存储根目录和 `--no-vendor-save`
     * 选项，确保入口文件可以拿到完整结构化配置，而不需要自己解析字符串。
     *
     * @param 无输入参数。
     *
     * @return 无返回值；失败通过 `expect()` 记录。
     *
     * @throws 本函数不主动抛出异常。
     *
     * @note
     * 测试不会创建轨迹目录，也不会连接 RM75。
     *
     * @warning
     * 如果未来修改位置参数顺序，需要同步更新测试和 README。
     *
     * @thread_safety
     * 非线程安全；依赖测试全局失败计数。
     *
     * @par 边界条件
     * 使用相对存储根目录，避免绝对路径依赖。
     *
     * @par 后续维护
     * 新增安全选项时应补充对应测试。
     */
    void testDragTeachRecordArguments() {
        char program[] = "rm_drag_teach_record";
        char session[] = "demo";
        char duration[] = "12";
        char sample_period[] = "100";
        char storage_root[] = "trajectory_output";
        char no_vendor_save[] = "--no-vendor-save";
        char *argv[] = {program, session, duration, sample_period, storage_root, no_vendor_save};

        const auto config = rm_control::config::ParseDragTeachRecordCliArguments(6, argv);
        expect(config.ok, "drag teach arguments should parse");
        if (!config.ok) {
            return;
        }
        expect(config.value.record_config.session_name == "demo", "drag teach session parsed");
        expect(config.value.record_config.duration_seconds == 12, "drag teach duration parsed");
        expect(config.value.record_config.sample_period_ms == 100, "drag teach sample period parsed");
        expect(config.value.record_config.storage_root == "trajectory_output", "drag teach storage parsed");
        expect(!config.value.record_config.save_vendor_trajectory, "drag teach no vendor save parsed");
        expect(!config.value.live_requested, "drag teach must default to DryRun authorization");
        expect(config.value.record_config.drag_options.mode ==
                   rm_control::common::DragTeachMode::OrdinaryRecorded,
               "ordinary recorded mode should be the safe default");
    }

    void testDragTeachLiveSafetyArguments() {
        char program[] = "rm_drag_teach_record";
        char live[] = "--live";
        char safety_option[] = "--safety-config";
        char safety_path[] = "config/custom_drag_safety.json";
        char six_dof[] = "--six-dof-force";
        char *argv[] = {program, live, safety_option, safety_path, six_dof};

        const auto config = rm_control::config::ParseDragTeachRecordCliArguments(5, argv);
        expect(config.ok, "drag teach live safety arguments should parse");
        if (!config.ok) {
            return;
        }
        expect(config.value.live_requested, "--live should require explicit presence");
        expect(config.value.safety_config_path == safety_path,
               "custom safety config path should be preserved as filesystem path");
        expect(config.value.record_config.drag_options.mode ==
                   rm_control::common::DragTeachMode::SixDofForcePositionAndOrientation,
               "six-dof force mode should require its explicit option");
    }

    /**
     * @brief 测试轨迹转换 CLI 的默认输出路径解析。
     *
     * @author mashiro
     * @date 2026-07-09
     * @last_modified 2026-07-09
     *
     * @details
     * 该测试只提供输入 CSV 路径，确认 CLI 层能够生成 session name 和默认输出路径。
     * 默认路径实际写入能力由 trajectory 模块测试覆盖。
     *
     * @param 无输入参数。
     *
     * @return 无返回值；失败通过 `expect()` 记录。
     *
     * @throws 本函数不主动抛出异常。
     *
     * @note
     * 解析阶段可能确保默认轨迹目录存在，但不会读取输入 CSV。
     *
     * @warning
     * 如果默认存储策略改变，需要同步更新断言粒度。
     *
     * @thread_safety
     * 非线程安全；依赖测试全局失败计数。
     *
     * @par 边界条件
     * 输入路径包含目录和扩展名，用于验证 session name 提取。
     *
     * @par 后续维护
     * 保持该测试关注 CLI 输出，不把 CSV 内容校验混入其中。
     */
    void testTrajectoryConvertDefaultOutput() {
        char program[] = "rm_trajectory_convert";
        char input_csv[] = "csv/demo-path.csv";
        char *argv[] = {program, input_csv};

        const auto config = rm_control::config::ParseTrajectoryConvertCliArguments(2, argv);
        expect(config.ok, "trajectory convert arguments should parse");
        if (!config.ok) {
            return;
        }
        expect(config.value.input_csv_path == "csv/demo-path.csv", "trajectory input path parsed");
        expect(config.value.session_name == "demo-path", "trajectory session name parsed");
        expect(config.value.output_cpp_path.find("demo-path_tcp_path.cpp") != std::string::npos,
               "trajectory default output path parsed");
    }

};

} // namespace

/**
 * @brief 运行 CLI 配置解析单元测试。
 *
 * @author mashiro
 * @date 2026-07-09
 * @last_modified 2026-07-09
 *
 * @details
 * 该入口按顺序运行所有 CLI 解析测试，并根据失败计数返回进程状态码。
 * 测试不访问真实硬件、不启动外部进程、不依赖网络。
 *
 * @param 无输入参数。
 *
 * @return 0 所有测试通过。
 * @return 1 至少一个测试失败。
 *
 * @throws 本函数不主动抛出异常。
 *
 * @note
 * 该测试程序由 CTest 调用。
 *
 * @warning
 * 每个测试进程创建独立套件实例，不共享可变全局状态。
 *
 * @thread_safety
 * 非线程安全；单线程测试入口。
 *
 * @par 边界条件
 * 任一测试提前返回只影响该测试内部后续断言，不影响其他测试继续执行。
 *
 * @par 后续维护
 * 新增 CLI 模块时应在此入口追加对应测试函数。
 */
int main() {
    CliConfigTestSuite suite;
    const int failures = suite.Run();

    if (failures == 0) {
        std::cout << "[PASS] rm_cli_config_tests\n";
        return 0;
    }

    std::cerr << failures << " test(s) failed\n";
    return 1;
}
