// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0

///|
fn Conn::on_server(
  self : Conn,
  config : Server,
  kind : @msg.Kind,
  body : Bytes,
  raw : Bytes,
) -> Unit raise @alert.Alert {
  match (self.phase, kind) {
    (Waiting, ClientHello) => self.on_client_hello(config, body, raw)
    (Finishing, Finished) => {
      let d = self.digest()
      if !@keys.finished_ok(
          self.client_secret[:],
          self.th()[:],
          body[:],
          digest=d,
        ) {
        raise @alert.fatal(DecryptError)
      }
      self.record(raw[:])
      self.reader = Some(
        @record.Cipher::new(self.client_app[:], suite=self.suite.unwrap()),
      )
      self.phase = Open
    }
    (Open, KeyUpdate) => self.on_update(body)
    _ => raise @alert.fatal(UnexpectedMessage)
  }
}

///|
fn Conn::on_client_hello(
  self : Conn,
  config : Server,
  body : Bytes,
  raw : Bytes,
) -> Unit raise @alert.Alert {
  guard @msg.read_hello(body[:]) is Some(hello) else {
    raise @alert.fatal(DecodeError)
  }
  let schemes = schemes(config.key)
  let choice = @hs.negotiate(hello, groups=config.groups[:], schemes=schemes[:])
  guard config.suites.iter().find_first(s => hello.suites.contains(s.code()))
    is Some(suite) else {
    raise @alert.fatal(HandshakeFailure)
  }
  if self.retried && self.suite != Some(suite) {
    raise @alert.fatal(IllegalParameter)
  }
  self.suite = Some(suite)
  match choice {
    Retry(group) => {
      if self.retried {
        raise @alert.fatal(IllegalParameter)
      }
      self.retried = true
      self.first_hello = raw
      let hrr = @msg.server_hello(
        @hs.retry(hello.session_id[:], suite.code(), group),
      )
      self.begin_transcript(Some(hrr))
      self.send_handshake(hrr)
    }
    Chosen(group~, key~, scheme~) => {
      if self.transcript is None {
        self.begin_transcript(None)
      }
      self.record(raw[:])
      if @ext.find(hello.extensions[:], ServerName) is Some(e) {
        self.sni = @ext.read_server_name(e.data[:])
      }
      let encrypted : Array[@ext.Ext] = []
      if @ext.find(hello.extensions[:], Alpn) is Some(e) &&
        config.alpn.length() > 0 {
        let wanted = @ext.read_protocols(e.data[:])
        guard config.alpn.iter().find_first(p => wanted.contains(p)) is Some(p) else {
          raise @alert.fatal(NoApplicationProtocol)
        }
        self.alpn = Some(p)
        encrypted.push({ kind: Alpn, data: @ext.protocols([p][:]), })
      }
      let (secret, public) = share(group, self.rand)
      let shared = agree(group, secret, key)
      let random = (self.rand)(32)
      let extensions : Array[@ext.Ext] = [
        { kind: KeyShare, data: @ext.selected_share(group, public[:]), },
      ]
      let sh = @msg.server_hello(
        @hs.server_hello(
          random[:],
          hello.session_id[:],
          suite.code(),
          extensions=extensions[:],
        ),
      )
      self.send_handshake(sh)
      self.record(sh[:])
      self.open_handshake(shared)
      self.writer = Some(@record.Cipher::new(self.server_secret[:], suite~))
      self.reader = Some(@record.Cipher::new(self.client_secret[:], suite~))
      let flight = Buffer()
      let ee = @msg.frame(
        EncryptedExtensions,
        @msg.encrypted_extensions(encrypted[:])[:],
      )
      self.record(ee[:])
      flight.write_bytes(ee)
      let cert = @msg.frame(Certificate, @msg.certificate(config.chain[:])[:])
      self.record(cert[:])
      flight.write_bytes(cert)
      let signature = sign(
        config.key,
        scheme,
        @msg.signed(@msg.server_context, self.th()[:]),
      )
      let cv = @msg.frame(
        CertificateVerify,
        @msg.certificate_verify(signature[:], scheme)[:],
      )
      self.record(cv[:])
      flight.write_bytes(cv)
      let fin = @msg.frame(
        Finished,
        @keys.finished(
          self.server_secret[:],
          self.th()[:],
          digest=self.digest(),
        )[:],
      )
      self.record(fin[:])
      flight.write_bytes(fin)
      self.send_handshake(flight.to_bytes())
      self.open_application()
      self.writer = Some(@record.Cipher::new(self.server_app[:], suite~))
      self.phase = Finishing
    }
  }
}