#include <openssl/evp.h>
#include <openssl/sha.h>
#include <fstream>
#include <vector>
#include <stdexcept>
#include <iomanip>
#include <sstream>
#include <string>
#include <array>

std::string sha256_file(const std::string& path) {
  std::ifstream f(path, std::ios::binary);
  if (!f) throw std::runtime_error("Cannot open file: " + path);

  EVP_MD_CTX* ctx = EVP_MD_CTX_new();
  if (!ctx) throw std::runtime_error("EVP_MD_CTX_new failed");
  const EVP_MD* md = EVP_sha256();
  if (EVP_DigestInit_ex(ctx, md, nullptr) != 1) {
    EVP_MD_CTX_free(ctx);
    throw std::runtime_error("EVP_DigestInit_ex failed");
  }

  std::vector<char> buf(1 << 20); // 1 MiB chunks
  while (f) {
    f.read(buf.data(), buf.size());
    std::streamsize n = f.gcount();
    if (n > 0 && EVP_DigestUpdate(ctx, buf.data(), static_cast<size_t>(n)) != 1) {
      EVP_MD_CTX_free(ctx);
      throw std::runtime_error("EVP_DigestUpdate failed");
    }
  }

  std::array<unsigned char, SHA256_DIGEST_LENGTH> out{};
  unsigned int outlen = 0;
  if (EVP_DigestFinal_ex(ctx, out.data(), &outlen) != 1) {
    EVP_MD_CTX_free(ctx);
    throw std::runtime_error("EVP_DigestFinal_ex failed");
  }
  EVP_MD_CTX_free(ctx);

  std::ostringstream oss;
  for (size_t i = 0; i < outlen; ++i)
    oss << std::hex << std::setw(2) << std::setfill('0') << (int)out[i];
  return oss.str();
}
