///|
/// The `Bearer ` scheme prefix an `Authorization` header carries a JWT under.
let bearer_prefix : String = "Bearer "

///|
/// Extract the raw token from an `Authorization: Bearer ` header value,
/// or `None` if the value is absent or not a bearer credential. The scheme name
/// is matched case-insensitively (RFC 7235 makes it case-insensitive), the token
/// verbatim.
fn strip_bearer(value : String?) -> String? {
  match value {
    Some(v) =>
      if v.length() >= bearer_prefix.length() &&
        v[0:bearer_prefix.length()].to_owned().to_lower() ==
        bearer_prefix.to_lower() {
        Some(v[bearer_prefix.length():].to_owned())
      } else {
        None
      }
    None => None
  }
}

///|
/// The event stream an unauthenticated request receives: a `401 Unauthorized`
/// with a `WWW-Authenticate: Bearer` challenge and a short plain-text body. A
/// pure value so the guard's rejection is testable without the async transport.
fn unauthorized_events() -> Array[@moonasgi.Event] {
  [
    @moonasgi.Event::HttpResponseStart(
      status=401,
      headers=[
        ("content-type", "text/plain; charset=utf-8"),
        ("www-authenticate", "Bearer"),
      ],
      trailers=false,
    ),
    @moonasgi.Event::HttpResponseBody(body=b"401 Unauthorized", more_body=false),
  ]
}

///|
/// Whether a request bearing `token` is authorised at `now_secs`: the token
/// verifies against `secret` under HS256 and is neither expired nor
/// not-yet-valid. A missing token is unauthorised. Exposed as a pure decision so
/// the middleware's accept/reject is testable without driving the transport.
pub fn jwt_authorized(
  token : String?,
  secret : String,
  now_secs : Int64,
) -> Bool {
  match token {
    Some(t) =>
      try {
        let _ = jwt_verify(t, secret, now_secs)
        true
      } catch {
        _ => false
      }
    None => false
  }
}

///|
/// JWT auth middleware (← go-zero's `handler.Authorize`): require every HTTP
/// request to carry a valid `Authorization: Bearer ` header. The token is
/// verified against `secret` under HS256 at the current time read from `clock`
/// (milliseconds, converted to the JWT seconds epoch); an absent, malformed,
/// tampered, expired, or not-yet-valid token is rejected with `401 Unauthorized`
/// before the wrapped app runs. Non-HTTP scopes (lifespan, websocket) pass
/// through untouched.
pub fn auth(secret : String, clock : Clock) -> Middleware {
  inner => {
    (scope, receive, send) => {
      match scope {
        Http(_) => {
          let token = strip_bearer(scope_header(scope, "authorization"))
          let now_secs = clock.now() / 1000L
          if jwt_authorized(token, secret, now_secs) {
            inner(scope, receive, send)
          } else {
            for event in unauthorized_events() {
              send(event)
            }
          }
        }
        _ => inner(scope, receive, send)
      }
    }
  }
}