require 'uri'

module Hawkei
  module Plugins
    module Rack
      ##
      # Hawkei \Plugins \Rack \Middleware
      #
      # Middleware for Rack
      #
      class Middleware

        def initialize(app)
          @app = app
        end

        def call(env)
          request = ::Rack::Request.new(env)

          store_tracker(request)
          store_request_data(request, env)

          Hawkei::Plugins::Rails::MiddlewareData.store_data(request) if defined?(::Rails)
          status, headers, body = @app.call(env)

          write_cookie_session_tracker_id!(headers)
          [status, headers, body]
        ensure
          Hawkei::Store.clear! unless env['hawkei_test']
        end

        private

        def session_tracker_id_name
          '_hawkei_stid'.freeze
        end

        def store_tracker(request)
          Hawkei::Store.set(
            :session_tracker_id,
            request.cookies[session_tracker_id_name] || SecureRandom.uuid
          )
        end

        def store_request_data(request, env)
          Hawkei::Store.set(
            :request,
            url: obfuscate_uri(request.url),
            ssl: request.ssl?,
            host: request.host,
            port: request.port,
            path: request.path,
            referrer: obfuscate_uri(request.referrer),
            method: request.request_method,
            xhr: request.xhr?,
            user_agent: request.user_agent,
            ip: request.ip,
            get_params: obfuscation_get_params(request, 'GET'),
            post_params: obfuscation_get_params(request, 'POST'),
            headers: obfuscate_headers(env)
          )

          Hawkei::Store.bulk_set(
            server_software: request.env['SERVER_SOFTWARE']
          )
        end

        def write_cookie_session_tracker_id!(headers)
          ::Rack::Utils.set_cookie_header!(
            headers || {},
            session_tracker_id_name,
            Util.deep_compact(
              value: Hawkei::Store.get(:session_tracker_id),
              path: '/',
              domain: Hawkei.configurations.domain,
              http_only: false,
              max_age: (10 * 365 * 24 * 60 * 60),
            )
          )
        end

        def obfuscate_uri(url)
          uri = URI.parse(url)

          params =
            Util.deep_obfuscate_value(
              ::Rack::Utils.parse_query(uri.query),
              Hawkei.configurations.obfuscated_fields,
              'HIDDEN'
            )

          return url if params.empty?

          uri.merge(
            "?#{::Rack::Utils.build_query(params)}"
          ).to_s
        rescue StandardError => _e
          ''
        end

        def obfuscation_get_params(request, type)
          Util.deep_obfuscate_value(
            request.send(type),
            Hawkei.configurations.obfuscated_fields
          )
        rescue StandardError => _e
          {}
        end

        def https_request?(env)
          env['HTTPS'] == 'on' ||
            env['HTTP_X_FORWARDED_SSL'] == 'on' ||
            env['HTTP_X_FORWARDED_PROTO'].to_s.split(',').first == 'https' ||
            env['rack.url_scheme'] == 'https'
        end

        def obfuscate_headers(env)
          skip_headers = %w[HTTP_COOKIE]

          headers = env.keys.grep(/^HTTP_|^CONTENT_/).each_with_object({}) do |key, hash|
            next if skip_headers.include?(key)

            name = key.gsub(/^HTTP_/, '').split('_').map(&:capitalize).join('-')

            hash[name] = env[key]
          end

          Util.deep_obfuscate_value(
            headers,
            Hawkei.configurations.obfuscated_fields
          )
        rescue StandardError => _e
          {}
        end

      end
    end
  end
end