///|
fn cli_command() -> @argparse.Command {
  Command(
    "send-qq-email",
    about="Send a test email through QQ SMTP over TLS.",
    version="0.1.0",
    arg_required_else_help=true,
    options=[
      OptionArg("sender", about="QQ sender address", required=true),
      OptionArg("recipient", about="Recipient address", required=true),
      OptionArg("auth-code", about="QQ SMTP authorization code", required=true),
      OptionArg("subject", about="Message subject", default_values=[
        "MoonBit QQ SMTP test",
      ]),
      OptionArg("body", about="Plain text body", default_values=[
        "This message was sent by moonbit-community/email cmd/main.",
      ]),
      OptionArg(
        "attachment",
        about="Attachment file path, can be repeated",
        action=Append,
      ),
    ],
  )
}

///|
fn option_value(matches : @argparse.Matches, name : String) -> String {
  matches.values.get(name).unwrap_or([])[0]
}

///|
fn option_values(matches : @argparse.Matches, name : String) -> Array[String] {
  matches.values.get(name).unwrap_or([])
}

///|
fn mailbox_or_panic(label : String, address : String) -> @core.Mailbox {
  match @core.Mailbox::address_only(address) {
    Ok(mailbox) => mailbox
    Err(_) => {
      println("Invalid \{label} address: \{address}")
      panic()
    }
  }
}

///|
fn envelope_or_panic(
  sender : @core.Mailbox,
  recipient : @core.Mailbox,
) -> @core.Envelope {
  match @core.Envelope::new(sender, [recipient]) {
    Ok(envelope) => envelope
    Err(_) => {
      println("Failed to build SMTP envelope")
      panic()
    }
  }
}

///|
fn attachment_filename(path : String) -> String {
  let slash = path.rev_find("/")
  let backslash = path.rev_find("\\")
  let start = match (slash, backslash) {
    (Some(left), Some(right)) => if left > right { left + 1 } else { right + 1 }
    (Some(index), None) => index + 1
    (None, Some(index)) => index + 1
    (None, None) => 0
  }
  path[start:].to_owned()
}

///|
fn attachment_part_or_panic(path : String) -> @core.Body {
  let filename = attachment_filename(path)
  if filename == "" {
    println("Attachment path has no filename: \{path}")
    panic()
  }
  match
    @core.Body::attachment_file(
      filename,
      path,
      content_type=@codec.infer_content_type(filename),
    ) {
    Ok(body) => body
    Err(_) => {
      println("Failed to build attachment body for \{path}")
      panic()
    }
  }
}

///|
fn email_body_or_panic(
  body : String,
  attachment_paths : Array[String],
) -> @core.Body {
  if attachment_paths.is_empty() {
    return @core.Body::plain_text(body)
  }
  let parts : Array[@core.Body] = [@core.Body::plain_text(body)]
  for path in attachment_paths {
    parts.push(attachment_part_or_panic(path))
  }
  match @core.Body::mixed(parts) {
    Ok(multipart) => multipart
    Err(_) => {
      println("Failed to build multipart email body")
      panic()
    }
  }
}

///|
fn message_or_panic(
  sender : @core.Mailbox,
  recipient : @core.Mailbox,
  subject : String,
  body : @core.Body,
) -> @core.Message {
  match @core.Message::new(sender, [recipient], [], [], subject, body, []) {
    Ok(message) => message
    Err(_) => {
      println("Failed to build email message")
      panic()
    }
  }
}

///|
fn print_outcome(outcome : @transport.DeliveryOutcome) -> Unit {
  println("SMTP response: \{outcome.response}")
  println("Accepted recipients: \{outcome.accepted.length()}")
  println("Rejected recipients: \{outcome.rejected.length()}")
  println("Unsent recipients: \{outcome.unsent.length()}")
}

///|
async fn main {
  let matches = cli_command().parse()
  let sender_address = option_value(matches, "sender")
  let recipient_address = option_value(matches, "recipient")
  let auth_code = option_value(matches, "auth-code")
  let subject = option_value(matches, "subject")
  let body_text = option_value(matches, "body")
  let attachment_paths = option_values(matches, "attachment")

  let sender = mailbox_or_panic("sender", sender_address)
  let recipient = mailbox_or_panic("recipient", recipient_address)
  let envelope = envelope_or_panic(sender, recipient)
  let body = email_body_or_panic(body_text, attachment_paths)
  let message = message_or_panic(sender, recipient, subject, body)

  let config = @transport.SmtpConfig::new(
    "smtp.qq.com",
    465,
    security=Tls,
    auth=Plain(sender_address, auth_code),
  )
  let transport = @transport.SmtpTransport::new(config)

  println("Connecting to smtp.qq.com:465 over TLS...")
  println("Sending from \{sender_address} to \{recipient_address} ...")
  if !attachment_paths.is_empty() {
    println("Attachments: \{attachment_paths.length()}")
  }

  try {
    let outcome = transport.send_message(envelope, message)
    print_outcome(outcome)
    if outcome.accepted.is_empty() {
      println("No recipients were accepted by the SMTP server.")
      panic()
    }
    println("Email send attempt completed.")
  } catch {
    err => {
      println("Send failed: \{err.to_string()}")
      panic()
    }
  }
}