require 'set'
require 'twitch_activity_log'

# Middlewares used in admin-panel
module Middleware
  # middleware that logs all controllers that are called with parameters.
  # sensitive parameter values are logged with [FILTERED] using
  # request.filtered_parameters and the env['action_dispatch.parameter_filter'].
  # by default, this is [:password] (anything that contains 'password').
  class ActivityLog
    def initialize(app)
      @app = app
      # we're activitely not logging these parameters as they are never useful
      # or redundant
      @skip_params = [
        # already captured as rails_controller_method
        'controller', 'action', 'commit',
        # not interesting
        'utf8',
        # csrf token that is not interesting
        'authenticity_token'
      ]
    end

    def call(env)
      return @app.call(env) if skip_log(env)

      @activity_logs ||= Twitch::ActivityLog::Client.new(
        environment: Settings.activity_log.environment
      )

      activity_log = extract_activity_log_pre env

      start_time = Time.now
      (status, headers, body) = @app.call(env)
      activity_log[:http_response_time] = Time.now.to_f - start_time.to_f

      activity_log_post = extract_activity_log_post env
      if activity_log_post
        activity_log = activity_log.merge activity_log_post
        @activity_logs.put(activity_log)
      end

      [status, headers, body]
    end

    def skip_log(env)
      Settings.activity_log.disable
    end

    def extract_activity_log_pre(env)
      raw_input = env['rack.input'].read
      env['rack.input'].rewind
      return {} unless raw_input

      {
        http_request_body_size: raw_input.bytesize,
      }
    end

    def extract_activity_log_post(env)
      return nil unless user_id(env)

      ctl = env['action_controller.instance']
      return nil unless ctl

      {
        user_id: user_id(env),
        access_date: Time.now,
        http_request_method: ctl.request.request_method,
        http_request_headers: hash_from_headers(
          ctl.request.headers, prefix: 'HTTP'
        ),
        http_request_url: ctl.request.original_url,
        http_response_headers: hash_from_headers(ctl.response.headers),
        http_response_body_size: ctl.response.body.bytesize,
        http_response_status_code: ctl.response.status,
        rails_controller_method: "#{ctl.class.name}.#{ctl.action_name}",
        rails_params: ctl.request.filtered_parameters.select { |key, value|
          !@skip_params.include?(key)
        },
        rails_session_id: rails_session_id(env)
      }
    end

    def user_id(env)
      sess = env['rack.session']
      sess ? sess['login_name'] : nil
    end

    def rails_session_id(env)
      sess = env['rack.request.cookie_hash']
      sess ? sess['_admin_panel_session_id'] : nil
    end

    def hash_from_headers(headers, prefix: '')
      header_hash = {}
      headers.each do |key, value|
        header_hash[key] = value if key.start_with? prefix
      end
      normalize_headers header_hash
    end

    def normalize_headers(headers)
      headers.each_with_object({}) do |(key, value), r|
        r[key.sub(/^HTTP_/, '').sub(/_/, '-').downcase] = value
      end
    end
  end
end
