///|
/// The certificate chain and private key an HTTPS server presents (← uvicorn's `ssl_certfile` and
/// `ssl_keyfile`), as PEM files. The chain file holds the leaf first, then any intermediates; the
/// key file holds a `PRIVATE KEY`, `EC PRIVATE KEY` or `RSA PRIVATE KEY`. The same files serve on
/// every platform: the TLS is moontls, not the system's.
pub(all) struct TlsCert {
  certificate_file : String
  private_key_file : String
}

///|
/// The application protocols a TLS listener offers, in preference order: HTTP/2 first, as
/// hypercorn and nginx do, falling back to HTTP/1.1.
pub let alpn : Array[String] = ["h2", "http/1.1"]

///|
/// Read the files into a TLS server configuration. A key that is not the leaf's is refused here,
/// at startup, rather than by every client's handshake.
pub async fn TlsCert::load(
  self : TlsCert,
  alpn~ : Array[String] = alpn,
) -> @tls.Server {
  @tls.Server::new(
    @x509.pems(@fs.read_file(self.certificate_file).text()),
    @x509.PrivateKey::pem(@fs.read_file(self.private_key_file).text()),
    alpn~,
  )
}

///|
/// Serve a moonasgi ASGI application over native HTTP/1.1 **over TLS** (HTTPS,
/// ← uvicorn's `--ssl-certfile`/`--ssl-keyfile`). Convenience wrapper over
/// `serve_tls_config` that builds the `Config` from `host`/`port`/`backlog` and
/// the certificate paths.
///
/// Runs the full ASGI lifespan protocol around the accept loop (startup before
/// the listener binds, shutdown on exit — even under cancellation), then, for
/// every accepted connection, completes a TLS handshake and serves HTTP/2 when
/// ALPN settles on `h2`, HTTP/1.1 otherwise.
///
/// A WebSocket upgrade on an HTTP/1.1 connection is served too (`wss://`): the
/// 101, the subprotocol echo and the frames all travel over the encrypted stream.
pub async fn serve_tls(
  app : @moonasgi.AsgiApp,
  certificate_file~ : String,
  private_key_file~ : String,
  host~ : String = "127.0.0.1",
  port~ : Int32 = @config.port,
  backlog~ : Int32 = 2048,
) -> Unit {
  serve_tls_config(app, @config.Config::new(host~, port~, backlog~), {
    certificate_file,
    private_key_file,
  })
}

///|
/// Serve a moonasgi ASGI application over HTTPS under an explicit `Config` and
/// `TlsCert`. Mirrors `serve_config`: lifespan startup is driven before the
/// listener binds and shutdown on the way out (guarded by `protect_from_cancel`
/// so it still runs when the serving task is cancelled), and each accepted
/// connection completes a TLS handshake within `timeout_handshake` and then runs
/// the same loop as a plain one.
pub async fn serve_tls_config(
  app : @moonasgi.AsgiApp,
  config : @config.Config,
  cert : TlsCert,
) -> Unit {
  run(app, config, ShutdownHandle::new(), Some(cert.load()))
}

///|
/// Self-signed localhost material for the integration tests below, generated by
/// `scripts/gen-cert.sh`.
fn test_cert() -> TlsCert {
  { certificate_file: "certs/cert.pem", private_key_file: "certs/key.pem", }
}

///|
/// Real HTTPS round-trip: a genuine `@http` TLS client performs `GET /` over an
/// encrypted connection to a `serve_tls`-served app and observes `200` + the
/// exact body. The client is the system's TLS (OpenSSL or SChannel), so this is
/// moontls against another implementation, through the HTTP/1.1 codec to the app.
async test "https: real TLS client GET returns 200 with body" {
  @async.with_task_group(g => {
    let app : @moonasgi.AsgiApp = (_scope, _receive, send) => {
      send(
        @moonasgi.Event::HttpResponseStart(
          status=200,
          headers=[("content-type", "text/plain")],
          trailers=false,
        ),
      )
      send(
        @moonasgi.Event::HttpResponseBody(
          body=b"secure hello over tls",
          more_body=false,
        ),
      )
    }
    let task = g.spawn(() => {
      serve_tls_config(app, @config.Config::new(port=18443), test_cert())
    })
    @async.sleep(400)
    let client = @http.Client::Client(
      "https://127.0.0.1:18443",
      trust=NoVerification,
    )
    let resp = client.get("/")
    assert_eq(resp.code, 200)
    assert_eq(client.read_all().text(), "secure hello over tls")
    client.close()
    task.cancel()
  })
}