require 'devise/version'

class Devise::TwoFactorAuthenticationController < DeviseController
  prepend_before_action :authenticate_scope!
  before_action :prepare_and_validate, :handle_two_factor_authentication

  def show
  end

  def update
    render(:show, status: :unprocessable_entity) and return if params[:code].nil?

    if resource.authenticate_otp(params[:code])
      after_two_factor_success_for(resource)
    else
      after_two_factor_fail_for(resource)
    end
  end

  def resend_code
    resource.send_new_otp
    redirect_to send("#{resource_name}_two_factor_authentication_path"), notice: I18n.t('devise.two_factor_authentication.code_has_been_sent')
  end

  private

  def after_two_factor_success_for(resource)
    set_remember_two_factor_cookie(resource)

    warden.session(resource_name)[DeviseMultiFactor::NEED_AUTHENTICATION] = false
    # For compatability with devise versions below v4.2.0
    # https://github.com/plataformatec/devise/commit/2044fffa25d781fcbaf090e7728b48b65c854ccb
    if respond_to?(:bypass_sign_in)
      bypass_sign_in(resource, scope: resource_name)
    else
      sign_in(resource_name, resource, bypass: true)
    end
    set_flash_message :notice, :success
    resource.update_attribute(:second_factor_attempts_count, 0)

    redirect_to after_two_factor_success_path_for(resource)
  end

  def set_remember_two_factor_cookie(resource)
    expires_seconds = resource.class.remember_otp_session_for_seconds

    if expires_seconds && expires_seconds > 0
      expires_at = expires_seconds.seconds.from_now
      cookies.signed[DeviseMultiFactor::REMEMBER_TFA_COOKIE_NAME] = {
        value: DeviseMultiFactor::RememberTFACookie.new
          .generate_cookie_data(resource, expires_at: expires_at),
        expires: expires_at,
      }
    end
  end

  def max_login_attempts_reached
    render :max_login_attempts_reached
  end

  def after_two_factor_success_path_for(resource)
    stored_location_for(resource_name) || :root
  end

  def after_two_factor_fail_for(resource)
    resource.second_factor_attempts_count += 1
    resource.save
    set_flash_message :alert, :attempt_failed, now: true

    if resource.max_login_attempts?
      sign_out(resource)
      max_login_attempts_reached
    else
      render :show
    end
  end

  def authenticate_scope!
    send(:"authenticate_#{resource_name}!", force: true)
    self.resource = send("current_#{resource_name}")
  end

  def prepare_and_validate
    redirect_to :root and return if resource.nil?
    @limit = resource.max_login_attempts
    if resource.max_login_attempts?
      sign_out(resource)
      max_login_attempts_reached and return
    end
  end
end