module Backup
  module Notifier
    class Error < Backup::Error; end

    class Base
      include Utilities::Helpers
      include Config::Helpers

      ##
      # When set to true, the user will be notified by email
      # when a backup process ends without raising any exceptions
      attr_accessor :on_success
      alias :notify_on_success? :on_success

      ##
      # When set to true, the user will be notified by email
      # when a backup process is successful, but has warnings
      attr_accessor :on_warning
      alias :notify_on_warning? :on_warning

      ##
      # When set to true, the user will be notified by email
      # when a backup process raises an exception before finishing
      attr_accessor :on_failure
      alias :notify_on_failure? :on_failure

      ##
      # Number of times to retry failed attempts to send notification.
      # Default: 10
      attr_accessor :max_retries

      ##
      # Time in seconds to pause before each retry.
      # Default: 30
      attr_accessor :retry_waitsec

      ##
      # Message to send. Depends on notifier implementation if this is used.
      # Default: lambda returning:
      # "#{ message } #{ model.label } (#{ model.trigger })"
      #
      # @yieldparam [model] Backup::Model
      # @yieldparam [data] Hash containing `message` and `key` values.
      attr_accessor :message

      attr_reader :model

      def initialize(model)
        @model = model
        load_defaults!

        @on_success = true if on_success.nil?
        @on_warning = true if on_warning.nil?
        @on_failure = true if on_failure.nil?
        @max_retries    ||= 10
        @retry_waitsec  ||= 30
        @message        ||= lambda do |m, data|
          "[#{data[:status][:message]}] #{m.label} (#{m.trigger})"
        end
      end

      # This method is called from an ensure block in Model#perform! and must
      # not raise any exceptions. However, each Notifier's #notify! method
      # should raise an exception if the request fails so it may be retried.
      def perform!
        status =
          case model.exit_status
          when 0
            :success if notify_on_success?
          when 1
            :warning if notify_on_success? || notify_on_warning?
          else
            :failure if notify_on_failure?
          end

        if status
          Logger.info "Sending notification using #{notifier_name}..."
          with_retries { notify!(status) }
        end
      rescue Exception => err
        Logger.error Error.wrap(err, "#{notifier_name} Failed!")
      end

      private

      def with_retries
        retries = 0
        begin
          yield
        rescue StandardError, Timeout::Error => err
          retries += 1
          raise if retries > max_retries

          Logger.info Error.wrap(err, "Retry ##{retries} of #{max_retries}.")
          sleep(retry_waitsec)
          retry
        end
      end

      ##
      # Return the notifier name, with Backup namespace removed
      def notifier_name
        self.class.to_s.sub("Backup::", "")
      end

      ##
      # Return status data for message creation
      def status_data_for(status)
        {
          success: {
            message: "Backup::Success",
            key: :success
          },
          warning: {
            message: "Backup::Warning",
            key: :warning
          },
          failure: {
            message: "Backup::Failure",
            key: :failure
          }
        }[status]
      end
    end
  end
end
