# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
# frozen_string_literal: true

cs__scoped_require 'contrast/api/dtm.pb'
cs__scoped_require 'contrast/api/decorators/address'
cs__scoped_require 'contrast/components/interface'
cs__scoped_require 'contrast/utils/string_utils'
cs__scoped_require 'contrast/utils/timer'

module Contrast
  module Api
    module Decorators
      # Used to decorate the HttpRequest protobuf model so it can own some of
      # the data massaging required for Request dtm
      module HttpRequest
        include Contrast::Components::Interface
        access_component :agent

        OMITTED_BODY = '{{body-omitted-by-contrast}}'

        def self.included klass
          klass.extend(ClassMethods)
        end

        # Append the sender and receiver to self.
        #
        # @param request [Contrast::Api::Dtm::HttpRequest]
        def append_addresses request
          self.sender = Contrast::Api::Dtm::Address.build_sender(request)
          self.receiver = Contrast::Api::Dtm::Address.build_receiver
        end

        # Append the connection to self.
        #
        # @param request [Contrast::Api::Dtm::HttpRequest]
        def append_connection request
          self.protocol = Contrast::Utils::StringUtils.force_utf8(request.scheme)
          self.version = '1.1' # currently not in rack request; hard-coding
          self.method = Contrast::Utils::StringUtils.force_utf8(request.request_method)
          self.raw = Contrast::Utils::StringUtils.force_utf8(request.rack_request.path_info)
        end

        # Append the parameters to self, using the preferred
        # normalized_request_params field.
        #
        # @param request [Contrast::Api::Dtm::HttpRequest]
        def append_params request
          request.parameters.each do |k, v|
            append_pair(normalized_request_params, k, v)
          end
        end

        # Append the normalized headers to self, using the preferred
        # request_headers field.
        #
        # @param request [Contrast::Api::Dtm::HttpRequest]
        def append_headers request
          request.headers.each do |k, v|
            next unless k && v
            next if v.is_a?(Hash)

            key = Contrast::Utils::StringUtils.force_utf8(k)
            val = Contrast::Utils::StringUtils.force_utf8(v)
            request_headers[key] = val
          end

          request.cookies.each do |k, v|
            append_pair(normalized_cookies, k, v)
          end
        end

        # Append the body to self, using the preferred
        # request_body_binary field.
        #
        # @param request [Contrast::Api::Dtm::HttpRequest]
        def append_body request
          self.document_type = Contrast::Utils::StringUtils.force_utf8(request.document_type)

          self.request_body_binary = if omit_body?(request)
                                       OMITTED_BODY
                                     else
                                       Contrast::Utils::StringUtils.force_utf8(request.body)
                                     end
          return if request.file_names.empty?

          request.file_names.each do |name, filename|
            pair = Contrast::Api::Dtm::SimplePair.new
            pair.key = Contrast::Utils::StringUtils.force_utf8(name)
            pair.value = Contrast::Utils::StringUtils.force_utf8(filename)
            multipart_headers << pair
          end
        end

        def omit_body? request
          return true if AGENT.omit_body?
          return false if request.document_type != :NORMAL

          request.content_type&.include?('multipart/form-data')
        end

        def append_pair map, key, value
          return unless key && value
          return if value.is_a?(Hash)

          safe_key = Contrast::Utils::StringUtils.force_utf8(key)
          map[safe_key] ||= Contrast::Api::Dtm::Pair.new
          map[safe_key].key = safe_key
          map[safe_key].values << Contrast::Utils::StringUtils.force_utf8(value)
        end

        # Used to add class methods to the ApplicationUpdate class on inclusion of the decorator
        module ClassMethods
          include Contrast::Components::Interface
          access_component :scope

          # Convert our Request into a DTM
          # @param request [Contrast::Agent::Request]
          # @return [Contrast::Api::Dtm::HttpRequest]
          def build request
            with_contrast_scope do
              msg = new
              msg.uuid = Contrast::Utils::StringUtils.force_utf8(__id__)
              msg.timestamp_ms = Contrast::Utils::Timer.now_ms.to_i

              msg.append_addresses(request)
              msg.append_connection(request)
              msg.append_params(request)
              msg.append_headers(request)
              msg.append_body(request)
              msg
            end
          end
        end
      end
    end
  end
end

Contrast::Api::Dtm::HttpRequest.include(Contrast::Api::Decorators::HttpRequest)