# 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 SiteSetting.require_change_email_confirmation || @user.staff?
          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