module Authentication
  extend ActiveSupport::Concern
  include SessionLookup

  included do
    before_action :require_authentication
    before_action :deny_bots
    helper_method :signed_in?

    protect_from_forgery with: :exception, unless: -> { authenticated_by.bot_key? }
  end

  class_methods do
    def allow_unauthenticated_access(**options)
      skip_before_action :require_authentication, **options
    end

    def allow_bot_access(**options)
      skip_before_action :deny_bots, **options
    end

    def require_unauthenticated_access(**options)
      skip_before_action :require_authentication, **options
      before_action :restore_authentication, :redirect_signed_in_user_to_root, **options
    end
  end

  private
    def signed_in?
      Current.user.present?
    end

    def require_authentication
      restore_authentication || bot_authentication || request_authentication
    end

    def restore_authentication
      if session = find_session_by_cookie
        resume_session session
      end
    end

    def bot_authentication
      if params[:bot_key].present? && bot = User.authenticate_bot(params[:bot_key].strip)
        Current.user = bot
        set_authenticated_by(:bot_key)
      end
    end

    def request_authentication
      session[:return_to_after_authenticating] = request.url
      redirect_to new_session_url
    end

    def redirect_signed_in_user_to_root
      redirect_to root_url if signed_in?
    end

    def redirect_signed_in_user_to_chat
      redirect_to chat_url if signed_in?
    end

    def start_new_session_for(user)
      user.sessions.start!(user_agent: request.user_agent, ip_address: request.remote_ip).tap do |session|
        authenticated_as session
      end
    end

    def resume_session(session)
      session.resume user_agent: request.user_agent, ip_address: request.remote_ip
      authenticated_as session
    end

    def authenticated_as(session)
      return if session.user.suspended?

      Current.user = session.user
      set_authenticated_by(:session)
      cookies.signed.permanent[:session_token] = { value: session.token,
                                                   httponly: true,
                                                   same_site: :lax,
                                                   secure: Rails.env.production?,
                                                   domain: ENV["COOKIE_DOMAIN"]
      }
      request.session[:user_id] = session.user.id
    end

    def post_authenticating_url
      return_url = session.delete(:return_to_after_authenticating)
      safe_redirect_url?(return_url) ? return_url : root_url
    end

    def safe_redirect_url?(url)
      uri = URI.parse(url)
      uri.host.blank? || uri.host == URI.parse(root_url).hostname
    rescue URI::InvalidURIError
      false
    end

    def reset_authentication
      request.session.delete(:user_id)
      cookies.delete(:session_token, domain: ENV["COOKIE_DOMAIN"])
    end

    def deny_bots
      head :forbidden if authenticated_by.bot_key?
    end

    def set_authenticated_by(method)
      @authenticated_by = method.to_s.inquiry
    end

    def authenticated_by
      @authenticated_by ||= "".inquiry
    end
end
