#' Create a SMTP server object.
#'
#' @name server
#'
#' @param host DNS name or IP address of the SMTP server.
#' @param port Port that the SMTP server is listening on.
#' @param username Username for SMTP server.
#' @param password Password for SMTP server or API key.
#' @param insecure Whether to ignore SSL issues.
#' @param reuse Whether the connection to the SMTP server should be left open for reuse.
#' @param helo The HELO domain name of the sending host. If left as \code{NA} then will use local host name.
#' @param protocol Which protocol (SMTP or SMTPS) to use for communicating with
#'   the server. Default will choose appropriate protocol based on port.
#' @param max_times Maximum number of times to retry.
#' @param pause_base Base delay (in seconds) for exponential backoff. See \link[purrr]{rate_backoff}.
#' @param ... Additional curl options. See \code{curl::curl_options()} for a list of supported options.
#'
#' @return A function which is used to send messages to the server.
#' @export
#' @examples
#' # Set parameters for SMTP server (with username and password).
#' smtp <- server(
#'   host = "smtp.gmail.com",
#'   port = 587,
#'   username = "bob@gmail.com",
#'   password = "bd40ef6d4a9413de9c1318a65cbae5d7"
#' )
#'
#' # Set parameters for a (fake) testing SMTP server.
#' #
#' # More information about this service can be found at https://www.smtpbucket.com/.
#' #
#' smtp <- server(
#'   host = "mail.smtpbucket.com",
#'   port = 8025
#' )
#'
#' # Create a message
#' msg <- envelope() %>%
#'   from("bob@gmail.com") %>%
#'   to("alice@yahoo.com")
#'
#' # Send message (verbose output from interactions with server)
#' \dontrun{
#' smtp(msg, verbose = TRUE)
#' }
#'
#' # To confirm that the message was sent, go to https://www.smtpbucket.com/ then:
#' #
#' # - fill in "bob@gmail.com" for the Sender field and
#' # - fill in "alice@yahoo.com" for the Recipient field then
#' # - press the Search button.
#'
#' # With explicit HELO domain.
#' #
#' smtp <- server(host = "mail.example.com",
#'                helo = "client.example.com")
server <- function(
  host,
  port = 25,
  username = NULL,
  password = NULL,
  insecure = FALSE,
  reuse = TRUE,
  helo = NA,
  protocol = NA,
  pause_base = 1,
  max_times = 5,
  ...) {
  function(msg, verbose = FALSE) {
    debugfunction <- if (verbose) function(type, msg) cat(readBin(msg, character()), file = stderr()) # nocov

    if (is.null(from(msg))) stop("Must specify who the email is from.")

    recipients <- c(to(msg), cc(msg), bcc(msg))
    #
    if (length(recipients) < 1) stop("Must specify at least one email recipient.")

    # See curl::curl_options() for available options.
    #
    # * SSL
    #
    # - If you get the "The certificate chain was issued by an authority that is not trusted." error then
    #   can add in ssl_verifypeer = FALSE.
    # - Other flags:
    #
    #   - ssl_verifyhost
    #   - ssl_verifypeer
    #   - ssl_verifystatus
    #
    #   Run curl_options('ssl') to see other options.
    #
    if (insecure) {
      ssl_verifypeer = FALSE
    } else {
      ssl_verifypeer = TRUE
    }

    port <- as.integer(port)
    if (port %in% c(465, 587)) {
      use_ssl = 1
    } else {
      use_ssl = 0
    }

    smtp_server <- smtp_url(host, port, protocol, helo)
    #
    if (verbose) {
      log_debug("Sending email to ", smtp_server, ".")
    }

    # Create an insistent version of send_mail().
    #
    # This is to mitigate occasional curl_fetch_memory() errors.
    #
    if (max_times > 1) {
      send_mail <- insistently(
        send_mail,
        rate = rate_backoff(
          max_times = max_times,
          pause_base = pause_base,
          pause_cap = pause_base * 2**max_times,
          jitter = FALSE
        )
      )
    }

    result <- send_mail(
      # Strip descriptive name (retained in email header so that it appears
      # in email client).
      mail_from = raw(from(msg)),
      mail_rcpt = raw(recipients),
      # Get character representation of envelope object.
      message = as.character(msg),
      smtp_server = smtp_server,
      username = username,
      password = password,
      verbose = verbose,
      debugfunction = debugfunction,
      ssl_verifypeer = ssl_verifypeer,
      use_ssl = use_ssl,
      forbid_reuse = !reuse,
      ...
    )

    invisible(result)
  }
}

#' @rdname server
#'
#' @export
#'
#' @examples
#'
#' # Set parameters for Gmail SMTP server. The host and port are implicit.
#' smtp <- gmail(
#'   username = "bob@gmail.com",
#'   password = "bd40ef6d4a9413de9c1318a65cbae5d7"
#' )
gmail <- function(
  username,
  password,
  ...) {
  # Retrieve function.
  fcall <- match.call(expand.dots = TRUE)
  # Substitute server() as function to call.
  fcall[[1]] <- server
  # Fill in host and port arguments.
  fcall$host = "smtp.gmail.com"
  fcall$port = ifelse(is.null(fcall$port), 587, fcall$port)
  # Now run the function call.
  eval(fcall, parent.frame())
}

#' @rdname server
#'
#' @section Sendgrid:
#'
#' To use SendGrid you'll need to first \href{https://docs.sendgrid.com/for-developers/sending-email/integrating-with-the-smtp-api}{create an API key}.
#' Then use the API key as the password.
#'
#' SendGrid will accept messages on ports 25, 587 and 2525 (using SMTP) as well
#' as 465 (using SMTPS).
#'
#' @export
#'
#' @examples
#'
#' # Set API key for SendGrid SMTP server.
#' smtp <- sendgrid(
#'   password = "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"
#' )
sendgrid <- function(
  password,
  ...) {
  fcall <- match.call(expand.dots = TRUE)

  fcall[[1]] <- server
  fcall$host = "smtp.sendgrid.net"
  fcall$port = ifelse(is.null(fcall$port), 587, fcall$port)
  fcall$username = "apikey"

  eval(fcall, parent.frame())
}

#' @rdname server
#'
#' @section Mailgun:
#'
#' To use Mailgun you'll need to first register a sender domain. This will then
#' be assigned a username and password.
#'
#' Mailgun will accept messages on ports 25 and 587 (using SMTP) as well as 465
#' (using SMTPS).
#'
#' @export
#'
#' @examples
#'
#' # Set username and password for Mailgun SMTP server.
#' smtp <- mailgun(
#'   username = "postmaster@sandbox9ptce35fdf0b31338dec4284eb7aaa59.mailgun.org",
#'   password = "44d072e7g2b5f3bf23b2b642da0fe3a7-2ac825a1-a5be680a"
#' )
mailgun <- function(
  username,
  password,
  ...) {
  fcall <- match.call(expand.dots = TRUE)

  fcall[[1]] <- server
  fcall$host = "smtp.mailgun.org"
  fcall$port = ifelse(is.null(fcall$port), 587, fcall$port)

  eval(fcall, parent.frame())
}

#' @rdname server
#'
#' @section Sendinblue:
#'
#' To use Sendinblue you'll need to first create an account. You'll find your
#' SMTP username and password in the SMTP & API section of your account
#' settings.
#'
#' @export
#'
#' @examples
#'
#' # Set username and password for Sendinblue SMTP server.
#' smtp <- sendinblue(
#'   username = "bob@gmail.com",
#'   password = "xsmtpsib-c75cf91323adc53a1747c005447cbc9a893c35888635bb7bef1a624bf773da33"
#' )
sendinblue <- function(
  username,
  password,
  ...) {
  fcall <- match.call(expand.dots = TRUE)

  fcall[[1]] <- server
  fcall$host = "smtp-relay.sendinblue.com"
  fcall$port = 587

  eval(fcall, parent.frame())
}

#' @rdname server
#'
#' @section MailerSend:
#'
#' To use MailerSend you'll need to first create an account. You'll find your
#' SMTP username and password under Domains. See \href{https://www.mailersend.com/help/smtp-relay}{How to send emails via SMTP with MailerSend}.
#'
#' Although this is not likely to be a problem in practice, MailerSend insists
#' that all messages have at minimum a valid subject and either text or HTML
#' content.
#'
#' @export
#'
#' @examples
#'
#' # Set username and password for MailerSend SMTP server.
#' smtp <- mailersend(
#'   username = "NS_Pf3ALM@gmail.com",
#'   password = "e5ATWLlTnWWDaKeE"
#' )
mailersend <- function(
  username,
  password,
  ...) {
  fcall <- match.call(expand.dots = TRUE)

  fcall[[1]] <- server
  fcall$host = "smtp.mailersend.net"
  fcall$port = 587

  eval(fcall, parent.frame())
}
