# frozen_string_literal: true

# Builds a Mail::Message we can use for sending. Optionally supports using a template
# for the body and subject
module Email
  class MessageBuilder
    attr_reader :template_args, :reply_by_email_key

    ALLOW_REPLY_BY_EMAIL_HEADER = "X-Discourse-Allow-Reply-By-Email"
    INSTRUCTIONS_SEPARATOR = "---\n"

    def initialize(to, opts = nil)
      @to = to
      @opts = opts || {}
      @template_args = {
        site_name: SiteSetting.title,
        email_prefix: SiteSetting.email_prefix.presence || SiteSetting.title,
        base_url: Discourse.base_url,
        user_preferences_url: "#{Discourse.base_url}/my/preferences",
        hostname: Discourse.current_hostname,
      }.merge!(@opts)

      if @opts[:recipient_user].present?
        @template_args[:recipient_username] = @opts[:recipient_user].username
      end

      if @opts[:template].present? && I18n.exists?("#{@opts[:template]}.preview")
        @template_args[:email_preview] ||= I18n.t("#{@opts[:template]}.preview", @template_args)
      end

      return if @template_args[:url].blank?

      @template_args[:header_instructions] ||= I18n.t(
        "user_notifications.header_instructions",
        @template_args,
      )
      @visit_link_to_respond_key =
        DiscoursePluginRegistry.apply_modifier(
          :message_builder_visit_link_to_respond,
          "user_notifications.visit_link_to_respond",
          @opts,
          @to,
        )
      @reply_by_email_key =
        DiscoursePluginRegistry.apply_modifier(
          :message_builder_reply_by_email,
          "user_notifications.reply_by_email",
          @opts,
          @to,
        )

      if @opts[:include_respond_instructions] == false
        if @opts[:private_reply]
          @template_args[:respond_instructions] = I18n.t(
            "user_notifications.pm_participants",
            @template_args,
          )
        else
          @template_args[:respond_instructions] = ""
        end
      else
        if @opts[:only_reply_by_email]
          respond_instructions_key = +"user_notifications.only_reply_by_email"
          if @opts[:private_reply]
            if @opts[:username] == Discourse.system_user.username
              respond_instructions_key << "_pm_button_only"
            else
              respond_instructions_key << "_pm"
            end
          end
        else
          respond_instructions_key =
            (
              if allow_reply_by_email?
                +@reply_by_email_key
              else
                +@visit_link_to_respond_key
              end
            )
          if @opts[:private_reply]
            if @opts[:username] == Discourse.system_user.username
              respond_instructions_key << "_pm_button_only"
            else
              respond_instructions_key << "_pm"
            end
          end
        end
        @template_args[:respond_instructions] = (
          if respond_instructions_key != ""
            INSTRUCTIONS_SEPARATOR + I18n.t(respond_instructions_key, @template_args)
          else
            ""
          end
        )
      end

      if @opts[:add_unsubscribe_link]
        unsubscribe_string =
          if @opts[:mailing_list_mode]
            "unsubscribe_mailing_list"
          elsif SiteSetting.unsubscribe_via_email_footer
            "unsubscribe_link_and_mail"
          else
            "unsubscribe_link"
          end
        @template_args[:unsubscribe_instructions] = I18n.t(unsubscribe_string, @template_args)
      end
    end

    def subject
      has_override =
        TranslationOverride.exists?(
          locale: I18n.locale,
          translation_key: "#{@opts[:template]}.subject_template",
        )

      if @opts[:template] && has_override
        augmented_template_args =
          @template_args.merge(
            site_name: @template_args[:email_prefix],
            optional_re: @opts[:add_re_to_subject] ? I18n.t("subject_re") : "",
            optional_pm: @opts[:private_reply] ? @template_args[:subject_pm] : "",
            optional_cat: format_category,
            optional_tags: format_tags,
            topic_title: @template_args[:topic_title] ? @template_args[:topic_title] : "",
          )
        subject = I18n.t("#{@opts[:template]}.subject_template", augmented_template_args)
      elsif @opts[:use_site_subject]
        subject = String.new(SiteSetting.email_subject)
        subject.gsub!("%{site_name}", @template_args[:email_prefix])
        subject.gsub!("%{optional_re}", @opts[:add_re_to_subject] ? I18n.t("subject_re") : "")
        subject.gsub!("%{optional_pm}", @opts[:private_reply] ? @template_args[:subject_pm] : "")
        subject.gsub!("%{optional_cat}", format_category)
        subject.gsub!("%{optional_tags}", format_tags)
        if @template_args[:topic_title]
          subject.gsub!("%{topic_title}", @template_args[:topic_title])
        end
      elsif @opts[:use_topic_title_subject]
        subject = @opts[:add_re_to_subject] ? I18n.t("subject_re") : ""
        subject = "#{subject}#{@template_args[:topic_title]}"
      elsif @opts[:template]
        subject = I18n.t("#{@opts[:template]}.subject_template", @template_args)
      else
        subject = @opts[:subject]
      end

      DiscoursePluginRegistry.apply_modifier(:message_builder_subject, subject, @opts, @to)
    end

    def html_part
      return unless html_override = @opts[:html_override]

      if @template_args[:unsubscribe_instructions].present?
        unsubscribe_instructions =
          PrettyText.cook(@template_args[:unsubscribe_instructions], sanitize: false).html_safe
        html_override.gsub!("%{unsubscribe_instructions}", unsubscribe_instructions)
      else
        html_override.gsub!("%{unsubscribe_instructions}", "")
      end

      if @template_args[:email_preview].present?
        email_preview = PrettyText.cook(@template_args[:email_preview], sanitize: false).html_safe
        html_override.gsub!("%{email_preview}", email_preview)
      else
        html_override.gsub!("%{email_preview}", "")
      end

      if @template_args[:header_instructions].present?
        header_instructions =
          PrettyText.cook(@template_args[:header_instructions], sanitize: false).html_safe
        html_override.gsub!("%{header_instructions}", header_instructions)
      else
        html_override.gsub!("%{header_instructions}", "")
      end

      if @template_args[:respond_instructions].present?
        respond_instructions =
          PrettyText.cook(@template_args[:respond_instructions], sanitize: false).html_safe
        html_override.gsub!("%{respond_instructions}", respond_instructions)
      else
        html_override.gsub!("%{respond_instructions}", "")
      end

      html =
        UserNotificationRenderer.render(
          template: "layouts/email_template",
          format: :html,
          locals: {
            html_body: html_override.html_safe,
          },
        )
      html = DiscoursePluginRegistry.apply_modifier(:message_builder_html_part, html, @opts, @to)

      Mail::Part.new do
        content_type "text/html; charset=UTF-8"
        body html
      end
    end

    def body
      body = nil

      if @opts[:template]
        %i[topic_title inviter_name].each do |key|
          @template_args[key] = escaped_template_arg(key) if @template_args.key?(key)
        end

        augmented_template_args =
          @template_args.merge(
            optional_re: "",
            optional_pm: "",
            optional_cat: format_category,
            optional_tags: format_tags,
          )

        body = I18n.t("#{@opts[:template]}.text_body_template", augmented_template_args).dup
      else
        body = @opts[:body].dup
      end

      if @template_args[:unsubscribe_instructions].present?
        body << "\n"
        body << @template_args[:unsubscribe_instructions]
      end
      DiscoursePluginRegistry.apply_modifier(:message_builder_body, body, @opts, @to)
    end

    def build_args
      args = {
        to: @to,
        subject: subject,
        body: body,
        charset: "UTF-8",
        from: from_value,
        cc: @opts[:cc],
        bcc: @opts[:bcc],
      }

      args[:delivery_method_options] = @opts[:delivery_method_options] if @opts[
        :delivery_method_options
      ]
      args[:delivery_method_options] = (args[:delivery_method_options] || {}).merge(
        return_response: true,
      )

      args
    end

    def header_args
      result = {}

      if @template_args[:email_preview].present?
        result["X-Discourse-Email-Preview"] = @template_args[:email_preview]
      end

      if @opts[:add_unsubscribe_link]
        if unsubscribe_url = @template_args[:unsubscribe_url].presence
          result["List-Unsubscribe"] = "<#{unsubscribe_url}>"
          result["List-Unsubscribe-Post"] = "List-Unsubscribe=One-Click"
        else
          result["List-Unsubscribe"] = "<#{@template_args[:user_preferences_url]}>"
        end
      end

      result["X-Discourse-Post-Id"] = @opts[:post_id].to_s if @opts[:post_id]
      result["X-Discourse-Post-Ids"] = @opts[:post_ids].join(",") if @opts[:post_ids].present?
      result["X-Discourse-Topic-Id"] = @opts[:topic_id].to_s if @opts[:topic_id]
      result["X-Discourse-Topic-Ids"] = @opts[:topic_ids].join(",") if @opts[:topic_ids].present?

      # At this point these have been filtered by the recipient's guardian for visibility,
      # see UserNotifications#send_notification_email
      result["X-Discourse-Tags"] = @template_args[:show_tags_in_subject] if @opts[
        :show_tags_in_subject
      ]
      result["X-Discourse-Category"] = @template_args[:show_category_in_subject] if @opts[
        :show_category_in_subject
      ]

      # Mimics X-GitHub-Sender, which identifies the GitHub user that originated the message,
      # useful to filter and prioritize mail.
      result["X-Discourse-Sender"] = @opts[:username] if @opts[:username].present?

      # Please, don't send us automatic responses...
      result["X-Auto-Response-Suppress"] = "All"

      # Disable Outlook's noisy "reaction via email" feature
      result["x-ms-reactions"] = "disallow"

      if !allow_reply_by_email?
        # This will end up being the notification_email, which is a
        # noreply address.
        result["Reply-To"] = from_value
      else
        # The only reason we use from address for reply to is for group
        # SMTP emails, where the person will be replying to the group's
        # email_username.
        if !@opts[:use_from_address_for_reply_to]
          result[ALLOW_REPLY_BY_EMAIL_HEADER] = true
          result["Reply-To"] = reply_by_email_address
        else
          # No point in adding a reply-to header if it is going to be identical
          # to the from address/alias. If the from option is not present, then
          # the default reply-to address is used.
          result["Reply-To"] = from_value if from_value != alias_email(@opts[:from])
        end
      end

      result.merge(MessageBuilder.custom_headers(SiteSetting.email_custom_headers))
    end

    def self.custom_headers(string)
      result = {}
      string
        .split("|")
        .each do |item|
          header = item.split(":", 2)
          if header.length == 2
            name = header[0].strip
            value = header[1].strip
            result[name] = value if name.length > 0 && value.length > 0
          end
        end unless string.nil?
      result
    end

    protected

    def allow_reply_by_email?
      SiteSetting.reply_by_email_enabled? && reply_by_email_address.present? &&
        @opts[:allow_reply_by_email]
    end

    def private_reply?
      allow_reply_by_email? && @opts[:private_reply]
    end

    def from_value
      return @from_value if @from_value
      @from_value = @opts[:from] || SiteSetting.notification_email
      @from_value = alias_email(@from_value)
    end

    def reply_by_email_address
      return @reply_by_email_address if @reply_by_email_address
      return nil if SiteSetting.reply_by_email_address.blank?

      @reply_by_email_address = SiteSetting.reply_by_email_address.dup

      @reply_by_email_address =
        if private_reply?
          alias_email(@reply_by_email_address)
        else
          site_alias_email(@reply_by_email_address)
        end
    end

    def alias_email(source)
      if @opts[:from_alias].blank? && SiteSetting.email_site_title.blank? &&
           SiteSetting.title.blank?
        return source
      end

      if @opts[:from_alias].present?
        %Q|"#{Email.cleanup_alias(@opts[:from_alias])}" <#{source}>|
      elsif source == SiteSetting.notification_email || source == SiteSetting.reply_by_email_address
        site_alias_email(source)
      else
        source
      end
    end

    def site_alias_email(source)
      from_alias = Email.site_title
      %Q|"#{Email.cleanup_alias(from_alias)}" <#{source}>|
    end

    private

    def escaped_template_arg(key)
      value = template_args[key].dup
      # explicitly escaped twice, as Mailers will mark the body as html_safe
      once_escaped = String.new(ERB::Util.html_escape(value))
      ERB::Util.html_escape(once_escaped)
    end

    def format_category
      if @template_args[:show_category_in_subject]
        "[#{@template_args[:show_category_in_subject]}] "
      else
        ""
      end
    end

    def format_tags
      if @template_args[:show_tags_in_subject]
        "#{@template_args[:show_tags_in_subject]} "
      else
        ""
      end
    end
  end
end
