///|
/// mbit production-validation "shadow" app.
///
/// Modes (via env `MBIT_BENCH_MODE`):
///   - "plain": minimal stack (recovery only) — raw framework throughput.
///   - "obs"  : full observability stack (request_id, structured_logger,
///              metrics, secure, body_size_limit, recovery) — the default.
///
/// Routes:
///   GET  /                 plain text
///   GET  /json             JSON object response
///   GET  /hello/:name      path parameter
///   GET  /query            query parameter
///   POST /echo             JSON body echo
///   GET  /api/users/:id    path-param JSON
///   POST /upload           multipart file upload (returns size)
///   POST /upload-small     upload capped at 64 KB (body_size_limit -> 413)
///   GET  /panic            aborts — recovered as 500
///   GET  /slow             busy-waits ~400 ms (graceful-shutdown test)
///   GET  /api/limited      rate-limited (20 req / 10 s)
///   GET  /metrics          Prometheus metrics (obs mode only)

async fn h_json(ctx : @core.Context) -> Unit {
  ctx.json(200, Json::object(@core.h([("msg", Json::string("ok")), ("n", Json::number(42.0))])))
}

async fn h_text(ctx : @core.Context) -> Unit {
  ctx.string(200, "hello")
}

async fn h_hello(ctx : @core.Context) -> Unit {
  ctx.string(200, "Hello, " + ctx.param_default("name", "?") + "!")
}

async fn h_query(ctx : @core.Context) -> Unit {
  ctx.string(200, "q=" + ctx.query_default("q", ""))
}

async fn h_echo(ctx : @core.Context) -> Unit {
  match ctx.body_json() {
    Some(j) => ctx.json(200, j)
    None => ctx.json(400, Json::object(@core.h([("error", Json::string("bad json"))])))
  }
}

async fn h_user(ctx : @core.Context) -> Unit {
  ctx.json(200, Json::object(@core.h([("id", Json::string(ctx.param_default("id", "")))])))
}

async fn h_upload(ctx : @core.Context) -> Unit {
  match ctx.form_file("file") {
    Some(c) => ctx.json(200, Json::object(@core.h([("size", Json::number(c.length().to_double()))])))
    None => ctx.json(400, Json::object(@core.h([("error", Json::string("no file"))])))
  }
}

async fn h_panic(ctx : @core.Context) -> Unit {
  // NOTE: `raise(Failure(...))` is a catchable exception that `recovery()`
  // handles (returns 500). `abort("...")` is a HARD panic that terminates the
  // whole process and CANNOT be recovered by try/catch — see report.
  raise(Failure("boom"))
}

async fn h_slow(ctx : @core.Context) -> Unit {
  let end = @env.now() + 400UL
  while @env.now() < end {
    ()
  }
  ctx.string(200, "slow done")
}

async fn h_limited(ctx : @core.Context) -> Unit {
  ctx.string(200, "limited ok")
}

async fn h_shutdown(ctx : @core.Context) -> Unit {
  let app = @core.new()
  let ok = app.shutdown(cleanup=fn() { println("[bench] cleanup called") })
  ctx.string(200, "shutdown returned " + ok.to_string())
}

async fn h_404(ctx : @core.Context) -> Unit {
  ctx.json(404, Json::object(@core.h([("error", Json::string("not found"))])))
}

async fn main {
  let mode = match @env.get_env_var("MBIT_BENCH_MODE") {
    Some(m) => m
    None => "obs"
  }
  let app = @core.new()
  // Observability middleware go OUTSIDE recovery (matching Gin): so that
  // after an inner panic is recovered to 500, metrics/loggers see the final
  // status and their post-`next()` bookkeeping still runs.
  if mode != "plain" {
    app.use(@middleware.request_id()) |> ignore
    app.use(@middleware.structured_logger()) |> ignore
    app.use(@middleware.metrics()) |> ignore
  }
  app.use(@core.recovery()) |> ignore
  if mode != "plain" {
    app.use(@middleware.secure(@middleware.SecureConfig::default())) |> ignore
    app.use(@middleware.body_size_limit(64L * 1024L * 1024L)) |> ignore
  }
  app.get("/", [h_text]) |> ignore
  app.get("/json", [h_json]) |> ignore
  app.get("/hello/:name", [h_hello]) |> ignore
  app.get("/query", [h_query]) |> ignore
  app.post("/echo", [h_echo]) |> ignore
  app.get("/api/users/:id", [h_user]) |> ignore
  app.post("/upload", [h_upload]) |> ignore
  app.post("/upload-small", [
    @middleware.body_size_limit(64L * 1024L),
    h_upload,
  ]) |> ignore
  app.get("/panic", [h_panic]) |> ignore
  app.get("/slow", [h_slow]) |> ignore
  app.get("/api/limited", [
    @middleware.rate_limit(@middleware.RateLimitConfig::{
      max_requests: 20,
      window_secs: 10,
      strategy: @middleware.FixedWindow,
      token_bucket: { rate: 20.0, burst: 20 },
      key_fn: fn(ctx) { ctx.client_ip() },
      condition: fn(_) { true },
      include_headers: true,
    }),
    h_limited,
  ]) |> ignore
  app.get("/shutdown", [h_shutdown]) |> ignore
  app.no_route([h_404]) |> ignore
  app.run("127.0.0.1:18081")
}