///|
/// Native HTTP server for vcdb using moonbitlang/async/http.

///|
struct ServerOptions {
  mut host : String
  mut port : Int
  mut storage_path : String
  mut in_memory : Bool
  mut help : Bool
  mut error : String?
}

///|
fn ServerOptions::default() -> ServerOptions {
  {
    host: "127.0.0.1",
    port: 6333,
    storage_path: ".local-storage",
    in_memory: false,
    help: false,
    error: None,
  }
}

///|
fn parse_args(args : Array[String]) -> ServerOptions {
  let opts = ServerOptions::default()
  let mut i = 0
  while i < args.length() {
    let arg = args[i]
    if arg == "--help" || arg == "-h" {
      opts.help = true
      return opts
    } else if arg == "--storage" && i + 1 < args.length() {
      opts.storage_path = args[i + 1]
      i = i + 1
    } else if arg == "--in-memory" {
      opts.in_memory = true
    } else if arg == "--host" && i + 1 < args.length() {
      opts.host = args[i + 1]
      i = i + 1
    } else if arg == "--port" && i + 1 < args.length() {
      let port_arg = args[i + 1]
      let parsed = @string.parse_int(port_arg) catch {
        _ => {
          opts.error = Some("Invalid port: " + port_arg)
          return opts
        }
      }
      opts.port = parsed
      i = i + 1
    }
    i = i + 1
  }
  opts
}

///|
fn print_help() -> Unit {
  println(
    "vcdb native HTTP server\n\nUsage: moon run cmd/native-serve -- [options]\n\nOptions:\n  --host       Host to bind (default: 127.0.0.1)\n  --port          Port to bind (default: 6333)\n  --storage    Storage root (default: .local-storage)\n  --in-memory        Disable filesystem persistence\n  -h, --help         Show this help message",
  )
}

///|
fn command_args() -> Array[String] {
  let argv = @env.args()
  if argv.length() <= 1 {
    []
  } else {
    let args : Array[String] = []
    for i in 1.. String {
  match request_method {
    @async_http.RequestMethod::Get => "GET"
    @async_http.RequestMethod::Post => "POST"
    @async_http.RequestMethod::Put => "PUT"
    @async_http.RequestMethod::Delete => "DELETE"
    @async_http.RequestMethod::Options => "OPTIONS"
    _ => "GET"
  }
}

///|
async fn send_http_response(
  conn : @async_http.ServerConnection,
  response : @http.HttpResponse,
) -> Unit {
  conn.send_response(response.status, "OK", extra_headers=response.headers)
  conn.write(response.body)
  conn.end_response()
}

///|
async fn handle_preflight(conn : @async_http.ServerConnection) -> Unit {
  let response = @http.add_cors_headers(@http.HttpResponse::new(204, ""))
  conn.send_response(204, "No Content", extra_headers=response.headers)
  conn.end_response()
}

///|
async fn run_server(options : ServerOptions) -> Unit {
  if options.in_memory {
    run_server_with_router(
      options,
      @http.Router::with_storage(@storage.MemoryStorage::new()),
    )
  } else {
    run_server_with_router(
      options,
      @http.Router::with_storage(
        @storage.NativeFileStorage::new(options.storage_path),
      ),
    )
  }
}

///|
async fn[S : @storage.AsyncStorage] run_server_with_router(
  options : ServerOptions,
  router : @http.Router[S],
) -> Unit {
  let addr_str = options.host + ":" + options.port.to_string()
  let addr = @async_socket.Addr::parse(addr_str) catch {
    _ => {
      println("Failed to parse address: \{addr_str}")
      return
    }
  }
  let server = @async_http.Server::new(addr, reuse_addr=true) catch {
    e => {
      println("Failed to start server: \{e}")
      return
    }
  }
  println(
    "vcdb native gateway listening on http://\{options.host}:\{options.port}",
  )
  if options.in_memory {
    println("storage: in-memory")
  } else {
    println("storage: \{options.storage_path}")
  }
  server.run_forever((req, body_reader, conn) => {
    let http_method = method_name(req.meth)
    if http_method == "OPTIONS" {
      handle_preflight(conn)
      return
    }
    let body_data = @async_io.Reader::read_all(body_reader) catch {
      _ => {
        conn.send_response(400, "Bad Request")
        conn.write("{\"error\":\"failed to read body\"}")
        conn.end_response()
        return
      }
    }
    let body_text = body_data.text() catch {
      _ => {
        conn.send_response(400, "Bad Request")
        conn.write("{\"error\":\"request body is not valid UTF-8\"}")
        conn.end_response()
        return
      }
    }
    let response = @http.add_cors_headers(
      router.route(
        @http.HttpRequest::new(http_method, req.path, body=body_text),
      ),
    )
    send_http_response(conn, response)
  })
}

///|
async fn main {
  let options = parse_args(command_args())
  match options.error {
    Some(err) => {
      println(err)
      return
    }
    None => ()
  }
  if options.help {
    print_help()
    return
  }
  run_server(options)
}