///|
/// A structured access-log record (← go-zero's `logx` HTTP access fields). Rather
/// than a free-form line, each request is captured as typed fields and rendered
/// as one JSON object per line — the format go-zero emits under `logx` and the
/// shape log collectors (ELK, Loki) expect.
pub(all) struct RequestLog {
  http_method : String
  path : String
  status : Int
  duration_ms : Int64
  request_id : String
  client_ip : String
  user_agent : String
}

///|
/// The record as a JSON value with a stable field order, `duration_ms` rendered
/// as a number of milliseconds.
pub fn RequestLog::to_json(self : RequestLog) -> Json {
  Json::object(
    Map([
      ("method", Json::string(self.http_method)),
      ("path", Json::string(self.path)),
      ("status", Json::number(self.status.to_double())),
      ("duration_ms", Json::number(self.duration_ms.to_double())),
      ("request_id", Json::string(self.request_id)),
      ("client_ip", Json::string(self.client_ip)),
      ("user_agent", Json::string(self.user_agent)),
    ]),
  )
}

///|
/// The record rendered as a single-line JSON string, ready to write to a log
/// sink.
pub fn RequestLog::render(self : RequestLog) -> String {
  self.to_json().stringify()
}

///|
/// The client IP from an HTTP scope: the `X-Forwarded-For` first hop if present
/// (proxy-aware, as go-zero's `httpx.GetRemoteAddr`), else the transport peer
/// from the scope's `client`, else `"-"`.
fn client_ip_of(scope : @moonasgi.Scope) -> String {
  match scope {
    Http(hs) => {
      for pair in hs.headers {
        if pair.0.to_lower() == "x-forwarded-for" {
          let raw = pair.1
          let mut end = 0
          while end < raw.length() && raw[end] != ',' {
            end = end + 1
          }
          return raw[0:end].to_owned()
        }
      }
      match hs.client {
        Some((ip, _)) => ip
        None => "-"
      }
    }
    _ => "-"
  }
}

///|
/// A request header's value from an HTTP scope, matched case-insensitively;
/// `default` when absent or on a non-HTTP scope.
fn header_ci(
  scope : @moonasgi.Scope,
  name : String,
  default : String,
) -> String {
  match scope {
    Http(hs) => {
      for pair in hs.headers {
        if pair.0.to_lower() == name {
          return pair.1
        }
      }
      default
    }
    _ => default
  }
}

///|
/// Structured-logging middleware (← go-zero's `LogHandler`): time each HTTP
/// request on the shared clock, capture its method/path/client-ip/user-agent and
/// the response status observed on `HttpResponseStart`, and print one
/// `RequestLog` JSON line when the response starts. Non-HTTP scopes pass through
/// without logging.
pub fn structured_logging(clock : Clock) -> Middleware {
  inner => {
    (scope, receive, send) => {
      match scope {
        Http(hs) => {
          let start = clock.now()
          let verb = hs.http_method
          let path = hs.path
          let ip = client_ip_of(scope)
          let ua = header_ci(scope, "user-agent", "-")
          let rid = header_ci(scope, "x-request-id", "-")
          let logged : Ref[Bool] = { val: false }
          let observed : @moonasgi.Send = event => {
            match event {
              HttpResponseStart(status~, headers~, trailers~) => {
                if !logged.val {
                  logged.val = true
                  let entry = RequestLog::{
                    http_method: verb,
                    path,
                    status,
                    duration_ms: clock.now() - start,
                    request_id: rid,
                    client_ip: ip,
                    user_agent: ua,
                  }
                  println(entry.render())
                }
                send(
                  @moonasgi.Event::HttpResponseStart(
                    status~,
                    headers~,
                    trailers~,
                  ),
                )
              }
              other => send(other)
            }
          }
          inner(scope, receive, observed)
        }
        _ => inner(scope, receive, send)
      }
    }
  }
}