# Inspired in great part by Discourse's Email::Receiver
module Gitlab
  module Email
    class Receiver
      class ProcessingError < StandardError; end
      class EmailUnparsableError < ProcessingError; end
      class SentNotificationNotFoundError < ProcessingError; end
      class EmptyEmailError < ProcessingError; end
      class AutoGeneratedEmailError < ProcessingError; end
      class UserNotFoundError < ProcessingError; end
      class UserBlockedError < ProcessingError; end
      class UserNotAuthorizedError < ProcessingError; end
      class NoteableNotFoundError < ProcessingError; end
      class InvalidNoteError < ProcessingError; end

      def initialize(raw)
        @raw = raw
      end

      def execute
        raise EmptyEmailError if @raw.blank?

        raise SentNotificationNotFoundError unless sent_notification

        raise AutoGeneratedEmailError if message.header.to_s =~ /auto-(generated|replied)/

        author = sent_notification.recipient

        raise UserNotFoundError unless author

        raise UserBlockedError if author.blocked?

        project = sent_notification.project

        raise UserNotAuthorizedError unless project && author.can?(:create_note, project)

        raise NoteableNotFoundError unless sent_notification.noteable

        reply = ReplyParser.new(message).execute.strip

        raise EmptyEmailError if reply.blank?

        reply = add_attachments(reply)

        note = create_note(reply)

        unless note.persisted?
          msg = "The comment could not be created for the following reasons:"
          note.errors.full_messages.each do |error|
            msg << "\n\n- #{error}"
          end

          raise InvalidNoteError, msg
        end
      end

      private

      def message
        @message ||= Mail::Message.new(@raw)
      rescue Encoding::UndefinedConversionError, Encoding::InvalidByteSequenceError => e
        raise EmailUnparsableError, e
      end

      def reply_key
        key_from_to_header || key_from_additional_headers
      end

      def key_from_to_header
        key = nil
        message.to.each do |address|
          key = Gitlab::IncomingEmail.key_from_address(address)
          break if key
        end

        key
      end

      def key_from_additional_headers
        reply_key = nil

        Array(message.references).each do |message_id|
          reply_key = Gitlab::IncomingEmail.key_from_fallback_reply_message_id(message_id)
          break if reply_key
        end

        reply_key
      end

      def sent_notification
        return nil unless reply_key

        SentNotification.for(reply_key)
      end

      def add_attachments(reply)
        attachments = Email::AttachmentUploader.new(message).execute(sent_notification.project)

        attachments.each do |link|
          reply << "\n\n#{link[:markdown]}"
        end

        reply
      end

      def create_note(reply)
        Notes::CreateService.new(
          sent_notification.project,
          sent_notification.recipient,
          note:           reply,
          noteable_type:  sent_notification.noteable_type,
          noteable_id:    sent_notification.noteable_id,
          commit_id:      sent_notification.commit_id,
          line_code:      sent_notification.line_code
        ).execute
      end
    end
  end
end
