#pragma once

#include <optional>
#include <span>
#include <string_view>
#include <vector>

#include "arena_allocator.hpp"

/**
 * @brief Represents a precondition for optimistic concurrency control.
 *
 * Preconditions allow transactions to verify that the data they read
 * during transaction preparation is still valid at commit time. This
 * enables optimistic concurrency control by detecting conflicting
 * modifications from other transactions.
 */
struct Precondition {
  /**
   * @brief Type of precondition check to perform.
   */
  enum class Type {
    PointRead, ///< Check existence/content of a single key
    RangeRead  ///< Check consistency of a key range
  };

  Type type; ///< Type of precondition check
  int64_t
      version; ///< Expected version number (0 uses read_version from request)
  std::string_view begin; ///< Begin key (or single key for PointRead)
  std::string_view end;   ///< End key for RangeRead (unused for PointRead)
};

/**
 * @brief Represents a mutation operation in a commit request.
 *
 * Operations define the actual changes to be applied to the database
 * if all preconditions pass. Operations are applied in the order they
 * appear in the commit request.
 */
struct Operation {
  /**
   * @brief Type of mutation operation to perform.
   */
  enum class Type {
    Write,      ///< Set a key-value pair
    Delete,     ///< Remove a single key
    RangeDelete ///< Remove all keys in a range
  };

  Type type;               ///< Type of operation
  std::string_view param1; ///< Key for Write/Delete, begin key for RangeDelete
  std::string_view
      param2; ///< Value for Write, end key for RangeDelete (unused for Delete)
};

/**
 * @brief Format-agnostic commit request data structure.
 *
 * All string data is stored in the arena allocator to ensure efficient
 * memory management and ownership. This class has no knowledge of any
 * specific serialization formats or encoding schemes.
 */
struct CommitRequest {
private:
  ArenaAllocator arena_;
  std::optional<std::string_view> request_id_;
  std::string_view leader_id_;
  int64_t read_version_ = 0;
  std::vector<Precondition, ArenaStlAllocator<Precondition>> preconditions_;
  std::vector<Operation, ArenaStlAllocator<Operation>> operations_;

public:
  /**
   * @brief Construct a new CommitRequest with default arena size.
   */
  explicit CommitRequest()
      : arena_(), preconditions_(ArenaStlAllocator<Precondition>(&arena_)),
        operations_(ArenaStlAllocator<Operation>(&arena_)) {}

  // Move constructor
  CommitRequest(CommitRequest &&other) noexcept
      : arena_(std::move(other.arena_)), request_id_(other.request_id_),
        leader_id_(other.leader_id_), read_version_(other.read_version_),
        preconditions_(std::move(other.preconditions_)),
        operations_(std::move(other.operations_)) {}

  // Move assignment operator
  CommitRequest &operator=(CommitRequest &&other) noexcept {
    if (this != &other) {
      arena_ = std::move(other.arena_);
      request_id_ = other.request_id_;
      leader_id_ = other.leader_id_;
      read_version_ = other.read_version_;
      preconditions_ = std::move(other.preconditions_);
      operations_ = std::move(other.operations_);
    }
    return *this;
  }

  // Copy constructor and assignment are deleted
  CommitRequest(const CommitRequest &) = delete;
  CommitRequest &operator=(const CommitRequest &) = delete;

  /**
   * @brief Get the request ID if present.
   * @return Optional request ID
   */
  const std::optional<std::string_view> &request_id() const {
    return request_id_;
  }

  /**
   * @brief Get the leader ID.
   * @return Leader ID string view
   */
  std::string_view leader_id() const { return leader_id_; }

  /**
   * @brief Get the read version.
   * @return Read version number
   */
  int64_t read_version() const { return read_version_; }

  /**
   * @brief Get the preconditions.
   * @return span of preconditions
   */
  std::span<const Precondition> preconditions() const { return preconditions_; }

  /**
   * @brief Get the operations.
   * @return span of operations
   */
  std::span<const Operation> operations() const { return operations_; }

  /**
   * @brief Get the total allocated bytes in the arena.
   * @return Total allocated bytes
   */
  size_t total_allocated() const { return arena_.total_allocated(); }

  /**
   * @brief Get the used bytes in the arena.
   * @return Used bytes
   */
  size_t used_bytes() const { return arena_.used_bytes(); }

  /**
   * @brief Get access to the underlying arena allocator for debugging.
   *
   * @note This function is primarily used for testing and debugging.
   * Production code should prefer the specific accessor methods like
   * total_allocated() and used_bytes() instead of direct arena access.
   *
   * @return Reference to the arena allocator
   */
  const ArenaAllocator &arena() const { return arena_; }

  /**
   * @brief Get access to the underlying arena allocator for allocation.
   *
   * @note This function exposes the internal arena for direct manipulation.
   * It should be used carefully and primarily for internal parser
   * implementation or testing purposes.
   *
   * @return Reference to the arena allocator
   */
  ArenaAllocator &arena() { return arena_; }

  /**
   * @brief Reset the commit request for reuse.
   */
  void reset() {
    arena_.reset();
    request_id_.reset();
    leader_id_ = {};
    read_version_ = 0;
    preconditions_.clear();
    operations_.clear();
  }

  // Builder methods for setting data
  // Note: All string_view parameters must point to arena-allocated memory

  /**
   * @brief Set the optional request ID for this commit.
   * @param arena_allocated_request_id String view pointing to arena-allocated
   * memory
   */
  void set_request_id(std::string_view arena_allocated_request_id) {
    request_id_ = arena_allocated_request_id;
  }

  /**
   * @brief Set the leader ID for consistency checks.
   * @param arena_allocated_leader_id String view pointing to arena-allocated
   * memory
   */
  void set_leader_id(std::string_view arena_allocated_leader_id) {
    leader_id_ = arena_allocated_leader_id;
  }

  /**
   * @brief Set the read version for precondition validation.
   * @param read_version The snapshot version number
   */
  void set_read_version(int64_t read_version) { read_version_ = read_version; }

  /**
   * @brief Add a precondition to the commit request.
   * @param type Type of precondition (PointRead or RangeRead)
   * @param version Version number for the precondition check
   * @param arena_allocated_begin Begin key (or single key for PointRead)
   * @param arena_allocated_end End key for RangeRead (optional, empty for
   * PointRead)
   */
  void add_precondition(Precondition::Type type, int64_t version,
                        std::string_view arena_allocated_begin,
                        std::string_view arena_allocated_end = {}) {
    preconditions_.push_back(Precondition{type, version, arena_allocated_begin,
                                          arena_allocated_end});
  }

  /**
   * @brief Add an operation to the commit request.
   * @param type Type of operation (Write, Delete, or RangeDelete)
   * @param arena_allocated_param1 Key for Write/Delete, begin key for
   * RangeDelete
   * @param arena_allocated_param2 Value for Write, end key for RangeDelete
   * (optional for Delete)
   */
  void add_operation(Operation::Type type,
                     std::string_view arena_allocated_param1,
                     std::string_view arena_allocated_param2 = {}) {
    operations_.push_back(
        Operation{type, arena_allocated_param1, arena_allocated_param2});
  }

  /**
   * @brief Copy a string into the arena and return a string_view.
   * Helper utility for external code that needs to copy data into arena memory.
   * @param str The string to copy
   * @return String view pointing to arena-allocated memory
   */
  std::string_view copy_to_arena(std::string_view str) {
    if (str.empty()) {
      return {};
    }

    char *arena_str = arena_.allocate<char>(str.size());
    std::memcpy(arena_str, str.data(), str.size());

    return std::string_view(arena_str, str.size());
  }

  /**
   * @brief Apply any post-processing logic after data has been populated.
   * This should be called after all data has been added to the request.
   */
  void finalize() {
    // Fill in default read version for preconditions that don't specify one
    for (auto &precondition : preconditions_) {
      if (precondition.version == 0) {
        precondition.version = read_version_;
      }
    }
  }
};
