/**
 * @file reversed_vendor_trajectory_store.cpp
 * @brief RM75 厂商 TXT 轨迹的只读反向派生文件实现。
 *
 * @author 唐永康
 * @date 2026-07-11
 */
#include "rm_control/modules/trajectory/reversed_vendor_trajectory_store.h"

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

#include <algorithm>
#include <chrono>
#include <fstream>
#include <string>
#include <system_error>
#include <utility>
#include <vector>

#ifndef RM_RUNTIME_DATA_ROOT
#define RM_RUNTIME_DATA_ROOT "build/runtime_data"
#endif

namespace rm_control::modules::trajectory {
namespace {

constexpr int kMaximumUniqueDirectoryAttempts = 1000;
constexpr std::size_t kChecksumNamePrefixLength = 12U;
constexpr const char *kReversedTrajectoryFileName = "reversed_trajectory.txt";

struct PhysicalTextLines {
    std::vector<std::string> lines;
    std::string newline_sequence = "\n";
    bool has_trailing_newline = false;
};

struct ReversedArtifactPaths {
    std::filesystem::path directory;
    std::filesystem::path trajectory_file;
};

class CreatedArtifactCleanup final {
public:
    explicit CreatedArtifactCleanup(ReversedArtifactPaths paths) : paths_(std::move(paths)) {}

    ~CreatedArtifactCleanup() {
        if (released_) {
            return;
        }
        std::error_code ignored_error;
        std::filesystem::remove(paths_.trajectory_file, ignored_error);
        ignored_error.clear();
        std::filesystem::remove(paths_.directory, ignored_error);
    }

    void Release() noexcept {
        released_ = true;
    }

private:
    ReversedArtifactPaths paths_;
    bool released_ = false;
};

std::string PathForError(const std::filesystem::path &path) {
    return path.string();
}

common::Result<void> ValidateForwardMetadata(
    const VendorDragTrajectoryParseResult &trajectory) {
    if (trajectory.source_file.empty() || trajectory.samples.empty() ||
        trajectory.trajectory_point_count != trajectory.samples.size()) {
        return common::Result<void>::failure(
            "forward vendor trajectory path, samples, and point count are inconsistent");
    }
    if (trajectory.source_file_size_bytes == 0U || trajectory.source_file_sha256.empty() ||
        trajectory.sample_period_ms <= 0 || trajectory.raw_units_per_degree <= 0 ||
        !trajectory.timestamp_is_derived) {
        return common::Result<void>::failure(
            "forward vendor trajectory is missing size, SHA-256, units, or derived timing");
    }
    return common::Result<void>::success();
}

common::Result<void> InspectReadOnlyRegularFile(
    const std::filesystem::path &path,
    std::uintmax_t expected_size) {
    std::error_code filesystem_error;
    const auto status = std::filesystem::symlink_status(path, filesystem_error);
    if (filesystem_error || !std::filesystem::is_regular_file(status)) {
        return common::Result<void>::failure(
            filesystem_error ? "failed to inspect trajectory file " + PathForError(path) +
                                   ": " + filesystem_error.message()
                             : "trajectory file must be a non-symlink regular file: " +
                                   PathForError(path));
    }
    const auto write_permissions = std::filesystem::perms::owner_write |
        std::filesystem::perms::group_write | std::filesystem::perms::others_write;
    if ((status.permissions() & write_permissions) != std::filesystem::perms::none) {
        return common::Result<void>::failure(
            "trajectory file must be read-only for owner, group, and others: " +
            PathForError(path));
    }
    const std::uintmax_t actual_size = std::filesystem::file_size(path, filesystem_error);
    if (filesystem_error || actual_size != expected_size) {
        return common::Result<void>::failure(
            filesystem_error ? "failed to read trajectory file size " + PathForError(path) +
                                   ": " + filesystem_error.message()
                             : "trajectory file size differs from parsed metadata: " +
                                   PathForError(path));
    }
    return common::Result<void>::success();
}

common::Result<void> VerifyFileIntegrity(
    const VendorDragTrajectoryParseResult &trajectory) {
    const auto before = InspectReadOnlyRegularFile(
        trajectory.source_file, trajectory.source_file_size_bytes);
    if (!before.ok) {
        return before;
    }
    const auto checksum = CalculateFileSha256(trajectory.source_file);
    if (!checksum.ok) {
        return common::Result<void>::failure(checksum.error_message);
    }
    const auto after = InspectReadOnlyRegularFile(
        trajectory.source_file, trajectory.source_file_size_bytes);
    if (!after.ok) {
        return after;
    }
    if (checksum.value != trajectory.source_file_sha256) {
        return common::Result<void>::failure(
            "trajectory file SHA-256 differs from parsed metadata: " +
            PathForError(trajectory.source_file));
    }
    return common::Result<void>::success();
}

common::Result<bool> FileHasTrailingLineFeed(const std::filesystem::path &path) {
    std::ifstream input(path, std::ios::binary);
    if (!input) {
        return common::Result<bool>::failure(
            "failed to open trajectory while checking its final newline: " +
            PathForError(path));
    }
    input.seekg(-1, std::ios::end);
    char final_byte = '\0';
    input.get(final_byte);
    if (!input) {
        return common::Result<bool>::failure(
            "failed to read trajectory final byte: " + PathForError(path));
    }
    return common::Result<bool>::success(final_byte == '\n');
}

common::Result<void> NormalizeAndValidateNewlineStyle(PhysicalTextLines *text) {
    if (text == nullptr || text->lines.empty()) {
        return common::Result<void>::failure("trajectory physical lines must not be empty");
    }
    bool newline_style_selected = false;
    bool uses_carriage_return = false;
    for (std::size_t index = 0U; index < text->lines.size(); ++index) {
        const bool line_had_terminator = index + 1U < text->lines.size() ||
                                         text->has_trailing_newline;
        if (!line_had_terminator) {
            continue;
        }
        const bool has_carriage_return = !text->lines[index].empty() &&
                                         text->lines[index].back() == '\r';
        if (newline_style_selected && uses_carriage_return != has_carriage_return) {
            return common::Result<void>::failure(
                "vendor trajectory uses mixed LF and CRLF physical line endings");
        }
        newline_style_selected = true;
        uses_carriage_return = has_carriage_return;
        if (has_carriage_return) {
            text->lines[index].pop_back();
        }
    }
    text->newline_sequence = uses_carriage_return ? "\r\n" : "\n";
    return common::Result<void>::success();
}

common::Result<PhysicalTextLines> ReadPhysicalTextLines(
    const std::filesystem::path &path) {
    const auto trailing_line_feed = FileHasTrailingLineFeed(path);
    if (!trailing_line_feed.ok) {
        return common::Result<PhysicalTextLines>::failure(trailing_line_feed.error_message);
    }
    std::ifstream input(path, std::ios::binary);
    if (!input) {
        return common::Result<PhysicalTextLines>::failure(
            "failed to open trajectory for physical-line reading: " + PathForError(path));
    }
    PhysicalTextLines text;
    text.has_trailing_newline = trailing_line_feed.value;
    std::string line;
    while (std::getline(input, line)) {
        text.lines.push_back(line);
    }
    if (!input.eof()) {
        return common::Result<PhysicalTextLines>::failure(
            "failed while reading trajectory physical lines: " + PathForError(path));
    }
    const auto newline = NormalizeAndValidateNewlineStyle(&text);
    return newline.ok ? common::Result<PhysicalTextLines>::success(text)
                      : common::Result<PhysicalTextLines>::failure(newline.error_message);
}

common::Result<void> ValidateOutputDirectory(const std::filesystem::path &output_directory) {
    if (output_directory.empty()) {
        return common::Result<void>::failure(
            "reversed vendor trajectory output directory must not be empty");
    }
    std::error_code filesystem_error;
    std::filesystem::create_directories(output_directory, filesystem_error);
    if (filesystem_error) {
        return common::Result<void>::failure(
            "failed to create reversed trajectory output directory " +
            PathForError(output_directory) + ": " + filesystem_error.message());
    }
    const auto status = std::filesystem::symlink_status(output_directory, filesystem_error);
    if (filesystem_error || !std::filesystem::is_directory(status)) {
        return common::Result<void>::failure(
            "reversed trajectory output path must be a non-symlink directory: " +
            PathForError(output_directory));
    }
    return common::Result<void>::success();
}

common::Result<ReversedArtifactPaths> CreateUniqueArtifactDirectory(
    const std::filesystem::path &output_directory,
    const std::string &source_sha256) {
    const auto directory_check = ValidateOutputDirectory(output_directory);
    if (!directory_check.ok) {
        return common::Result<ReversedArtifactPaths>::failure(
            directory_check.error_message);
    }
    const auto timestamp = std::chrono::duration_cast<std::chrono::nanoseconds>(
        std::chrono::system_clock::now().time_since_epoch()).count();
    const std::string prefix = "reverse_" +
        source_sha256.substr(0U, std::min(kChecksumNamePrefixLength, source_sha256.size())) +
        "_" + std::to_string(timestamp);
    for (int attempt = 0; attempt < kMaximumUniqueDirectoryAttempts; ++attempt) {
        const std::string suffix = attempt == 0 ? "" : "_" + std::to_string(attempt);
        const std::filesystem::path directory = output_directory / (prefix + suffix);
        std::error_code filesystem_error;
        if (std::filesystem::create_directory(directory, filesystem_error)) {
            return common::Result<ReversedArtifactPaths>::success(
                {directory, directory / kReversedTrajectoryFileName});
        }
        if (filesystem_error && filesystem_error != std::errc::file_exists) {
            return common::Result<ReversedArtifactPaths>::failure(
                "failed to create unique reversed trajectory directory " +
                PathForError(directory) + ": " + filesystem_error.message());
        }
    }
    return common::Result<ReversedArtifactPaths>::failure(
        "failed to allocate a unique reversed trajectory directory after " +
        std::to_string(kMaximumUniqueDirectoryAttempts) + " attempts");
}

common::Result<void> WriteReversedPhysicalLinesWithoutOverwrite(
    const PhysicalTextLines &forward_text,
    const std::filesystem::path &output_path) {
    std::error_code filesystem_error;
    if (std::filesystem::exists(output_path, filesystem_error) || filesystem_error) {
        return common::Result<void>::failure(
            filesystem_error ? "failed to inspect reversed trajectory target " +
                                   PathForError(output_path) + ": " + filesystem_error.message()
                             : "refusing to overwrite reversed trajectory target: " +
                                   PathForError(output_path));
    }
    std::ofstream output(output_path, std::ios::binary | std::ios::out);
    if (!output) {
        return common::Result<void>::failure(
            "failed to create reversed trajectory file: " + PathForError(output_path));
    }
    for (std::size_t output_index = 0U; output_index < forward_text.lines.size(); ++output_index) {
        const auto &line = forward_text.lines[forward_text.lines.size() - 1U - output_index];
        output.write(line.data(), static_cast<std::streamsize>(line.size()));
        if (output_index + 1U < forward_text.lines.size() || forward_text.has_trailing_newline) {
            output.write(forward_text.newline_sequence.data(),
                         static_cast<std::streamsize>(forward_text.newline_sequence.size()));
        }
    }
    output.flush();
    output.close();
    if (!output) {
        return common::Result<void>::failure(
            "failed to finish reversed trajectory file: " + PathForError(output_path));
    }
    return common::Result<void>::success();
}

common::Result<void> MakeFileReadOnly(const std::filesystem::path &path) {
    std::error_code filesystem_error;
    const auto read_only_permissions = std::filesystem::perms::owner_read |
        std::filesystem::perms::group_read | std::filesystem::perms::others_read;
    std::filesystem::permissions(
        path, read_only_permissions, std::filesystem::perm_options::replace, filesystem_error);
    return filesystem_error
        ? common::Result<void>::failure(
              "failed to make reversed trajectory read-only " + PathForError(path) + ": " +
              filesystem_error.message())
        : common::Result<void>::success();
}

common::Result<void> ValidateExactPhysicalLineReversal(
    const PhysicalTextLines &forward_text,
    const PhysicalTextLines &reversed_text) {
    if (forward_text.newline_sequence != reversed_text.newline_sequence ||
        forward_text.has_trailing_newline != reversed_text.has_trailing_newline ||
        forward_text.lines.size() != reversed_text.lines.size()) {
        return common::Result<void>::failure(
            "reversed trajectory physical-line count or newline format differs from source");
    }
    for (std::size_t index = 0U; index < reversed_text.lines.size(); ++index) {
        if (reversed_text.lines[index] !=
            forward_text.lines[forward_text.lines.size() - 1U - index]) {
            return common::Result<void>::failure(
                "reversed trajectory line " + std::to_string(index + 1U) +
                " does not exactly match the corresponding source line");
        }
    }
    return common::Result<void>::success();
}

bool PoseExactlyEqual(const common::Pose &left, const common::Pose &right) {
    return left.x == right.x && left.y == right.y && left.z == right.z &&
           left.rx == right.rx && left.ry == right.ry && left.rz == right.rz;
}

common::Result<void> ValidateReversedSamples(
    const VendorDragTrajectoryParseResult &forward,
    const VendorDragTrajectoryParseResult &reversed) {
    if (reversed.samples.size() != forward.samples.size() ||
        reversed.trajectory_point_count != forward.trajectory_point_count ||
        reversed.sample_period_ms != forward.sample_period_ms ||
        reversed.raw_units_per_degree != forward.raw_units_per_degree) {
        return common::Result<void>::failure(
            "reversed trajectory metadata differs from the forward trajectory");
    }
    for (std::size_t index = 0U; index < reversed.samples.size(); ++index) {
        const auto &actual = reversed.samples[index];
        const auto &expected = forward.samples[forward.samples.size() - 1U - index];
        const long long expected_elapsed =
            static_cast<long long>(index) * forward.sample_period_ms;
        if (actual.sample_index != index || actual.elapsed_ms != expected_elapsed ||
            actual.joints_deg != expected.joints_deg ||
            !PoseExactlyEqual(actual.tcp_pose, expected.tcp_pose) ||
            actual.source != expected.source) {
            return common::Result<void>::failure(
                "reversed trajectory sample " + std::to_string(index) +
                " does not exactly match the inverse forward sample");
        }
    }
    return common::Result<void>::success();
}

common::Result<PhysicalTextLines> ReadVerifiedForwardText(
    const VendorDragTrajectoryParseResult &forward) {
    const auto metadata = ValidateForwardMetadata(forward);
    if (!metadata.ok) {
        return common::Result<PhysicalTextLines>::failure(metadata.error_message);
    }
    const auto integrity_before_read = VerifyFileIntegrity(forward);
    if (!integrity_before_read.ok) {
        return common::Result<PhysicalTextLines>::failure(
            integrity_before_read.error_message);
    }
    const auto text = ReadPhysicalTextLines(forward.source_file);
    const auto integrity_after_read = VerifyFileIntegrity(forward);
    if (!text.ok || !integrity_after_read.ok) {
        return common::Result<PhysicalTextLines>::failure(
            !text.ok ? text.error_message : integrity_after_read.error_message);
    }
    if (text.value.lines.size() != forward.samples.size()) {
        return common::Result<PhysicalTextLines>::failure(
            "source physical-line count differs from parsed samples");
    }
    return text;
}

common::Result<VendorDragTrajectoryParseResult> ParseAndValidateReversedArtifact(
    const std::filesystem::path &path,
    const VendorDragTrajectoryParseResult &forward) {
    const auto reversed = ParseVendorDragTrajectoryJsonLines(
        path, forward.sample_period_ms, forward.raw_units_per_degree);
    if (!reversed.ok) {
        return common::Result<VendorDragTrajectoryParseResult>::failure(
            "failed to parse generated reversed trajectory: " + reversed.error_message);
    }
    const auto integrity = VerifyFileIntegrity(reversed.value);
    if (!integrity.ok) {
        return common::Result<VendorDragTrajectoryParseResult>::failure(
            integrity.error_message);
    }
    const auto samples = ValidateReversedSamples(forward, reversed.value);
    return samples.ok
        ? reversed
        : common::Result<VendorDragTrajectoryParseResult>::failure(samples.error_message);
}

} // namespace

std::filesystem::path DefaultReversedVendorTrajectoryDirectory() {
    return std::filesystem::path(RM_RUNTIME_DATA_ROOT) / "reversed_vendor_trajectories";
}

common::Result<VendorDragTrajectoryParseResult>
CreateReadOnlyReversedVendorTrajectoryWithoutOverwrite(
    const VendorDragTrajectoryParseResult &forward_trajectory,
    const std::filesystem::path &output_directory) {
    const auto forward_text = ReadVerifiedForwardText(forward_trajectory);
    if (!forward_text.ok) {
        return common::Result<VendorDragTrajectoryParseResult>::failure(
            forward_text.error_message);
    }
    const auto paths = CreateUniqueArtifactDirectory(
        output_directory, forward_trajectory.source_file_sha256);
    if (!paths.ok) {
        return common::Result<VendorDragTrajectoryParseResult>::failure(paths.error_message);
    }
    CreatedArtifactCleanup cleanup(paths.value);
    const auto write = WriteReversedPhysicalLinesWithoutOverwrite(
        forward_text.value, paths.value.trajectory_file);
    const auto read_only = write.ok
        ? MakeFileReadOnly(paths.value.trajectory_file)
        : common::Result<void>::failure(write.error_message);
    if (!read_only.ok) {
        return common::Result<VendorDragTrajectoryParseResult>::failure(read_only.error_message);
    }
    const auto reversed_text = ReadPhysicalTextLines(paths.value.trajectory_file);
    const auto exact_lines = reversed_text.ok
        ? ValidateExactPhysicalLineReversal(forward_text.value, reversed_text.value)
        : common::Result<void>::failure(reversed_text.error_message);
    if (!exact_lines.ok) {
        return common::Result<VendorDragTrajectoryParseResult>::failure(exact_lines.error_message);
    }
    const auto reversed = ParseAndValidateReversedArtifact(
        paths.value.trajectory_file, forward_trajectory);
    if (!reversed.ok) {
        return reversed;
    }
    cleanup.Release();
    return reversed;
}

} // namespace rm_control::modules::trajectory
