require 'active_support/core_ext/hash'
require 'footrest/client'
require_relative 'rate_limiting'
require_relative 'client_module'
require_relative 'redis_connection'

module Bearcat
  class Client < Footrest::Client
    require 'bearcat/api_array'

    @added_modules = []

    Dir[File.join(__dir__, 'client', '*.rb')].each do |mod|
      mname = File.basename(mod, '.*').camelize
      mname = 'GraphQL' if mname == 'GraphQl'
      require mod
      lmod = "Bearcat::Client::#{mname}".constantize
      include lmod
      @added_modules << lmod
    end

    def self.registered_endpoints
      @registered_endpoints ||= @added_modules.reduce({}) do |h, m|
        h.merge!(m._registered_endpoints) rescue h
      end
      @registered_endpoints
    end

    def request(method, &block)
      response = rate_limited_request do
        connection.send(method, &block)
      end
      ApiArray.process_response(response, self)
    end

    def set_connection(config)
      super
      connection.builder.insert(Footrest::RaiseFootrestErrors, ExtendedRaiseFootrestErrors)
      connection.builder.delete(Footrest::RaiseFootrestErrors)
    end

    protected

    def rate_limited_request
      return yield unless rate_limiter

      canvas_rate_limits= 0
      response = nil

      begin
        rate_limiter.apply(
          rate_limit_key,
          max_sleep: Bearcat.max_sleep_seconds,
          on_sleep: ->(tts) {
            message = "Canvas API rate limit reached; sleeping for #{tts.to_i} second(s) to catch up."
            Bearcat.logger.debug(message)
          },
        ) do
          response = yield
          0
        end
      rescue Footrest::HttpError::Forbidden => err
        # Somehow our rate-limiting didn't limit enough and Canvas stopped us.
        response = err.response
        if canvas_rate_limits < 2 && err.message.include?("(Rate Limit Exceeded)")
          canvas_rate_limits += 1
          rate_limiter.checkin_known(rate_limit_key, 0)

          message = "Canvas API applied rate limit; upticking Bearcat rate-limit avoidance and retrying (Retry #{canvas_rate_limits})."
          Bearcat.logger.debug(message)

          retry
        end
        raise
      ensure
        headers = response.try(:response_headers) || response.try(:headers) || {}
        # -50 to provide a little extra leeway and hopefully be more proactive, making sure we don't even get close to Canvas throwing a 403, even if an out-of-band process is involved
        rate_limiter.checkin_known(rate_limit_key, headers['x-rate-limit-remaining'].to_f - 100) if response
      end

      response
    end

    def rate_limiter
      @rate_limiter ||= begin
        rl = config[:rate_limiter] || Bearcat.rate_limiter
        master_rate_limit = config[:master_rate_limit].present? ? config[:master_rate_limit] : Bearcat.master_rate_limit

        if rl.nil? && master_rate_limit.nil? && defined?(Rails) && Rails.env.production? && defined?(::Redis) && Bearcat::RedisConnection.configured?("BEARCAT", explicit: false)
          master_rate_limit = true
        end

        if rl.nil? && master_rate_limit
          rl = RateLimiting::RedisLimiter
        end

        if rl.is_a?(Class)
          rl.new()
        elsif rl.present?
          rl
        end
      end
    end

    private

    def rate_limit_key
      Digest::SHA1.hexdigest(config[:token])
    end
  end

  # Overridden response error middleware that, if an error code doesn't map to an exception, raises a more generic exception
  class ExtendedRaiseFootrestErrors < Footrest::RaiseFootrestErrors
    def on_complete(response)
      super
      key = response[:status].to_i
      raise ERROR_MAP[key.floor(-2)], response if key >= 400
    end
  end
end