# frozen_string_literal: true

module ErpIntegration
  class RateLimiter
    class MissingMethodError < StandardError; end
    # The `api_key_fragment` method should return a string that is used to identify
    # the rate limiter by the API key.
    #
    # The `within_limit` method should yield to the block if the rate limit is not exceeded.
    REQUIRED_METHODS = %i[api_key_fragment name within_limit].freeze

    class << self
      # Validates that the rate limiter object responds to the required methods.
      # It requires the `api_key_fragment` and `within_limit` methods to be present.
      #
      # @raise [MissingMethodError]
      # @param rate_limiter [Object]
      def validate!(rate_limiter)
        REQUIRED_METHODS.each do |method|
          next if rate_limiter.respond_to?(method)

          raise MissingMethodError, "'#{rate_limiter.class}##{method}' method is required."
        end
      end

      # Finds the rate limiter by the API key.
      # If the API key is not found, it returns an unlimited rate limiter.
      #
      # @param api_key [String]
      # @return [Object] The rate limiter object.
      def find_by_api_key(api_key)
        new(rate_limiters[api_key] || unlimited)
      end

      # Returns an unlimited rate limiter.
      #
      # @return [RateLimiter::Unlimited] The unlimited rate limiter object.
      def unlimited
        @unlimited ||= Unlimited.new
      end

      private

      # The `rate_limiters` hash stores the rate limiter objects found by the API key.
      #
      # @return [Hash] The rate limiters hash.
      def rate_limiters
        @rate_limiters ||= Hash.new do |h, api_key|
          h[api_key] = ErpIntegration.config.rate_limiters.find do |rate_limiter|
            api_key.end_with?(rate_limiter.api_key_fragment)
          end
        end
      end
    end

    delegate :name, to: :@rate_limiter

    def initialize(rate_limiter)
      @rate_limiter = rate_limiter
    end

    def within_limit(&block)
      ErpIntegration.config.logger.with_tags(@rate_limiter.name) do
        @rate_limiter.within_limit(&block)
      end
    end

    # {Unlimited} is a rate limiter that allows all requests.
    # When the rate limiter wasn't found by the API key, it defaults to this class.
    # Or if the `rate_limiters` option is not set in the configuration.
    class Unlimited
      # Executes the block without any restrictions.

      attr_reader :name

      def initialize
        @name = 'unlimited'
      end

      def within_limit
        yield
      end
    end
  end
end