#include "rm_control/modules/trajectory/trajectory_playback_report_store.h"

#include <filesystem>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>

#ifndef RM_TEST_OUTPUT_DIR
#define RM_TEST_OUTPUT_DIR "."
#endif

namespace {

struct TestContext {
    int failures = 0;

    void Expect(bool condition, const std::string &message) {
        if (!condition) {
            ++failures;
            std::cerr << "[FAIL] " << message << '\n';
        }
    }
};

rm_control::modules::trajectory::ControllerManagedPlaybackReport ValidReport() {
    using Report =
        rm_control::modules::trajectory::ControllerManagedPlaybackReport;
    Report report;
    report.report_identifier = "20260711_120000";
    report.source_file = "source.txt";
    report.source_file_sha256 = std::string(64U, 'a');
    report.trajectory_point_count = 3U;
    report.sample_period_ms = 100;
    report.source_duration_ms = 200;
    report.plan_speed_percent = 5;
    report.controller_program_id = 7;
    report.observed_execution_duration_ms = 450.0;
    report.program_status_poll_count = 4U;
    report.minimum_poll_period_ms = 99.0;
    report.maximum_poll_period_ms = 102.0;
    report.average_poll_period_ms = 100.3;
    report.maximum_absolute_poll_jitter_ms = 2.0;
    report.final_controller_state = 0;
    report.o6_device_id = 0x27U;
    report.target_positions = {140U, 100U, 55U, 55U, 55U, 55U};
    report.speed_commands = {20U, 20U, 20U, 20U, 20U, 20U};
    report.torque_commands = {30U, 30U, 30U, 30U, 30U, 30U};
    report.final_positions = report.target_positions;
    return report;
}

std::string ReadText(const std::filesystem::path &path) {
    std::ifstream input(path);
    std::ostringstream text;
    text << input.rdbuf();
    return text.str();
}

void TestWritesStructuredReportWithoutOverwrite(TestContext *context) {
    const std::filesystem::path directory =
        std::filesystem::path(RM_TEST_OUTPUT_DIR) / "playback_report_store";
    std::filesystem::remove_all(directory);
    const auto first =
        rm_control::modules::trajectory::WriteControllerManagedPlaybackReportWithoutOverwrite(
            ValidReport(), directory);
    context->Expect(first.ok, "valid controller-managed report should be written");
    if (!first.ok) {
        return;
    }
    const std::string text = ReadText(first.value);
    context->Expect(text.find("host_program_state_polling_not_point_dispatch") !=
                        std::string::npos,
                    "report must state the scope of polling jitter");
    context->Expect(text.find("\"target_positions\": [140, 100, 55") !=
                        std::string::npos,
                    "report must preserve the O6 target positions");

    const auto duplicate =
        rm_control::modules::trajectory::WriteControllerManagedPlaybackReportWithoutOverwrite(
            ValidReport(), directory);
    context->Expect(!duplicate.ok,
                    "same report identifier must not overwrite an existing file");
}

void TestRejectsInvalidMetadataBeforeWriting(TestContext *context) {
    auto report = ValidReport();
    report.source_file_sha256 = "not-a-sha256";
    const std::filesystem::path directory =
        std::filesystem::path(RM_TEST_OUTPUT_DIR) / "invalid_playback_report";
    std::filesystem::remove_all(directory);
    const auto result =
        rm_control::modules::trajectory::WriteControllerManagedPlaybackReportWithoutOverwrite(
            report, directory);
    context->Expect(!result.ok, "invalid report metadata must be rejected");
    context->Expect(!std::filesystem::exists(directory),
                    "invalid report must not create an output directory");
}

void TestWritesRoundTripMetadata(TestContext *context) {
    auto report = ValidReport();
    report.report_identifier = "20260711_120001";
    report.returned_from_trajectory_end = true;
    report.return_source_file = "return_source.txt";
    report.return_source_file_sha256 = std::string(64U, 'b');
    report.return_controller_program_id = 8;
    report.return_observed_execution_duration_ms = 430.0;
    report.return_program_status_poll_count = 5U;
    report.return_minimum_poll_period_ms = 98.0;
    report.return_maximum_poll_period_ms = 103.0;
    report.return_average_poll_period_ms = 100.1;
    report.return_maximum_absolute_poll_jitter_ms = 3.0;
    report.return_final_controller_state = 0;
    const std::filesystem::path directory =
        std::filesystem::path(RM_TEST_OUTPUT_DIR) / "round_trip_playback_report";
    std::filesystem::remove_all(directory);

    const auto result =
        rm_control::modules::trajectory::WriteControllerManagedPlaybackReportWithoutOverwrite(
            report, directory);

    context->Expect(result.ok, "valid round-trip report should be written");
    if (!result.ok) {
        return;
    }
    const std::string text = ReadText(result.value);
    context->Expect(text.find("\"returned_from_trajectory_end\": true") !=
                        std::string::npos,
                    "report must identify the reverse-return workflow");
    context->Expect(text.find("\"return_controller_program_id\": 8") !=
                        std::string::npos,
                    "report must preserve the return controller program id");
    context->Expect(text.find(std::string(64U, 'b')) != std::string::npos,
                    "report must preserve the return trajectory SHA-256");
}

} // namespace

int main() {
    TestContext context;
    TestWritesStructuredReportWithoutOverwrite(&context);
    TestRejectsInvalidMetadataBeforeWriting(&context);
    TestWritesRoundTripMetadata(&context);
    if (context.failures == 0) {
        std::cout << "[PASS] rm_trajectory_playback_report_store_tests\n";
        return 0;
    }
    std::cerr << context.failures << " test(s) failed\n";
    return 1;
}
