# frozen_string_literal: true

class EmailUpdater
  include HasErrors

  attr_reader :user
  attr_reader :change_req

  def self.human_attribute_name(name, options = {})
    User.human_attribute_name(name, options)
  end

  def initialize(guardian: nil, user: nil)
    @guardian = guardian
    @user = user
  end

  def change_to(email, add: false)
    @guardian.ensure_can_edit_email!(@user)

    email = Email.downcase(email.strip)
    EmailValidator.new(attributes: :email).validate_each(self, :email, email)
    return if errors.present?

    if existing_user = User.find_by_email(email)
      if SiteSetting.hide_email_address_taken
        Jobs.enqueue(:critical_user_email, type: :account_exists, user_id: existing_user.id)
      else
        error_message = +'change_email.error'
        error_message << '_staged' if existing_user.staged?
        errors.add(:base, I18n.t(error_message))
      end
    end

    if add
      secondary_emails_count = @user.secondary_emails.count
      if secondary_emails_count >= SiteSetting.max_allowed_secondary_emails
        errors.add(:base, I18n.t("change_email.max_secondary_emails_error"))
      end
    else
      old_email = @user.email
    end

    return if errors.present? || existing_user.present?

    if @guardian.is_staff? && @guardian.user != @user
      StaffActionLogger.new(@guardian.user).log_add_email(@user)
    else
      UserHistory.create!(action: UserHistory.actions[:add_email], acting_user_id: @user.id)
    end

    @change_req = EmailChangeRequest.find_or_initialize_by(user_id: @user.id, new_email: email)

    if @change_req.new_record?
      @change_req.requested_by = @guardian.user
      @change_req.old_email = old_email
      @change_req.new_email = email
    end

    if @change_req.change_state.blank? || @change_req.change_state == EmailChangeRequest.states[:complete]
      @change_req.change_state = if @user.staff?
        # Staff users must confirm their old email address first.
        EmailChangeRequest.states[:authorizing_old]
      else
        EmailChangeRequest.states[:authorizing_new]
      end
    end

    if @change_req.change_state == EmailChangeRequest.states[:authorizing_old]
      @change_req.old_email_token = @user.email_tokens.create!(email: @user.email, scope: EmailToken.scopes[:email_update])
      send_email(add ? :confirm_old_email_add : :confirm_old_email, @change_req.old_email_token)
    elsif @change_req.change_state == EmailChangeRequest.states[:authorizing_new]
      @change_req.new_email_token = @user.email_tokens.create!(email: email, scope: EmailToken.scopes[:email_update])
      send_email(:confirm_new_email, @change_req.new_email_token)
    end

    @change_req.save!
    @change_req
  end

  def confirm(token)
    confirm_result = nil

    User.transaction do
      email_token = EmailToken.confirmable(token, scope: EmailToken.scopes[:email_update])
      if email_token.blank?
        errors.add(:base, I18n.t('change_email.already_done'))
        confirm_result = :error
        next
      end

      email_token.update!(confirmed: true)

      @user = email_token.user
      @change_req = @user.email_change_requests
        .where('old_email_token_id = :token_id OR new_email_token_id = :token_id', token_id: email_token.id)
        .first

      case @change_req.try(:change_state)
      when EmailChangeRequest.states[:authorizing_old]
        @change_req.update!(
          change_state: EmailChangeRequest.states[:authorizing_new],
          new_email_token: @user.email_tokens.create!(email: @change_req.new_email, scope: EmailToken.scopes[:email_update])
        )
        send_email(:confirm_new_email, @change_req.new_email_token)
        confirm_result = :authorizing_new
      when EmailChangeRequest.states[:authorizing_new]
        @change_req.update!(change_state: EmailChangeRequest.states[:complete])
        if !@user.staff?
          # Send an email notification only to users who did not confirm old
          # email.
          send_email_notification(@change_req.old_email, @change_req.new_email)
        end
        update_user_email(@change_req.old_email, @change_req.new_email)
        confirm_result = :complete
      end
    end

    confirm_result || :error
  end

  def update_user_email(old_email, new_email)
    if old_email.present?
      @user.user_emails.find_by(email: old_email).update!(email: new_email)
    else
      @user.user_emails.create!(email: new_email)
    end
    @user.reload

    DiscourseEvent.trigger(:user_updated, @user)
    @user.set_automatic_groups
  end

  protected

  def send_email(type, email_token)
    Jobs.enqueue :critical_user_email,
                 to_address: email_token.email,
                 type: type,
                 user_id: @user.id,
                 email_token: email_token.token
  end

  def send_email_notification(old_email, new_email)
    Jobs.enqueue :critical_user_email,
                 to_address: @user.email,
                 type: old_email ? :notify_old_email : :notify_old_email_add,
                 user_id: @user.id,
                 new_email: new_email
  end
end