//
//  native-loger.h
//  humand
//
//  Created by Oleksandr Shumihin on 01/04/2026.
//  Copyright © 2026 Humand. All rights reserved.
//

#pragma once

#include "jsi/jsi.h"
#include <array>
#include <charconv>
#include <chrono>
#include <cstring>
#include <fcntl.h>
#include <filesystem>
#include <optional>
#include <string>
#include <string_view>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/statvfs.h>
#include <sys/types.h>
#include <unistd.h>
#include <vector>

namespace humand::native_logger {
using namespace facebook;

// ts(13) + ':'(1) + ulid(26) + ':'(1) + status(1) + '\n'(1)
constexpr size_t LINE_SIZE = 43; // 43 byte
constexpr size_t TS_SIZE = 13;
constexpr uint64_t FREE_SPACE_RESERVE_BYTES = 1 * 1024 * 1024;
constexpr size_t PENDING_RESERVE_SIZE = 150;

struct Meta {
  size_t cursor;
  size_t max_lines;
};

class FileLogger : public jsi::NativeState {
public:
  explicit FileLogger() = default;

  ~FileLogger() override { close_file(); }

  bool init(const std::string &log_file_path, size_t requested_bytes) {
    create_dir_if_needed(log_file_path);

    // close just in case
    close_file();
    cursor_ = 0;
    max_lines_ = 0;

    fd_ = ::open(log_file_path.c_str(), O_CREAT | O_RDWR, 0644);

    if (fd_ < 0) [[unlikely]] {
      return false;
    }

    std::optional<size_t> reserved_size =
        reserve_file(log_file_path, requested_bytes);

    if (!reserved_size.has_value()) [[unlikely]] {
      close_file();
      return false;
    }

    max_lines_ = reserved_size.value() / LINE_SIZE;

    if (max_lines_ == 0) [[unlikely]] {
      close_file();
      return false;
    }

    size_t found_cursor = find_next_index();
    cursor_ = found_cursor % max_lines_;

    mapped_size_ = max_lines_ * LINE_SIZE;

    void *mapped = ::mmap(nullptr, mapped_size_, PROT_READ | PROT_WRITE,
                          MAP_SHARED, fd_, 0);

    if (mapped == MAP_FAILED) [[unlikely]]  {
      mapped_ = nullptr;
      mapped_size_ = 0;
      close_file();
      return false;
    }

    pending_.reserve(PENDING_RESERVE_SIZE);

    mapped_ = static_cast<char *>(mapped);
    return true;
  }

  bool append(const std::string_view ulid, const int status) {
    std::array<char, LINE_SIZE> line;

    const int64_t ts = next_monotonic_ts();

    if (!make_line(line.data(), ts, ulid, status)) [[unlikely]] {
      return false;
    }

    if (is_buffering_) {
      pending_.emplace_back(line);
      return true;
    }

    return write_log_mmap(line.data());
  }

  void clear() { clear_mmap(); }

  bool is_empty() { return !has_at_least_one_line(); }

  void start_buffering() { is_buffering_ = true; }

  void stop_buffering_and_flush() {
    for (const auto &line : pending_) {
      write_log_mmap(line.data());
    }

    pending_.clear();
    is_buffering_ = false;
  }

  Meta get_meta() { return Meta{.cursor = cursor_, .max_lines = max_lines_}; }

private:
  // file descriptor for the log file
  int fd_ = -1;
  // points to the next line to write in the circular buffer
  size_t cursor_ = 0;
  // total number of lines the file can hold (file size / LINE_SIZE)
  size_t max_lines_ = 0;
  // last emitted timestamp to ensure monotonicity
  int64_t last_emitted_ts_ = 0;

  char *mapped_ = nullptr;
  size_t mapped_size_ = 0;
  bool is_buffering_ = false;

  std::vector<std::array<char, LINE_SIZE>> pending_;

  bool write_log_mmap(const char *line) noexcept {
    if (mapped_ == nullptr || max_lines_ == 0) [[unlikely]] {
      return false;
    }

    const size_t offset = cursor_ * LINE_SIZE;
    std::memcpy(mapped_ + offset, line, LINE_SIZE);
    cursor_ = (cursor_ + 1) % max_lines_;

    return true;
  }

  bool make_line(char *out, const int64_t ts, const std::string_view value,
                 const int action) noexcept {
    if (out == nullptr) [[unlikely]] {
      return false;
    }

    if (value.size() != 26) [[unlikely]] {
      return false;
    }

    if (action < 0 || action > 9) [[unlikely]] {
      return false;
    }

    char *ptr = out;

    const auto [p, ec] = std::to_chars(ptr, out + LINE_SIZE, ts);

    if (ec != std::errc{}) [[unlikely]] {
      return false;
    }

    if ((p - out) != TS_SIZE) [[unlikely]] {
      return false;
    }

    ptr = p;
    *ptr++ = ':';

    std::memcpy(ptr, value.data(), value.size());
    ptr += value.size();

    *ptr++ = ':';
    *ptr++ = static_cast<char>('0' + action);
    *ptr++ = '\n';

    return static_cast<size_t>(ptr - out) == LINE_SIZE;
  }

  std::optional<size_t> reserve_file(const std::string &path,
                                     const size_t requested_bytes) noexcept {
    namespace fs = std::filesystem;

    const size_t requested_aligned = (requested_bytes / LINE_SIZE) * LINE_SIZE;

    if (requested_aligned == 0) {
      return std::nullopt;
    }

    struct stat st{};
    if (::fstat(fd_, &st) != 0) {
      return std::nullopt;
    }

    const auto current_size = static_cast<size_t>(st.st_size);

    // Align file size to LINE_SIZE if needed
    if (current_size > 0 && current_size % LINE_SIZE != 0) {
      const size_t aligned_size = (current_size / LINE_SIZE) * LINE_SIZE;

      if (::ftruncate(fd_, static_cast<off_t>(aligned_size)) != 0) {
        return std::nullopt;
      }

      return aligned_size;
    }

    // Needed size
    if (current_size == requested_aligned) {
      return requested_aligned;
    }

    // File is already big enough, no need to grow
    if (current_size > requested_aligned) {
      return current_size;
    }

    const fs::path file_path{path};
    const std::string dir_path = file_path.parent_path().string();

    struct statvfs stat_fs{};
    if (::statvfs(dir_path.c_str(), &stat_fs) != 0) {
      return std::nullopt;
    }

    const uint64_t free_bytes =
        static_cast<uint64_t>(stat_fs.f_bavail) * stat_fs.f_frsize;

    if (free_bytes <= FREE_SPACE_RESERVE_BYTES + LINE_SIZE) {
      return current_size > 0 ? std::optional<size_t>(current_size)
                              : std::nullopt;
    }

    const uint64_t available_bytes = free_bytes - FREE_SPACE_RESERVE_BYTES;
    const size_t bytes_to_grow = requested_aligned - current_size;

    // Not enough space to increase the file to the requested size
    if (available_bytes < bytes_to_grow) {
      return current_size > 0 ? std::optional<size_t>(current_size)
                              : std::nullopt;
    }

    if (::ftruncate(fd_, static_cast<off_t>(requested_aligned)) != 0) {
      return current_size > 0 ? std::optional<size_t>(current_size)
                              : std::nullopt;
    }

    return requested_aligned;
  }

  // Find the latest ts and the next cursor.
  // Maps the whole file read-only: binary search operates on raw pointers into
  // mapped memory — no pread, no intermediate buffers, no copies.
  // The OS brings in only the pages actually touched (demand paging).
  size_t find_next_index() const noexcept {
    if (max_lines_ == 0) {
      return 0;
    }

    const size_t file_size = max_lines_ * LINE_SIZE;
    void *mapped = ::mmap(nullptr, file_size, PROT_READ, MAP_PRIVATE, fd_, 0);
    if (mapped == MAP_FAILED) {
      return 0;
    }

    // RAII: munmap fires on every return path, including early exits.
    struct Guard {
      void *ptr;
      size_t sz;
      ~Guard() noexcept { ::munmap(ptr, sz); }
    } guard{mapped, file_size};

    const char *const base = static_cast<const char *>(mapped);

    // Direct pointer into mapped memory — zero copies, no syscall in the loop.
    auto ts_at = [&](size_t idx) -> const char * {
      return base + idx * LINE_SIZE;
    };

    auto is_digit_prefix = [](const char *buf) -> bool {
      for (size_t i = 0; i < TS_SIZE; ++i) {
        if (buf[i] < '0' || buf[i] > '9') {
          return false;
        }
      }
      return true;
    };

    const char *const first_ts = ts_at(0);
    const char *const last_ts = ts_at(max_lines_ - 1);

    if (!is_digit_prefix(first_ts)) {
      return 0;
    }

    // Case 1: file partially filled, tail is empty
    if (!is_digit_prefix(last_ts)) {
      size_t low = 0;
      size_t high = max_lines_; // [low, high)

      while (low < high) {
        const size_t mid = low + (high - low) / 2;

        if (!is_digit_prefix(ts_at(mid))) {
          high = mid;
        } else {
          low = mid + 1;
        }
      }

      return low % max_lines_;
    }

    // Case 2: wrapped ring, find first smaller-than-first block
    if (std::memcmp(first_ts, last_ts, TS_SIZE) > 0) {
      size_t low = 0;
      size_t high = max_lines_; // [low, high)

      while (low < high) {
        const size_t mid = low + (high - low) / 2;

        if (std::memcmp(ts_at(mid), first_ts, TS_SIZE) >= 0) {
          low = mid + 1;
        } else {
          high = mid;
        }
      }

      return low % max_lines_;
    }

    // Case 3: full file, no wrap yet -> next write goes to 0
    return 0;
  }

  void close_file() noexcept {
    if (mapped_ != nullptr) {
      ::msync(mapped_, mapped_size_, MS_SYNC);
      ::munmap(mapped_, mapped_size_);
      mapped_ = nullptr;
      mapped_size_ = 0;
    }

    if (fd_ >= 0) {
      ::close(fd_);
      fd_ = -1;
    }
  }

  // Generate a monotonic timestamp in ms
  // needed to be monotonic for search the latest log to recover the cursor
  int64_t next_monotonic_ts() noexcept {
    using namespace std::chrono;

    const int64_t now =
        duration_cast<milliseconds>(system_clock::now().time_since_epoch())
            .count();

    if (now > last_emitted_ts_) {
      last_emitted_ts_ = now;
    } else {
      last_emitted_ts_ += 1;
    }

    return last_emitted_ts_;
  }

  void create_dir_if_needed(std::string_view path) noexcept {
    try {
      namespace fs = std::filesystem;
      const fs::path dir = fs::path(path).parent_path();

      if (dir.empty()) {
        return;
      }

      std::error_code ec;
      fs::create_directories(dir, ec);
    } catch (...) {
    }
  }

  // Write zeros to the file
  bool clear_mmap() {
    if (mapped_ == nullptr || mapped_size_ == 0) {
      return false;
    }

    std::memset(mapped_, 0, mapped_size_);
    const bool sync_ok = ::msync(mapped_, mapped_size_, MS_SYNC) == 0;
    cursor_ = 0;

    return sync_ok;
  }

  // check first 4 bytes of the first line for zero to determine if the file has
  // at least one line of logs
  bool has_at_least_one_line() const noexcept {
    if (mapped_ == nullptr || max_lines_ == 0) {
      return false;
    }

    uint32_t value = 0;
    std::memcpy(&value, mapped_, sizeof(value));
    const uint32_t has_zero_byte =
        (value - 0x01010101u) & (~value) & 0x80808080u;

    return has_zero_byte == 0;
  }
};
} // namespace humand::native_logger
