class SessionGuard
  attr_reader :name
  attr_reader :last_attempted
  attr_reader :via_remember
  attr_reader :remember_duration
  attr_reader :session
  attr_reader :cookies
  attr_reader :logged_out
  attr_reader :recall_attempted
  attr_reader :user

  def initialize(name:, session:, cookies: nil)
    @name = name
    @session = session
    @cookies = cookies
    @via_remember = false
    @remember_duration = 576000
    @logged_out = false
    @recall_attempted = false
  end

  def get_user
    return nil if @logged_out

    return @user unless @user.nil?

    id = @session[get_name]

    @user = User.find_by(id: id) unless id.nil?

    if @user.nil?
      @user = user_from_recaller

      update_session(@user.id) if @user
    end

    @user
  end

  def attempt(credentials = {}, remember = false)
    @last_attempted = user = User.authenticate_by(credentials)

    return false if user.nil?

    login(user, remember)

    true
  end

  def login(user, remember = false)
    update_session(user.id)

    if remember
      ensure_remember_token_is_set(user)
      create_recaller_cookie(user)
    end

    set_user(user)
  end

  def logout
    user = get_user

    clear_user_data_from_storage

    cycle_remember_token(user) unless @user.nil? || user.remember_token.blank?

    @user = nil

    @logged_out = true
  end

  def set_user(user)
    @user = user
    @logged_out = false
  end

  def check?
    !get_user.nil?
  end

  private

  def user_from_recaller
    _recaller = recaller

    return nil if _recaller.nil? || !_recaller.valid? || @recall_attempted

    @recall_attempted = true

    user = User.find_by(id: _recaller.id, remember_token: _recaller.token)

    @via_remember = true unless user.nil?

    user
  end

  def recaller
    return nil if @cookies.nil?

    _recaller = @cookies.signed[get_recaller_name]

    _recaller ? Recaller.new(_recaller) : nil
  end

  def update_session(id)
    @session[get_name] = id
  end

  def ensure_remember_token_is_set(user)
    cycle_remember_token(user) if user.remember_token.blank?
  end

  def create_recaller_cookie(user)
    @cookies.signed[get_recaller_name] = {
      value: "#{user.id}|#{user.remember_token}|#{user.password_digest}",
      expires: @remember_duration.minutes.from_now
    }
  end

  def clear_user_data_from_storage
    @session.delete(get_name)
    @cookies.delete(get_recaller_name) unless @cookies.nil?
  end

  def cycle_remember_token(user)
    user.update(remember_token: SecureRandom.alphanumeric(60))
  end

  def get_name
    "login_#{@name}_#{Digest::SHA1.hexdigest(self.class.name)}".to_sym
  end

  def get_recaller_name
    "remember_#{@name}_#{Digest::SHA1.hexdigest(self.class.name)}".to_sym
  end
end

class Recaller
  attr_reader :recaller

  def initialize(recaller)
    @recaller = recaller.split('|')
  end

  def id
    @recaller[0]
  end

  def token
    @recaller[1]
  end

  def valid?
    @recaller.length >= 3 && @recaller[0].present? && @recaller[1].present?
  end
end
