# frozen_string_literal: true

module Jobs
  class GroupSmtpEmail < ::Jobs::Base
    include Skippable

    sidekiq_options queue: "critical"

    sidekiq_retry_in do |count, exception|
      # retry in an hour when SMTP server is busy
      # or use default sidekiq retry formula. returning
      # nil/0 will trigger the default sidekiq
      # retry formula
      #
      # See https://github.com/mperham/sidekiq/blob/3330df0ee37cfd3e0cd3ef01e3e66b584b99d488/lib/sidekiq/job_retry.rb#L216-L234
      case exception.wrapped
      when Net::SMTPServerBusy
        return 1.hour + (rand(30) * (count + 1))
      end
    end

    def execute(args)
      return if quit_email_early?
      email = args[:email]
      recipient_user = User.find_by_email(email, primary: true)

      post = Post.find_by(id: args[:post_id])
      return skip(email, nil, recipient_user, :group_smtp_post_deleted) if post.blank?

      group = Group.find_by(id: args[:group_id])
      return if group.blank?

      if !group.smtp_enabled
        return skip(email, post, recipient_user, :group_smtp_disabled_for_group)
      end

      if !Topic.exists?(id: post.topic_id)
        return skip(email, post, recipient_user, :group_smtp_topic_deleted)
      end

      cc_addresses =
        args[:cc_emails].filter { |address| EmailAddressValidator.valid_value?(address) }

      # Mask the email addresses of non-staged users so
      # they are not revealed unnecessarily when we are sending
      # the email notification out.
      bcc_addresses = User.not_staged.with_email(cc_addresses).pluck(:email)
      cc_addresses = cc_addresses - bcc_addresses

      # There is a rare race condition causing the Imap::Sync class to create
      # an incoming email and associated post/topic, which then kicks off
      # the PostAlerter to notify others in the PM about a reply in the topic,
      # but for the OP which is not necessary (because the person emailing the
      # IMAP inbox already knows about the OP)
      #
      # Basically, we should never be sending this notification for the first
      # post in a topic.
      #
      # If the group does not have IMAP enabled then this could be legitimate,
      # for example in cases where we are creating a new topic to reply to another
      # group PM and we need to send the participants the group OP email.
      if post.is_first_post? && group.imap_enabled
        ImapSyncLog.warn(
          "Aborting SMTP email for post #{post.id} in topic #{post.topic_id} to #{email}, the post is the OP and should not send an email.",
          group,
        )
        return
      end

      ImapSyncLog.debug(
        "Sending SMTP email for post #{post.id} in topic #{post.topic_id} to #{email}.",
        group,
      )

      # The EmailLog record created by the sender will have the raw email
      # stored, the group smtp ID, and any cc addresses recorded for later
      # cross referencing.
      message =
        GroupSmtpMailer.send_mail(
          group,
          email,
          post,
          cc_addresses: cc_addresses,
          bcc_addresses: bcc_addresses,
        )

      begin
        Email::Sender.new(message, :group_smtp, recipient_user).send
      rescue Net::ReadTimeout => err
        # We can't be sure if the send actually failed or if ENTER . ENTER (to end
        # the SMTP data sequence) just timed out, as is the case with Gmail occassionaly,
        # where they can do this if they suspect you are sending spam.
        Discourse.warn_exception(
          err,
          message: "Got SMTP read timeout when sending group SMTP email",
          env: args,
        )
      end

      # Create an incoming email record to avoid importing again from IMAP
      # server. While this may not be technically required if IMAP is not
      # currently enabled for the group, it will help a lot with the initial
      # sync if it is turned on at a later date.
      begin
        IncomingEmail.create!(
          user_id: post.user_id,
          topic_id: post.topic_id,
          post_id: post.id,
          raw: message.to_s,
          subject: message.subject,
          message_id: message.message_id,
          to_addresses: message.to,
          cc_addresses: message.cc,
          from_address: message.from,
          created_via: IncomingEmail.created_via_types[:group_smtp],
        )
      rescue => err
        Discourse.warn_exception(
          err,
          message: "Failed to create IncomingEmail record when sending group SMTP email",
          env: args,
        )
      end
    end

    def quit_email_early?
      SiteSetting.disable_emails == "yes" || !SiteSetting.enable_smtp
    end

    def skip(email, post, recipient_user, reason)
      create_skipped_email_log(
        email_type: :group_smtp,
        to_address: email,
        user_id: recipient_user&.id,
        post_id: post&.id,
        reason_type: SkippedEmailLog.reason_types[reason],
      )
    end
  end
end