# frozen_string_literal: true

require 'rack'
require 'twitch/audit/client'
require 'twitch/audit/request_context'
require 'twitch/audit/mixins/client_ip_resolution'
require 'twitch/audit/models/client_configuration'

module Twitch
  module Audit
    # Rack middleware to use as the entry point for audit logging.
    class Middleware

      class << self
        # The configuration object for the middleware
        # @return [ClientConfiguration]
        attr_reader :config

        # Use this method to establish the configuration for the middleware.
        # @return [void]
        def configure
          partial = ClientConfiguration.partial
          yield partial

          @config = partial.resolved
          Rack::Request.prepend(Mixins::ClientIPResolution) # Fix X-Forwarded-For handling
        end

        def configured?
          !config.nil?
        end
      end

      # @param [Object] app a valid Rack application
      def initialize(app)
        @app = app
      end

      # @param [Object] env a valid Rack environment
      def call(env)
        return @app.call(env) unless configured?

        RequestContext.reset

        status = nil
        exception = false

        begin
          status, headers, body = @app.call(env)
        rescue StandardError => e
          exception = true
          throw e
        ensure
          if RequestContext.log_entry?
            entry = RequestContext.log_entry

            entry.service = config.service_name
            entry.exception = entry.exception || exception
            entry.hydrate_request_data(Rack::Request.new(env), status || 500)
            entry.hydrate_session_data(env['BOUNCER_TOKEN']) unless env['BOUNCER_TOKEN'].nil?

            client.enqueue(entry.resolved)
          end
        end

        [status, headers, body]
      end

      # Returns the configuration the middleware is using
      # @return [ClientConfiguration]
      def config
        self.class.config
      end

      # Is the middleware configured?
      # @return [Boolean]
      def configured?
        self.class.configured?
      end

      private

      # A memoized audit client
      # @return [Twitch::Audit::Client]
      def client
        @client ||= Client.new(config)
      end
    end
  end
end
