///|
/// Error type for handlers that want automatic HTTP error responses.
pub(all) suberror HttpError {
  HttpError(StatusCode, String)
} derive(Debug)

///|
fn wrap_error_handler(handler : async (Event) -> &Responder) -> HttpHandler {
  event => {
    handler(event) catch {
      HttpError(status, message) => HttpResponse::error(status, message)
      // JSON parse errors from derive(FromJson) or @json.parse
      @json.JsonDecodeError(_) as err =>
        HttpResponse::error(BadRequest, err.to_string())
      @json.InvalidEof as err =>
        HttpResponse::error(BadRequest, err.to_string())
      @json.InvalidChar(_) as err =>
        HttpResponse::error(BadRequest, err.to_string())
      @json.InvalidNumber(_) as err =>
        HttpResponse::error(BadRequest, err.to_string())
      @json.InvalidIdentEscape(_) as err =>
        HttpResponse::error(BadRequest, err.to_string())
      @json.DepthLimitExceeded as err =>
        HttpResponse::error(BadRequest, err.to_string())
      // UTF-8 decode failures — the client sent a body that isn't valid UTF-8
      @utf8.Malformed(_) => HttpResponse::error(BadRequest, "invalid UTF-8")
      // Unknown errors: 500 — log the real error, return a generic message
      err => {
        println("[crescent] unhandled error: \{err}")
        HttpResponse::error(InternalServerError, "Internal Server Error")
      }
    }
  }
}

///|
async test "wrap_error_handler passes through success" {
  let handler = wrap_error_handler(_ => "ok")
  let event = Event::{
    req: HttpRequest(Get, "/", {}, raw_body=b""),
    res: HttpResponse(status_code=OK),
    params: {},
  }
  let result = handler(event)
  let buf = Buffer()
  result.output(buf)
  assert_eq(buf.contents(), @utf8.encode("ok"))
}

///|
async test "wrap_error_handler catches HttpError" {
  let handler = wrap_error_handler(_event => {
    raise HttpError(BadRequest, "name is required")
  })
  let event = Event::{
    req: HttpRequest(Get, "/", {}, raw_body=b""),
    res: HttpResponse(status_code=OK),
    params: {},
  }
  let result = handler(event)
  result.options(event.res)
  assert_eq(event.res.status_code, BadRequest)
  let buf = Buffer()
  result.output(buf)
  let body = @utf8.decode(buf.contents())
  assert_true(body.contains("name is required"))
  assert_true(body.contains("400"))
}

///|
async test "wrap_error_handler catches JsonDecodeError as 400" {
  let handler = wrap_error_handler(event => {
    let _ : Json = event.req.json()
    "ok"
  })
  let event = Event::{
    req: HttpRequest(Post, "/", {}, raw_body=b"not valid json"),
    res: HttpResponse(status_code=OK),
    params: {},
  }
  let result = handler(event)
  result.options(event.res)
  assert_eq(event.res.status_code, BadRequest)
}

///|
async test "wrap_error_handler catches unknown error as 500" {
  let handler = wrap_error_handler(_event => raise NetworkError)
  let event = Event::{
    req: HttpRequest(Get, "/", {}, raw_body=b""),
    res: HttpResponse(status_code=OK),
    params: {},
  }
  let result = handler(event)
  result.options(event.res)
  assert_eq(event.res.status_code, InternalServerError)
}

///|
async test "wrap_error_handler catches Failure as 500 with generic message" {
  let handler = wrap_error_handler(_event => fail("secret db connection string"))
  let event = Event::{
    req: HttpRequest(Get, "/", {}, raw_body=b""),
    res: HttpResponse(status_code=OK),
    params: {},
  }
  let result = handler(event)
  result.options(event.res)
  assert_eq(event.res.status_code, InternalServerError)
  let buf = Buffer()
  result.output(buf)
  let body = @utf8.decode(buf.contents())
  // Must NOT leak internal error details to clients
  assert_true(body.contains("Internal Server Error"))
  assert_false(body.contains("secret db connection string"))
}

///|
async test "HttpError with NotFound status" {
  let handler = wrap_error_handler(_event => {
    raise HttpError(NotFound, "user not found")
  })
  let event = Event::{
    req: HttpRequest(Get, "/", {}, raw_body=b""),
    res: HttpResponse(status_code=OK),
    params: {},
  }
  let result = handler(event)
  result.options(event.res)
  assert_eq(event.res.status_code, NotFound)
}

///|
/// Registers a GET handler with automatic error-to-JSON mapping.
pub fn App::get(
  self : App,
  path : String,
  handler : async (Event) -> &Responder,
) -> Unit {
  self.get_raw(path, wrap_error_handler(handler))
}

///|
/// Registers a POST handler with automatic error-to-JSON mapping.
pub fn App::post(
  self : App,
  path : String,
  handler : async (Event) -> &Responder,
) -> Unit {
  self.post_raw(path, wrap_error_handler(handler))
}

///|
/// Registers a PUT handler with automatic error-to-JSON mapping.
pub fn App::put(
  self : App,
  path : String,
  handler : async (Event) -> &Responder,
) -> Unit {
  self.put_raw(path, wrap_error_handler(handler))
}

///|
/// Registers a PATCH handler with automatic error-to-JSON mapping.
pub fn App::patch(
  self : App,
  path : String,
  handler : async (Event) -> &Responder,
) -> Unit {
  self.patch_raw(path, wrap_error_handler(handler))
}

///|
/// Registers a DELETE handler with automatic error-to-JSON mapping.
pub fn App::delete(
  self : App,
  path : String,
  handler : async (Event) -> &Responder,
) -> Unit {
  self.delete_raw(path, wrap_error_handler(handler))
}

///|
/// Registers a HEAD handler with automatic error-to-JSON mapping.
pub fn App::head(
  self : App,
  path : String,
  handler : async (Event) -> &Responder,
) -> Unit {
  self.head_raw(path, wrap_error_handler(handler))
}

///|
/// Registers an OPTIONS handler with automatic error-to-JSON mapping.
pub fn App::options(
  self : App,
  path : String,
  handler : async (Event) -> &Responder,
) -> Unit {
  self.options_raw(path, wrap_error_handler(handler))
}

///|
/// Registers a TRACE handler with automatic error-to-JSON mapping.
pub fn App::trace(
  self : App,
  path : String,
  handler : async (Event) -> &Responder,
) -> Unit {
  self.trace_raw(path, wrap_error_handler(handler))
}

///|
/// Registers a CONNECT handler with automatic error-to-JSON mapping.
pub fn App::connect(
  self : App,
  path : String,
  handler : async (Event) -> &Responder,
) -> Unit {
  self.connect_raw(path, wrap_error_handler(handler))
}

///|
/// Registers a handler matching all HTTP methods with automatic error-to-JSON mapping.
pub fn App::all(
  self : App,
  path : String,
  handler : async (Event) -> &Responder,
) -> Unit {
  self.all_raw(path, wrap_error_handler(handler))
}