///|
/// Base64-encode a string (used for AUTH LOGIN/PLAIN payloads).
pub fn base64_encode_str(s : String) -> String {
  @base64.std_encode2str(string_to_bytes(s).to_fixedarray())
}

///|
/// Base64-decode a string.
pub fn base64_decode_str(s : String) -> String raise MailFailure {
  @base64.std_decode2str(s) catch {
    _ => raise MailFailure::encode("invalid base64 in AUTH exchange")
  }
}

///|
/// RFC 4616 PLAIN credentials: `\0username\0password`, base64 encoded.
pub fn auth_plain_credentials(username : String, password : String) -> String {
  base64_encode_str("\u{0000}\{username}\u{0000}\{password}")
}

///|
/// MD5 digest of `data` as raw bytes (built on `gmlewis/md5`).
fn md5_bytes(data : Bytes) -> Bytes raise MailFailure {
  let d = @md5.Digest::new()
  for b in data {
    d.write(b)
  }
  hex_to_bytes(d.check_sum())
}

///|
/// HMAC-MD5 (RFC 2104) with a 64-byte block, built on the MD5 dependency.
/// Returns the digest as a lowercase hex string (MM_AUTH_001).
pub fn hmac_md5(key : Bytes, msg : Bytes) -> String raise MailFailure {
  let mut k = key
  if k.length() > 64 {
    k = md5_bytes(k)
  }
  let padded = FixedArray::make(64, b'\x00')
  for i = 0; i < k.length(); i = i + 1 {
    padded[i] = k[i]
  }
  let ipad = FixedArray::make(64, b'\x00')
  let opad = FixedArray::make(64, b'\x00')
  for i = 0; i < 64; i = i + 1 {
    ipad[i] = (padded[i].to_int() ^ 0x36).to_byte()
    opad[i] = (padded[i].to_int() ^ 0x5c).to_byte()
  }
  let inner = md5_bytes(Bytes::from_iter(ipad.iter()) + msg)
  let outer = md5_bytes(Bytes::from_iter(opad.iter()) + inner)
  bytes_to_hex(outer)
}

///|
/// Build the CRAM-MD5 client response: `username `.
/// `challenge_b64` is the text after `334 ` in the server challenge.
pub fn cram_md5_response(
  username : String,
  password : String,
  challenge_b64 : String,
) -> String raise MailFailure {
  let challenge = base64_decode_str(challenge_b64)
  let digest = hmac_md5(string_to_bytes(password), string_to_bytes(challenge))
  "\{username} \{digest}"
}

///|
/// A CRAM-MD5 test vector for documentation purposes (RFC 2195 example values
/// come from the `smtplib` documentation).
pub const CRAM_MD5_VECTOR_CHALLENGE : String = "PDc1MDU2MzAxMDM2OTk0NDY3MDU1Pj5NSTcxNDcyNDU3OTQ3NTk4MDc0OTQ0NjkxPj4="