module AuthenticatedSystem
  protected

  # TODO: this module should be reviewed, because it seems to contain stuff that has
  # nothing to do with authentication. That stuff should be moved somewhere else.

  # Inclusion hook to make #current_user and #logged_in?
  # available as ActionView helper methods.
  def self.included(base)
    base.class_eval do

      helper_method :admin?, :authorized?, :current_account, :current_user,
                    :logged_in?, :owner?, :current_site, :admin? if respond_to?(:helper_method)

      prepend_before_filter :clear_current_user
      after_filter :update_current_user_after_login

      delegate :developer_portal_wildcard?,:admin_portal_wildcard?, to: :site_account_request
    end
  end

  def reset_session
    destroy_user_session!
    clear_current_user
    super
  end
  public :reset_session # required by protect_from_forgery with: :reset_session

  # TODO: move this to middleware
  def clear_current_user
    @current_user = User.current = nil
    authenticated_request.reset!
  end

  # Feel freee to add skip_after_filter for this method in controllers for stats or api transactions
  def update_current_user_after_login
    handle_remember_cookie!(params[:remember_me] == "1")
    user_session.access(request) if user_session
  end

  delegate :user_session, to: :authenticated_request

  def authenticated_request
    @_authenticated_request ||= AuthenticatedSystem::Request.new(request)
  end

  def create_user_session!
    us = current_user.user_sessions.build
    us.access(request)

    cookies.signed[:user_session] = {
      :value    => us.key,
      :httponly => true,
      # A secure cookie has the secure attribute enabled and is only used via HTTPS, ensuring that the cookie is always encrypted when transmitting from client to server. This makes the cookie less likely to be exposed to cookie theft via eavesdropping.
      :secure   => System::Application.config.three_scale.secure_cookie
    }

    us
  end

  def destroy_user_session!
    logger.info "Destroying user session #{user_session.to_param}"
    user_session.try!(:revoke!)
    cookies.delete :user_session
    @user_session = nil
  end

  def admin?
    logged_in? && current_user.admin?
  end

  def provider_admin_for?(site_account)
    current_account && current_account == site_account
  end

  # Returns true or false if the user is logged in.
  def logged_in?
    !!current_user
  end

  # Accesses the current user from the session.
  # Future calls avoid the database because nil is not equal to false.
  def current_user
    @current_user ||= (login_from_user_session || login_from_remember_me_cookie) unless @current_user == false
  end

  # Store the given user id in the session.
  def current_user=(new_user)
    @current_user = if new_user
      User.current = new_user
                    else
      false
                    end
  end

  # Filter method to enforce a login requirement.
  #
  # To require logins for all actions, use this in your controllers:
  #
  #   before_filter :login_required
  #
  # To require logins for specific actions, use this in your controllers:
  #
  #   before_filter :login_required, :only => [ :edit, :update ]
  #
  # To skip this in a subclassed controller:
  #
  #   skip_before_filter :login_required
  #
  def login_required
    logged_in? or unauthenticated
  end

  def unauthenticated
    request_login
  end

  def request_login
    store_location
    flash.keep

    if admin_portal_wildcard? || Account.is_admin_domain?(request.host) || site_account.master?
      redirect_to_login_for_providers
    else
      redirect_to_login_for_buyers
    end
  end

  # Store the URI of the current request in the session.
  #
  # We can return to this location by calling #redirect_back_or_default.
  def store_location
    session[:return_to] = request.fullpath
  end

  # Redirect to the URI stored by the most recent store_location call or
  # to the passed default.  Set an appropriately modified
  #   after_filter :store_location, :only => [:index, :new, :show, :edit]
  # for any controller you want to be bounce-backable.
  def redirect_back_or_default(default)
    url = URI.parse(url_for(session[:return_to] || default))

    redirect_to(url.select(:path, :query).compact.join('?'))
  ensure
    session[:return_to] = nil
  end

  #
  # Login
  #

  # Called from #current_user. First attempt to login by the user session stored in the cookie.
  def login_from_user_session
    if user_session && user_session.user && defined?(site_account)
      self.current_user = authenticated_request.current_user
    end
  end

  # Called from #current_user.  Finaly, attempt to login by an expiring token in the cookie.
  # for the paranoid: we _should_ be storing user_token = hash(cookie_token, request IP)
  def login_from_remember_me_cookie
    user = cookies[:auth_token] && defined?(site_account) && site_account.managed_users.find_by_remember_token(cookies[:auth_token])
    if user && user.remember_token?
      self.current_user = user
      handle_remember_cookie! false # freshen cookie token (keeping date)
      current_user
    end
  end

  #
  # Logout
  #

  # This is ususally what you want; resetting the session willy-nilly wreaks
  # havoc with forgery protection, and is only strictly necessary on login.
  # However, **all session state variables should be unset here**.
  def logout_keeping_session!
    # Kill server-side auth cookie
    @current_user.forget_me if @current_user.respond_to?(:forget_me)
    @current_user = false     # not logged in, and don't do it for me
    kill_remember_cookie!     # Kill client-side auth cookie
    session[:user_id] = nil   # keeps the session but kill our variable
    # explicitly kill any other session variables you set
  end

  # The session should only be reset at the tail end of a form POST --
  # otherwise the request forgery protection fails. It's only really necessary
  # when you cross quarantine (logged-out to logged-in).
  def logout_killing_session!
    logout_keeping_session!
    cms_token = session[:cms_token]
    reset_session
    session[:cms_token] = cms_token if cms_token
  end

  # Remember_me Tokens
  #
  # Cookies shouldn't be allowed to persist past their freshness date,
  # and they should be changed at each login

  # Cookies shouldn't be allowed to persist past their freshness date,
  # and they should be changed at each login

  def valid_remember_cookie?
    return nil unless @current_user
    (@current_user.remember_token?) &&
      (cookies[:auth_token] == @current_user.remember_token)
  end

  # Refresh the cookie auth token if it exists, create it otherwise
  def handle_remember_cookie! new_cookie_flag
    return unless @current_user
    case
    when valid_remember_cookie? then @current_user.refresh_token # keeping same expiry date
    when new_cookie_flag        then @current_user.remember_me
    else                             @current_user.forget_me
    end
    send_remember_cookie!
  end

  def kill_remember_cookie!
    cookies.delete :auth_token
  end

  def send_remember_cookie!
    cookies[:auth_token] = {
      :value   => @current_user.remember_token,
      :expires => @current_user.remember_token_expires_at }
  end

  # Account of current user, or nil if no one is logged in.
  def current_account
    @current_account || (current_user && current_user.account)
  end

  # Used when there is no "current_user" present, i.e. API calls with provider_key.
  def current_account=(account)
    @current_account = account
  end

  # This redirects to provider login screen
  def redirect_to_login_for_providers
    if domain_account.heroku?
      redirect_to Heroku.sso_url(domain_account, request.fullpath)
    else
      redirect_to provider_login_path
    end
  end

  # This redirects to login screen for buyers
  def redirect_to_login_for_buyers
    if site_account.settings.sso_login_url.blank?
      redirect_to developer_portal.login_path
    else
      redirect_to site_account.settings.sso_login_url
    end
  end

  def owner?(object_belonging_to_account)
    logged_in? && current_user.account.id == object_belonging_to_account.account_id
  end
end
