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

///|
/// An alert's level (RFC 8446 §6).
///
/// TLS 1.3 acts on the description rather than the level: only `CloseNotify`
/// and `UserCanceled` leave a connection usable, whichever level carries them.
/// The level is still on the wire, so it is still read and written.
pub(all) enum Level {
  Warning
  Fatal
} derive(Eq, Debug)

///|
pub extend Level with Eq::{equal, not_equal}

///|
pub extend Level with Debug::{to_repr}

///|
/// The octet this level goes on the wire as.
pub fn Level::code(self : Level) -> Int {
  match self {
    Warning => 1
    Fatal => 2
  }
}

///|
/// The level an octet names, or `None` for one RFC 8446 does not assign.
pub fn Level::of(code : Int) -> Level? {
  match code {
    1 => Some(Warning)
    2 => Some(Fatal)
    _ => None
  }
}

///|
/// What an endpoint says went wrong (RFC 8446 §6.1, §6.2).
///
/// The description is the only diagnostic a peer ever receives, so it is what
/// makes a failed handshake readable from the other end — a connection that
/// simply goes away tells the client nothing.
///
/// `Other` carries a code the RFC does not assign. An unrecognised description
/// still arrives, and dropping the number would lose the only thing it said.
pub(all) enum Kind {
  /// The sender is done writing — a clean shutdown, not a failure.
  CloseNotify
  /// A message arrived that the state machine was not waiting for, or out of order.
  UnexpectedMessage
  /// A record failed to decrypt or to authenticate.
  BadRecordMac
  /// A record's plaintext ran past the 2^14-octet limit.
  RecordOverflow
  /// No parameters both endpoints support — no shared group, suite or signature scheme.
  HandshakeFailure
  /// A certificate was corrupt, or carried a signature that did not verify.
  BadCertificate
  /// The certificate was of a type the receiver does not support.
  UnsupportedCertificate
  /// The signer revoked the certificate.
  CertificateRevoked
  /// The certificate has expired, or is not yet valid.
  CertificateExpired
  /// Some other problem with the certificate, with no more precise description.
  CertificateUnknown
  /// A field was well-formed on its own but contradicts the rest of the message.
  IllegalParameter
  /// The chain verified but ended at no trusted anchor.
  UnknownCa
  /// The certificate verified, but the receiver declines to proceed with this peer.
  AccessDenied
  /// A message could not be parsed: a length overran, or a field was out of range.
  DecodeError
  /// A handshake signature or a Finished MAC failed to verify.
  DecryptError
  /// The peer offered no protocol version this endpoint supports.
  ProtocolVersion
  /// handshake_failure, narrowed: the peer's parameters are weaker than accepted.
  InsufficientSecurity
  /// A local failure, unrelated to the peer or to the protocol.
  InternalError
  /// The peer fell back to an older version for an unacceptable reason.
  InappropriateFallback
  /// The handshake is being abandoned for a reason outside the protocol.
  UserCanceled
  /// A message lacked an extension RFC 8446 §9.2 makes mandatory for it.
  MissingExtension
  /// A server sent back an extension the ClientHello never offered.
  UnsupportedExtension
  /// No server exists for the name `server_name` asked for.
  UnrecognizedName
  /// The stapled OCSP response was invalid.
  BadCertificateStatusResponse
  /// The PSK identity offered is unknown and no full handshake will be run.
  UnknownPskIdentity
  /// The client sent no certificate where the server requires one.
  CertificateRequired
  /// None of the ALPN protocols offered is one this server speaks (RFC 7301 §3.2).
  NoApplicationProtocol
  /// A description RFC 8446 does not assign, carried through as its number.
  Other(Int)
} derive(Eq, Debug)

///|
pub extend Kind with Eq::{equal, not_equal}

///|
pub extend Kind with Debug::{to_repr}

///|
/// The octet this description goes on the wire as.
pub fn Kind::code(self : Kind) -> Int {
  match self {
    CloseNotify => 0
    UnexpectedMessage => 10
    BadRecordMac => 20
    RecordOverflow => 22
    HandshakeFailure => 40
    BadCertificate => 42
    UnsupportedCertificate => 43
    CertificateRevoked => 44
    CertificateExpired => 45
    CertificateUnknown => 46
    IllegalParameter => 47
    UnknownCa => 48
    AccessDenied => 49
    DecodeError => 50
    DecryptError => 51
    ProtocolVersion => 70
    InsufficientSecurity => 71
    InternalError => 80
    InappropriateFallback => 86
    UserCanceled => 90
    MissingExtension => 109
    UnsupportedExtension => 110
    UnrecognizedName => 112
    BadCertificateStatusResponse => 113
    UnknownPskIdentity => 115
    CertificateRequired => 116
    NoApplicationProtocol => 120
    Other(code) => code
  }
}

///|
/// The description an octet names. Never fails: one the RFC does not assign
/// becomes `Other`.
pub fn Kind::of(code : Int) -> Kind {
  match code {
    0 => CloseNotify
    10 => UnexpectedMessage
    20 => BadRecordMac
    22 => RecordOverflow
    40 => HandshakeFailure
    42 => BadCertificate
    43 => UnsupportedCertificate
    44 => CertificateRevoked
    45 => CertificateExpired
    46 => CertificateUnknown
    47 => IllegalParameter
    48 => UnknownCa
    49 => AccessDenied
    50 => DecodeError
    51 => DecryptError
    70 => ProtocolVersion
    71 => InsufficientSecurity
    80 => InternalError
    86 => InappropriateFallback
    90 => UserCanceled
    109 => MissingExtension
    110 => UnsupportedExtension
    112 => UnrecognizedName
    113 => BadCertificateStatusResponse
    115 => UnknownPskIdentity
    116 => CertificateRequired
    120 => NoApplicationProtocol
    other => Other(other)
  }
}

///|
/// Whether this description ends the connection (RFC 8446 §6.1).
///
/// Everything but `CloseNotify` and `UserCanceled`, whatever level the sender
/// put on it — the level is advisory and the description is not.
pub fn Kind::is_fatal(self : Kind) -> Bool {
  self != CloseNotify && self != UserCanceled
}

///|
/// An alert (RFC 8446 §6).
///
/// It is also the error a handshake raises, so one value carries both the
/// reason a caller branches on and the two octets to put on the wire.
pub(all) suberror Alert {
  Alert(level~ : Level, kind~ : Kind)
} derive(Eq, Debug)

///|
pub extend Alert with Eq::{equal, not_equal}

///|
pub extend Alert with Debug::{to_repr}

///|
/// A fatal alert carrying `kind` — every way a TLS 1.3 handshake can fail.
pub fn fatal(kind : Kind) -> Alert {
  Alert(level=Fatal, kind~)
}

///|
/// A warning alert carrying `kind`: `CloseNotify` and `UserCanceled`, the two a
/// peer may act on without tearing the connection down.
pub fn warning(kind : Kind) -> Alert {
  Alert(level=Warning, kind~)
}

///|
/// The two octets an alert goes on the wire as (RFC 8446 §6).
pub fn encode(alert : Alert) -> Bytes {
  let Alert(level~, kind~) = alert
  let out = Buffer()
  out.write_byte((level.code() & 0xff).to_byte())
  out.write_byte((kind.code() & 0xff).to_byte())
  out.to_bytes()
}

///|
/// Read an alert back off the wire, or `None` if fewer than two octets are
/// there.
///
/// A level the RFC does not assign is refused rather than guessed at; an
/// unassigned description is carried through as [`Kind::Other`], because the
/// description is the diagnostic and losing it loses the message.
pub fn decode(view : BytesView) -> Alert? {
  if view.length() < 2 {
    return None
  }
  match Level::of(view[0].to_int()) {
    Some(level) => Some(Alert(level~, kind=Kind::of(view[1].to_int())))
    None => None
  }
}