///|
/// Structured logging for mbit — powered by `moonbit-log` (`leppard/moonbit-log`).
///
/// ## Usage
///
/// ```
/// // Configure
/// Logger::set_level(Info)
/// Logger::set_format(JSONFormat)
///
/// // In middleware / handlers
/// Logger::debug("Processing request", fields=[("path", ctx.path())])
/// Logger::info("User logged in", fields=[("user_id", "42"), ("ip", ctx.client_ip())])
/// Logger::error("Database timeout", fields=[("query", "SELECT ...")])
///
/// // Structured request logging middleware
/// app.use(structured_logger(StructuredLogConfig::default()))
/// ```

///|
/// Logger — the central logging API for mbit.
///
/// All logging goes through `moonbit-log` (`leppard/moonbit-log`) and respects
/// configured log levels and output formats.
pub(all) struct Logger {}

///|
/// Log severity levels — maps to `@leppard/moonbit-log.Level`.
pub(all) enum LogLevel {
  Debug
  Info
  Warn
  Error
  Fatal
} derive(Debug, Eq, Hash, Compare)

///|
/// Log output format.
pub(all) enum LogFormat {
  /// Human-readable text output (default)
  TextFormat
  /// JSON lines (suitable for log aggregators like ELK, Datadog)
  JSONFormat
} derive(Debug, Eq)

///|
/// Convert LogLevel to string.
pub fn LogLevel::to_string(self : LogLevel) -> String {
  match self {
    Debug => "DEBUG"
    Info => "INFO"
    Warn => "WARN"
    Error => "ERROR"
    Fatal => "FATAL"
  }
}

///|
/// Convert our LogLevel to the underlying `@leppard/moonbit-log.Level`.
fn to_log_level(level : LogLevel) -> @leppard/moonbit-log.Level {
  match level {
    Debug => @leppard/moonbit-log.Level::Debug
    Info => @leppard/moonbit-log.Level::Info
    Warn => @leppard/moonbit-log.Level::Warn
    Error => @leppard/moonbit-log.Level::Error
    Fatal => @leppard/moonbit-log.Level::Fatal
  }
}

///|
/// Internal logger configuration state.
priv struct LoggerConfig {
  mut level : LogLevel
  mut _format : LogFormat
  mut app_name : String
  mut environment : String
}

///|
/// Default logger configuration.
let global_config : LoggerConfig = {
  level: Debug,
  _format: TextFormat,
  app_name: "mbit",
  environment: "development",
}

///| ——————————————————————————————————————————————————————————————————————
///  Configuration — Logger::set_* / Logger::level
///| ——————————————————————————————————————————————————————————————————————

///|
/// Set the minimum log level.
pub fn Logger::set_level(level : LogLevel) -> Unit {
  global_config.level = level
  let ml_level = to_log_level(level)
  let logger = @leppard/moonbit-log.get_global_logger()
  logger.set_level(ml_level)
}

///|
/// Get the current log level.
pub fn Logger::level() -> LogLevel {
  global_config.level
}

///|
/// Set the log output format.
/// - `TextFormat`: human-readable compact format
/// - `JSONFormat`: JSON lines format
pub fn Logger::set_format(format : LogFormat) -> Unit {
  global_config._format = format
  let ml_level = to_log_level(global_config.level)
  match format {
    TextFormat => {
      let handler = @leppard/moonbit-log.ConsoleHandler::new(ml_level, @leppard/moonbit-log.text_formatter())
      let hfn = @leppard/moonbit-log.make_handler(
        fn(e) { handler.log(e) },
        fn() {},
        fn() {},
      )
      let logger = @leppard/moonbit-log.get_global_logger()
      logger.clear_handlers()
      logger.add_handler(hfn)
    }
    JSONFormat => @leppard/moonbit-log.init_default_json()
  }
}

///|
/// Set the application name for log metadata.
/// Injected as a field into every log entry.
pub fn Logger::set_app_name(name : String) -> Unit {
  global_config.app_name = name
}

///|
/// Set the environment tag for log metadata.
/// Injected as a field ("env") into every log entry.
pub fn Logger::set_environment(env : String) -> Unit {
  global_config.environment = env
}

///|
/// Check if the given log level is enabled.
pub fn Logger::is_enabled(level : LogLevel) -> Bool {
  level >= global_config.level
}

///| ——————————————————————————————————————————————————————————————————————
///  Core logging methods — Logger::debug / info / warn / error / fatal
///| ——————————————————————————————————————————————————————————————————————

///|
/// Build the base fields array with app name and environment.
fn base_fields() -> Array[(String, String)] {
  let fields : Array[(String, String)] = []
  if global_config.app_name != "" {
    fields.push(("app", global_config.app_name))
  }
  if global_config.environment != "" {
    fields.push(("env", global_config.environment))
  }
  fields
}

///|
/// Log a debug message with optional key-value fields.
///
/// ```
/// Logger::debug("Cache miss", fields=[("key", "user:42")])
/// ```
pub fn Logger::debug(message : String, fields~ : Array[(String, String)] = []) -> Unit {
  if !Logger::is_enabled(Debug) { return }
  let all_fields = base_fields()
  for f in fields { all_fields.push(f) }
  if all_fields.length() > 0 {
    @leppard/moonbit-log.g_log_with(@leppard/moonbit-log.Level::Debug, message, all_fields)
  } else {
    @leppard/moonbit-log.g_debug(message)
  }
}

///|
/// Log an info message with optional key-value fields.
pub fn Logger::info(message : String, fields~ : Array[(String, String)] = []) -> Unit {
  if !Logger::is_enabled(Info) { return }
  let all_fields = base_fields()
  for f in fields { all_fields.push(f) }
  if all_fields.length() > 0 {
    @leppard/moonbit-log.g_log_with(@leppard/moonbit-log.Level::Info, message, all_fields)
  } else {
    @leppard/moonbit-log.g_info(message)
  }
}

///|
/// Log a warning message with optional key-value fields.
pub fn Logger::warn(message : String, fields~ : Array[(String, String)] = []) -> Unit {
  if !Logger::is_enabled(Warn) { return }
  let all_fields = base_fields()
  for f in fields { all_fields.push(f) }
  if all_fields.length() > 0 {
    @leppard/moonbit-log.g_log_with(@leppard/moonbit-log.Level::Warn, message, all_fields)
  } else {
    @leppard/moonbit-log.g_warn(message)
  }
}

///|
/// Log an error message with optional key-value fields.
pub fn Logger::error(message : String, fields~ : Array[(String, String)] = []) -> Unit {
  if !Logger::is_enabled(Error) { return }
  let all_fields = base_fields()
  for f in fields { all_fields.push(f) }
  if all_fields.length() > 0 {
    @leppard/moonbit-log.g_log_with(@leppard/moonbit-log.Level::Error, message, all_fields)
  } else {
    @leppard/moonbit-log.g_error(message)
  }
}

///|
/// Log a fatal message and terminate.
pub fn Logger::fatal(message : String, fields~ : Array[(String, String)] = []) -> Unit {
  let all_fields = base_fields()
  for f in fields { all_fields.push(f) }
  if all_fields.length() > 0 {
    @leppard/moonbit-log.g_log_with(@leppard/moonbit-log.Level::Fatal, message, all_fields)
  } else {
    @leppard/moonbit-log.g_fatal(message)
  }
  abort(message)
}

///| ——————————————————————————————————————————————————————————————————————
///  Structured request logging middleware
///| ——————————————————————————————————————————————————————————————————————

///|
/// Configuration for the structured request logger middleware.
pub(all) struct StructuredLogConfig {
  /// Log level for request logs
  level : LogLevel
  /// Whether to log the request body
  log_request_body : Bool
  /// Whether to log the response body
  log_response_body : Bool
  /// Maximum body length to log (in bytes, 0 = unlimited)
  max_body_log_length : Int
  /// Whether to skip logging for specific paths
  skip_paths : Array[String]
  /// Whether to include latency in logs
  include_latency : Bool
  /// Additional static fields to include in every request log
  static_fields : Array[(String, String)]
}

///|
/// Default structured log config.
pub fn StructuredLogConfig::default() -> StructuredLogConfig {
  {
    level: Info,
    log_request_body: false,
    log_response_body: false,
    max_body_log_length: 1024,
    skip_paths: ["/health", "/metrics"],
    include_latency: true,
    static_fields: [],
  }
}

///|
/// Structured request logging middleware — powered by moonbit-log.
///
/// Logs each request with structured fields:
/// - method, path, status, latency
/// - client_ip, user_agent, content_type
/// - request_id (if set in context)
///
/// ```
/// app.use(structured_logger(StructuredLogConfig::default()))
/// ```
pub fn structured_logger(config : StructuredLogConfig) -> Handler {
  async fn(ctx) {
    // Check skip paths
    let path = ctx.path()
    for skip in config.skip_paths {
      if path == skip {
        ctx.next()
        return
      }
    }

    let start = @env.now()
    let method = ctx.http_method()
    let client_ip = ctx.client_ip()
    let user_agent = match ctx.header("User-Agent") {
      Some(ua) => ua
      None => ""
    }
    let content_type = ctx.content_type()

    // Run the handler chain
    ctx.next()

    let elapsed = @env.now() - start
    let status = ctx.status_code

    // Build structured fields
    let fields : Array[(String, String)] = [
      ("method", method),
      ("path", path),
      ("status", status.to_string()),
      ("client_ip", client_ip),
    ]

    if user_agent != "" {
      fields.push(("user_agent", user_agent))
    }
    if content_type != "" {
      fields.push(("content_type", content_type))
    }
    if config.include_latency {
      fields.push(("latency_ms", elapsed.to_string()))
    }

    // Add request_id if available
    match ctx.get_string("request_id") {
      Some(id) => fields.push(("request_id", id))
      None => ()
    }

    // Add static fields
    for sf in config.static_fields {
      let (k, v) = sf
      fields.push((k, v))
    }

    // Add app name and environment
    if global_config.app_name != "" {
      fields.push(("app", global_config.app_name))
    }
    if global_config.environment != "" {
      fields.push(("env", global_config.environment))
    }

    // Build message and log via Logger API
    let msg = method + " " + path + " -> " + status.to_string()

    if status >= 500 {
      Logger::error(msg, fields=fields)
    } else if status >= 400 {
      Logger::warn(msg, fields=fields)
    } else {
      Logger::info(msg, fields=fields)
    }
  }
}

///| ——————————————————————————————————————————————————————————————————————
///  Metrics collector (basic)
///| ——————————————————————————————————————————————————————————————————————

///|
/// A simple in-memory metrics collector for request counts and latencies.
pub(all) struct MetricsCollector {
  /// Total requests by status code
  status_counts : Map[Int, Int64]
  /// Per-path request count
  path_counts : Map[String, Int64]
  /// Total request count
  mut total_requests : Int64
  /// Accumulated latency in ms
  mut total_latency_ms : Int64
  /// Min latency in ms
  mut min_latency_ms : Int64
  /// Max latency in ms
  mut max_latency_ms : Int64
}

///|
/// Create a new metrics collector.
pub fn MetricsCollector::new() -> MetricsCollector {
  {
    status_counts: Map([]),
    path_counts: Map([]),
    total_requests: 0L,
    total_latency_ms: 0L,
    min_latency_ms: 0L,
    max_latency_ms: 0L,
  }
}

///|
/// Record a request in the metrics collector.
pub fn MetricsCollector::record(
  self : MetricsCollector,
  status : Int,
  path : String,
  latency_ms : Int64,
) -> Unit {
  self.total_requests = self.total_requests + 1L
  self.total_latency_ms = self.total_latency_ms + latency_ms

  // Update status counts
  match self.status_counts.get(status) {
    Some(count) => self.status_counts.set(status, count + 1L)
    None => self.status_counts.set(status, 1L)
  }

  // Update path counts
  match self.path_counts.get(path) {
    Some(count) => self.path_counts.set(path, count + 1L)
    None => self.path_counts.set(path, 1L)
  }

  // Update min/max
  if self.min_latency_ms == 0L || latency_ms < self.min_latency_ms {
    self.min_latency_ms = latency_ms
  }
  if latency_ms > self.max_latency_ms {
    self.max_latency_ms = latency_ms
  }
}

///|
/// Export metrics as JSON for a `/metrics` endpoint.
pub fn MetricsCollector::to_json(self : MetricsCollector) -> Json {
  let status_map : Map[String, Json] = Map([])
  for code, count in self.status_counts {
    status_map.set(code.to_string(), Json::number(count.to_double()))
  }

  let top_paths : Map[String, Json] = Map([])
  for p, c in self.path_counts {
    top_paths.set(p, Json::number(c.to_double()))
  }

  Json::object({
    "total_requests": Json::number(self.total_requests.to_double()),
    "total_latency_ms": Json::number(self.total_latency_ms.to_double()),
    "min_latency_ms": Json::number(self.min_latency_ms.to_double()),
    "max_latency_ms": Json::number(self.max_latency_ms.to_double()),
    "avg_latency_ms": Json::number(
      if self.total_requests > 0L {
        (self.total_latency_ms / self.total_requests).to_double()
      } else {
        0.0
      },
    ),
    "status_codes": Json::object(status_map),
    "top_paths": Json::object(top_paths),
  })
}

///|
/// Metrics middleware — records request metrics into the provided collector.
///
/// ```
/// let metrics = MetricsCollector::new()
/// app.use(metrics_middleware(metrics))
/// app.get("/metrics", [fn(ctx) { ctx.json(200, metrics.to_json()) }])
/// ```
pub fn metrics_middleware(collector : MetricsCollector) -> Handler {
  async fn(ctx) {
    let start = @env.now()
    ctx.next()
    let elapsed = @env.now() - start
    collector.record(ctx.status_code, ctx.path(), elapsed.reinterpret_as_int64())
  }
}