require "mail"

module Backup
  module Notifier
    class Mail < Base
      ##
      # Mail delivery method to be used by the Mail gem.
      #
      # Supported methods:
      #
      # [:smtp - ::Mail::SMTP (default)]
      #   Settings used by this method:
      #   {#address}, {#port}, {#domain}, {#user_name}, {#password},
      #   {#authentication}, {#encryption}, {#openssl_verify_mode}
      #
      # [:sendmail - ::Mail::Sendmail]
      #   Settings used by this method:
      #   {#sendmail_args}
      #
      # [:exim - ::Mail::Exim]
      #   Settings used by this method:
      #   {#exim_args}
      #
      # [:file - ::Mail::FileDelivery]
      #   Settings used by this method:
      #   {#mail_folder}
      #
      attr_accessor :delivery_method

      ##
      # Sender Email Address
      attr_accessor :from

      ##
      # Receiver Email Address
      attr_accessor :to

      ##
      # CC receiver Email Address
      attr_accessor :cc

      ##
      # BCC receiver Email Address
      attr_accessor :bcc

      ##
      # Set reply to email address
      attr_accessor :reply_to

      ##
      # SMTP Server Address
      attr_accessor :address

      ##
      # SMTP Server Port
      attr_accessor :port

      ##
      # Your domain (if applicable)
      attr_accessor :domain

      ##
      # SMTP Server Username (sender email's credentials)
      attr_accessor :user_name

      ##
      # SMTP Server Password (sender email's credentials)
      attr_accessor :password

      ##
      # Authentication type
      #
      # Acceptable values: +:plain+, +:login+, +:cram_md5+
      attr_accessor :authentication

      ##
      # Set the method of encryption to be used for the +SMTP+ connection.
      #
      # [:starttls (default)]
      #   Use +STARTTLS+ to upgrade the connection to a +SSL/TLS+ connection.
      #
      # [:tls or :ssl]
      #   Use a +SSL/TLS+ connection.
      #
      # [:none]
      #   No encryption will be used.
      attr_accessor :encryption

      ##
      # OpenSSL Verify Mode
      #
      # Valid modes: +:none+, +:peer+, +:client_once+, +:fail_if_no_peer_cert+
      # See +OpenSSL::SSL+ for details.
      #
      # Use +:none+ for a self-signed and/or wildcard certificate
      attr_accessor :openssl_verify_mode

      ##
      # Optional arguments to pass to `sendmail`
      #
      # Note that this will override the defaults set by the Mail gem
      # (currently: '-i'). So, if set here, be sure to set all the arguments
      # you require.
      #
      # Example: '-i -X/tmp/traffic.log'
      attr_accessor :sendmail_args

      ##
      # Optional arguments to pass to `exim`
      #
      # Note that this will override the defaults set by the Mail gem
      # (currently: '-i -t') So, if set here, be sure to set all the arguments
      # you require.
      #
      # Example: '-i -t -X/tmp/traffic.log'
      attr_accessor :exim_args

      ##
      # Folder where mail will be kept when using the `:file` `delivery_method`.
      #
      # Default location is '$HOME/Backup/emails'
      attr_accessor :mail_folder

      ##
      # Array of statuses for which the log file should be attached.
      #
      # Available statuses are: `:success`, `:warning` and `:failure`.
      # Default: [:warning, :failure]
      attr_accessor :send_log_on

      def initialize(model, &block)
        super
        instance_eval(&block) if block_given?

        @send_log_on ||= [:warning, :failure]
        @encryption  ||= :starttls
      end

      private

      ##
      # Notify the user of the backup operation results.
      #
      # `status` indicates one of the following:
      #
      # `:success`
      # : The backup completed successfully.
      # : Notification will be sent if `on_success` is `true`.
      #
      # `:warning`
      # : The backup completed successfully, but warnings were logged.
      # : Notification will be sent, including a copy of the current
      # : backup log, if `on_warning` or `on_success` is `true`.
      #
      # `:failure`
      # : The backup operation failed.
      # : Notification will be sent, including a copy of the current
      # : backup log, if `on_failure` is `true`.
      #
      def notify!(status)
        email = new_email
        email.subject = message.call(model, status: status_data_for(status))

        send_log = send_log_on.include?(status)
        template = Backup::Template.new(model: model, send_log: send_log)
        email.body = template.result(sprintf("notifier/mail/%s.erb", status.to_s))

        if send_log
          email.convert_to_multipart
          email.attachments["#{model.time}.#{model.trigger}.log"] = {
            mime_type: "text/plain;",
            content: Logger.messages.map(&:formatted_lines).flatten.join("\n")
          }
        end

        email.deliver! # raise error if unsuccessful
      end

      ##
      # Configures the Mail gem by setting the defaults.
      # Creates and returns a new email, based on the @delivery_method used.
      def new_email
        method = %w[smtp sendmail exim file test]
          .index(@delivery_method.to_s) ? @delivery_method.to_s : "smtp"

        options =
          case method
          when "smtp"
            opts = {
              address: @address,
              port: @port,
              user_name: @user_name,
              password: @password,
              authentication: @authentication,
              enable_starttls_auto: @encryption == :starttls,
              openssl_verify_mode: @openssl_verify_mode,
              ssl: @encryption == :ssl,
              tls: @encryption == :tls
            }

            # Don't override default domain setting if domain not applicable.
            # ref https://github.com/mikel/mail/blob/2.6.3/lib/mail/network/delivery_methods/smtp.rb#L82
            opts[:domain] = @domain if @domain
            opts
          when "sendmail"
            opts = {}
            opts[:location] = utility(:sendmail)
            opts[:arguments] = @sendmail_args if @sendmail_args
            opts
          when "exim"
            opts = {}
            opts[:location] = utility(:exim)
            opts[:arguments] = @exim_args if @exim_args
            opts
          when "file"
            @mail_folder ||= File.join(Config.root_path, "emails")
            { location: File.expand_path(@mail_folder) }
          when "test" then {}
          end

        email = ::Mail.new
        email.delivery_method method.to_sym, options
        email.to       = to
        email.from     = from
        email.cc       = cc
        email.bcc      = bcc
        email.reply_to = reply_to
        email
      end
    end
  end
end
