/**
 * @file sha256_file_checksum.cpp
 * @brief 使用 OpenSSL EVP 流式计算轨迹文件 SHA-256。
 *
 * @author 唐永康
 * @date 2026-07-10
 */
#include "rm_control/modules/trajectory/sha256_file_checksum.h"

#include <openssl/evp.h>

#include <array>
#include <fstream>
#include <iomanip>
#include <memory>
#include <sstream>

namespace rm_control::modules::trajectory {
namespace {

constexpr std::size_t kDigestReadBufferSize = 64U * 1024U;

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

} // namespace

common::Result<std::string> CalculateFileSha256(const std::filesystem::path &file_path) {
    if (file_path.empty()) {
        return common::Result<std::string>::failure("SHA-256 file path must not be empty");
    }

    std::error_code filesystem_error;
    const bool is_regular_file = std::filesystem::is_regular_file(file_path, filesystem_error);
    if (filesystem_error) {
        return common::Result<std::string>::failure(
            "failed to inspect SHA-256 input " + PathForError(file_path) + ": " +
            filesystem_error.message());
    }
    if (!is_regular_file) {
        return common::Result<std::string>::failure(
            "SHA-256 input is not a regular file: " + PathForError(file_path));
    }

    std::ifstream input(file_path, std::ios::binary);
    if (!input) {
        return common::Result<std::string>::failure(
            "failed to open SHA-256 input: " + PathForError(file_path));
    }

    using DigestContext = std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_free)>;
    DigestContext digest_context(EVP_MD_CTX_new(), &EVP_MD_CTX_free);
    if (!digest_context) {
        return common::Result<std::string>::failure(
            "OpenSSL failed to allocate SHA-256 digest context for " + PathForError(file_path));
    }
    if (EVP_DigestInit_ex(digest_context.get(), EVP_sha256(), nullptr) != 1) {
        return common::Result<std::string>::failure(
            "OpenSSL failed to initialize SHA-256 for " + PathForError(file_path));
    }

    std::array<char, kDigestReadBufferSize> buffer{};
    while (input) {
        input.read(buffer.data(), static_cast<std::streamsize>(buffer.size()));
        const std::streamsize bytes_read = input.gcount();
        if (bytes_read > 0 &&
            EVP_DigestUpdate(digest_context.get(), buffer.data(),
                             static_cast<std::size_t>(bytes_read)) != 1) {
            return common::Result<std::string>::failure(
                "OpenSSL failed to update SHA-256 for " + PathForError(file_path));
        }
    }
    if (!input.eof()) {
        return common::Result<std::string>::failure(
            "failed while reading SHA-256 input: " + PathForError(file_path));
    }

    std::array<unsigned char, EVP_MAX_MD_SIZE> digest{};
    unsigned int digest_size = 0;
    if (EVP_DigestFinal_ex(digest_context.get(), digest.data(), &digest_size) != 1) {
        return common::Result<std::string>::failure(
            "OpenSSL failed to finalize SHA-256 for " + PathForError(file_path));
    }

    std::ostringstream hexadecimal_digest;
    hexadecimal_digest << std::hex << std::setfill('0');
    for (unsigned int index = 0; index < digest_size; ++index) {
        hexadecimal_digest << std::setw(2) << static_cast<unsigned int>(digest[index]);
    }
    return common::Result<std::string>::success(hexadecimal_digest.str());
}

} // namespace rm_control::modules::trajectory
