/*
 * Copyright (c) 2025, valkey-search contributors
 * All rights reserved.
 * SPDX-License-Identifier: BSD 3-Clause
 *
 */

#ifndef VMSDK_SRC_UTILS_INFO_H_
#define VMSDK_SRC_UTILS_INFO_H_

#include <atomic>
#include <optional>

#include "absl/strings/string_view.h"
#include "command_parser.h"
#include "module.h"
#include "vmsdk/src/valkey_module_api/valkey_module.h"

/*

Info Metrics

All of the fields that are reported by the "INFO" command are automatically
generated by the machinery in the "vmsdk::info_field" namespace.

To create an info field you instantiate one of the objects defined in this file
and then increment it at appropriate places in your code. The infrastructure
handles the rest of the work for you automatically. Normally, info fields are
instantiated at file scope, this is considered best practice. It's also possible
to dynamically instantiate info fields during module load. Once module load is
completed, no more info fields can be instantiated -- this preserves crash
integrity of the info subsystem.

The Valkey core invokes the info machinery in two different situations. In the
first situation you see it as part of the INFO command. This invocation happens
on the Main Thread.

If a crash occurs, then the info machinery is invoked as part of the crash dump
procedure. This invocation can happen in any thread context at any time --
meaning that info fields that depend on locking a mutex, allocating memory or
examining a complex data structure that might be in the process of being mutated
must not be part of a crash dump. The designation of crash-safe info fields is
done through a flag. The absence of that flag in the definition of the field
will mean that it won't be part of a crash dump.

The infrastructure requires that all info fields be designated as either
Application or Developer fields. The primary difference is that the OSS
Community pledges to maintain Application info fields compatibly between
releases. Whereas Developer info fields explicitly do not have this pledge and
are subject to change from release to release without notice. Best practices for
application developers is to only rely on Application info fields. There is a
CONFIG variable in the infrastructure to enable/disable the visibility of
Developer info fields. When this is at its default setting, there is no external
visibility for Developer info fields. Thus applications which are developed
around unmodified OSS code (or a pre-built binary) can safely use all of the
info fields that appear.

Generally, there is one info class for each data type: Number and String are
currently supported (Enum is possible).

The "Builder" pattern is adopted to construct info field classes.

*/

namespace vmsdk {
namespace info_field {

enum Flags {
  //
  // All Info fields must have exactly one of kApplication or kDeveloper
  // specified or
  // you'll get an assert on Module load.
  //
  kApplication = 1,  // Cross-release maintained
  kDeveloper = 2,    // For Developers, not cross-release maintained
  //
  // By default, fields aren't considered crash-safe, set this flag to indicate
  // that they are
  //
  kCrashSafe = 4,  // This field is Crash Safe
  //
  // For Numeric, this flag indicates that the value is displayed as an "SI
  // Byte" value.
  //
  kSIBytes = 8,
  //
  // Numeric Metrics are generally either "Sampling" or "Cumulative". Default is
  // Cumulative
  //
  kSampling = 16,
};

enum Units {
  kNone,

  kCount,
  kCountPerSecond,
  kPercent,

  kSeconds,
  kMilliSeconds,
  kMicroSeconds,

  kBytes,

  kBytesPerSecond,
  kKiloBytesPerSecond,
  kMegaBytesPerSecond,
  kGigaBytesPerSecond,
};

class Base {
 protected:
  std::string section_;
  std::string name_;
  Flags flags_;
  Units units_;

  virtual ~Base();
  Base(absl::string_view section, absl::string_view name, Flags flags,
       Units units);
  Base(const Base&) = delete;
  Base(const Base&&) = delete;

 public:
  const std::string& GetSection() const { return section_; }
  const std::string& GetName() const { return name_; }
  Flags GetFlags() const { return flags_; }
  Units GetUnits() const { return units_; }
  virtual void Dump(ValkeyModuleInfoCtx* ctx) const = 0;
  virtual void Reply(ValkeyModuleCtx* ctx) const = 0;
  virtual bool IsVisible() const = 0;
  bool IsCrashSafe() const { return (flags_ & Flags::kCrashSafe) != 0; }
  bool IsApplication() const { return (flags_ & Flags::kApplication) != 0; }
  bool IsDeveloper() const { return (flags_ & Flags::kDeveloper) != 0; }
  bool IsSampling() const { return (flags_ & Flags::kSampling) != 0; }
  bool IsCumulative() const { return !IsSampling(); }
};

template <typename T>
class Numeric;
template <typename T>
struct NumericBuilder;
using IntegerBuilder = NumericBuilder<long long>;
using Integer = Numeric<long long>;

using FloatBuilder = NumericBuilder<double>;
using Float = Numeric<double>;

//
// Builder for Numeric when the defaults aren't desirable.
//
template <typename T>
struct NumericBuilder {
  NumericBuilder() = default;

  // Mark this Application Visible
  NumericBuilder& App() {
    flags_ = Flags((flags_ & ~(Flags::kApplication | Flags::kDeveloper)) |
                   Flags::kApplication);
    return *this;
  }

  // Mark as Developer Visible
  NumericBuilder& Dev() {
    flags_ = Flags((flags_ & ~(Flags::kApplication | Flags::kDeveloper)) |
                   Flags::kDeveloper);
    return *this;
  }

  // Mark as a Bytes value, i.e., Display in Human Readable Units
  NumericBuilder& SIBytes() {
    flags_ = Flags(flags_ | Flags::kSIBytes);
    assert(flags_ & Flags::kSIBytes);
    return *this;
  }

  NumericBuilder& Units(Units units) {
    units_ = units;
    return *this;
  }

  // Used to make this field controllably visible. Note: By default, this is
  // marked not Crash Safe.
  NumericBuilder& VisibleIf(std::function<bool()> visible_func) {
    visible_func_ = visible_func;
    flags_ = Flags(flags_ & ~Flags::kCrashSafe);
    return *this;
  }

  // Used when the value must be computed at run-time. Note: By Default, this is
  // marked as not Crash Safe.
  NumericBuilder& Computed(std::function<T()> compute_func) {
    compute_func_ = compute_func;
    flags_ = Flags(flags_ & ~Flags::kCrashSafe);
    return *this;
  }

  // Mark as Sampling
  NumericBuilder& Sampling() {
    flags_ = Flags(flags_ | Flags::kSampling);
    return *this;
  }

  // Mark as Cumulative
  NumericBuilder& Cumulative() {
    flags_ = Flags(flags_ & ~Flags::kSampling);
    return *this;
  }

  //
  // DANGER:  Only use this function if you're certain that your callbacks are
  // crash safe. To be crash safe you must not:
  // 1. Access any locks.
  // 2. Allocate or Free any memory (this means strings too!)
  // 3. Access a complex data structure that gets mutated by any thread.
  //
  NumericBuilder& CrashSafe() {
    flags_ = Flags(flags_ | Flags::kCrashSafe);
    return *this;
  }

 private:
  friend class Numeric<T>;
  enum Flags flags_ { Flags::kCrashSafe };
  enum Units units_ { Units::kCount };
  std::optional<std::function<bool()>> visible_func_;
  std::optional<std::function<T()>> compute_func_;
};

//
// All numeric fields.
//
// This class provides an std::atomic<long long> for your use. But this is
// ignored if the "Computed" callback is used.
//
template <typename T>
class Numeric : private Base {
 public:
  long long Increment(T amount = 1) {
    return value_.fetch_add(amount, std::memory_order_relaxed);
  }
  long long Decrement(T amount = 1) {
    return value_.fetch_sub(amount, std::memory_order_relaxed);
  }
  long long Set(T value = 0) {
    return value_.exchange(value, std::memory_order_relaxed);
  }
  T Get() const { return value_.load(std::memory_order_relaxed); }
  Numeric(absl::string_view section, absl::string_view name,
          NumericBuilder<T> builder = NumericBuilder<T>());

 private:
  friend struct NumericBuilder<T>;
  void Dump(ValkeyModuleInfoCtx* ctx) const final;
  void Reply(ValkeyModuleCtx* ctx) const final;
  bool IsVisible() const final;
  std::atomic<long long> value_{0};
  std::optional<std::function<bool()>> visible_func_;
  std::optional<std::function<T()>> compute_func_;
};

//
// Builder for all String Valued Fields
//
// IN PROGRESS
//
struct StringBuilder {
  // Mark as Application Visible
  StringBuilder& App() {
    flags_ = Flags((flags_ & ~(Flags::kApplication | Flags::kDeveloper)) |
                   Flags::kApplication);
    return *this;
  }

  // Mark as Developer Visible
  StringBuilder& Dev() {
    flags_ = Flags((flags_ & ~(Flags::kApplication | Flags::kDeveloper)) |
                   Flags::kDeveloper);
    return *this;
  }

  // If visible optionally. Note: this field will not be visible in crash dumps
  StringBuilder& VisibleIf(std::function<bool()> visible_func) {
    visible_func_ = visible_func;
    flags_ = Flags(flags_ & ~Flags::kCrashSafe);
    return *this;
  }

  // Computed Value. Cannot be crash safe with std::string return value.
  StringBuilder& ComputedString(std::function<std::string()> compute_func) {
    compute_string_func_ = compute_func;
    flags_ = Flags(flags_ & ~Flags::kCrashSafe);
    return *this;
  }

  // Computed Value. This function is assumed to be crash safe.
  StringBuilder& ComputedCharPtr(std::function<const char*()> compute_func) {
    compute_char_func_ = compute_func;
    return *this;
  }
  //
  // DANGER:  Only use this function if you're certain that your callbacks are
  // crash safe. To be crash safe you must not:
  // 1. Access any locks.
  // 2. Allocate or Free any memory (this means strings too!)
  // 3. Access a complex data structure that gets mutated by any thread.
  //
  StringBuilder& CrashSafe() {
    flags_ = Flags(flags_ | Flags::kCrashSafe);
    return *this;
  }

 private:
  friend class String;
  Flags flags_{kDeveloper};
  Units units_{kNone};
  std::optional<std::function<bool()>> visible_func_;
  std::optional<std::function<const char*()>> compute_char_func_;
  std::optional<std::function<std::string()>> compute_string_func_;
};

//
// All String Valued Fields
//
class String : private Base {
 public:
  String(absl::string_view section, absl::string_view name,
         StringBuilder builder);

 private:
  friend struct StringBuilder;
  void Dump(ValkeyModuleInfoCtx* ctx) const final;
  void Reply(ValkeyModuleCtx* ctx) const final;
  bool IsVisible() const final;
  std::optional<std::function<bool()>> visible_func_;
  std::optional<std::function<const char*()>> compute_char_func_;
  std::optional<std::function<std::string()>> compute_string_func_;
};

//
// Validate configuration, Called on load to avoid difficult to diagnose asserts
// on module load
//
// false-> failure with reason written to the log.
//
bool Validate(ValkeyModuleCtx* ctx);

//
// Info Function Interface.
//
void DoSection(ValkeyModuleInfoCtx* ctx, absl::string_view section,
               int for_crash_report);
void DoSections(ValkeyModuleInfoCtx* ctx, int for_crash_report);
absl::Status ShowInfo(ValkeyModuleCtx* ctx, vmsdk::ArgsIterator& itr,
                      const vmsdk::module::Options& options);
// Get all fields filtered by APP or DEV
absl::Status ListMetrics(ValkeyModuleCtx* ctx, bool show_app, bool show_dev,
                         bool names_only = false);

//
// Some often used declarations
//
#define DEV_INTEGER_COUNTER(section, name) \
  static vmsdk::info_field::Integer name(  \
      #section, #name, vmsdk::info_field::IntegerBuilder().Dev())

bool GetShowDeveloper();

}  // namespace info_field
}  // namespace vmsdk

#endif