# frozen_string_literal: true

# After https://github.com/nsarno/knock/blob/master/lib/knock/authenticable.rb.
#
# Include this module in your controller to enable authentication
# for your endpoint.
#
# e.g.
# Calling `authenticate_user` will try to find a valid `User` based on
# the token payload.
module AuthToken
  module Authenticable
    def authenticate_for(entity_class)
      getter_name = "current_#{entity_class.to_s.gsub('::', '').underscore}"
      define_current_entity_getter(entity_class, getter_name)
      public_send(getter_name)
    end

    private

    def token
      params[:token] || token_from_request_headers
    end

    def method_missing(method, *args)
      prefix, entity_name = method.to_s.split('_', 2)
      case prefix
      when 'authenticate'
        unauthorized_entity(entity_name) unless authenticate_entity(entity_name)
      when 'current'
        authenticate_entity(entity_name)
      else
        super
      end
    end

    def respond_to_missing?(method, *)
      prefix, = method.to_s.split('_', 2)
      case prefix
      when 'authenticate'
        true
      else
        super
      end
    end

    def authenticate_entity(entity_name)
      return unless token

      entity_class = entity_name.camelize.constantize
      send(:authenticate_for, entity_class)
    end

    def unauthorized_entity(_entity_name)
      head(:unauthorized)
    end

    def token_from_request_headers
      request.headers['Authorization']&.split&.last
    end

    # Dynamically defines a method similar to the example below.
    #
    # def current_user
    #   @_current_user ||= fetch_entity_from_token(User)
    # end
    def define_current_entity_getter(entity_class, getter_name)
      return if respond_to?(getter_name)

      memoization_var_name = "@_#{getter_name}"
      self.class.send(:define_method, getter_name) do
        unless instance_variable_defined?(memoization_var_name)
          current = fetch_entity_from_token(entity_class)
          instance_variable_set(memoization_var_name, current)
        end
        instance_variable_get(memoization_var_name)
      end
    end

    def fetch_entity_from_token(entity_class)
      AuthToken.new(token: token).entity_for(entity_class)
    rescue ActiveRecord::RecordNotFound, JWT::DecodeError, JWT::EncodeError
      nil
    end
  end
end
