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

require 'contrast/agent/protect/rule/cmd_injection'
require 'contrast/agent/protect/rule/deserialization'
require 'contrast/agent/protect/rule/http_method_tampering'
require 'contrast/agent/protect/rule/no_sqli'
require 'contrast/api/dtm.pb'
require 'contrast/components/logger'

module Contrast
  module Agent
    module Reporting
      # This is the new ApplicationDefendAttackSample class which includes a samples of an attack for the given rule of
      # the given result observed in the activity period.
      class ApplicationDefendAttackSample
        include Contrast::Agent::Reporting::InputType

        # @return [Hash] => start: ms, elapsed: ms
        attr_reader :time_stamp

        class << self
          # @param attack_result [Contrast::Api::Dtm::AttackResult]
          # @param attack_sample [Contrast::Api::Dtm::RaspRuleSample]
          def convert attack_result, attack_sample
            activity = new
            activity.attach_data(attack_result, attack_sample)
            activity
          end
        end

        def initialize
          @blocked = false
          @event_type = :application_defend_attack_sample
        end

        def to_controlled_hash
          {
              blocked: @blocked,
              input: @input,
              details: @details,
              request: @request&.to_controlled_hash,
              stack: @stack&.map(&:to_controlled_hash),
              timestamp: time_stamp
          }
        end

        # @param attack_result [Contrast::Api::Dtm::AttackResult]
        # @param attack_sample [Contrast::Api::Dtm::RaspRuleSample]
        def attach_data attack_result, attack_sample
          @blocked = attack_result.response == ::Contrast::Api::Dtm::AttackResult::ResponseType::BLOCKED
          @input = build_input(attack_sample)
          @details = build_details(attack_result.rule_id, attack_sample)
          @time_stamp = build_time_stamp(attack_sample.timestamp_ms)
          @request = FindingRequest.convert(Contrast::Agent::REQUEST_TRACKER.current&.request)
          @stack = Contrast::Utils::StackTraceUtils.build_protect_report_stack_array
        end

        # @param start [Integer]
        # @return [Hash]
        def build_time_stamp start
          {
              start: start,
              elapsed: Contrast::Utils::Timer.now_ms - start
          }
        end

        # @param attack_sample [Contrast::Api::Dtm::RaspRuleSample]
        def build_input attack_sample
          user_input = attack_sample.user_input
          {
              documentPath: user_input.path,
              documentType: build_document_type(user_input.document_type),
              filters: user_input.matcher_ids,
              name: user_input.key,
              time: attack_sample.timestamp_ms,
              type: build_input_type(user_input.input_type),
              value: user_input.value
          }
        end

        # Convert the document type api enum to a String constant TeamServer can understand.
        #
        # @param type_enum [::Contrast::Api::Dtm::HttpRequest::DocumentType]
        # @return [String]
        def build_document_type type_enum
          case type_enum
          when ::Contrast::Api::Dtm::HttpRequest::DocumentType::JSON
            'JSON'
          when ::Contrast::Api::Dtm::HttpRequest::DocumentType::XML
            'XML'
          else
            'NORMAL'
          end
        end

        # Convert the input type api enum to a String constant TeamServer can understand.
        #
        # @param type_enum [when ::Contrast::Api::Dtm::UserInput::InputType]
        # @return [String]
        def build_input_type type_enum
          case type_enum
          when ::Contrast::Api::Dtm::UserInput::InputType::UNDEFINED_TYPE
            'UNDEFINED_TYPE'
          when ::Contrast::Api::Dtm::UserInput::InputType::BODY
            'BODY'
          when ::Contrast::Api::Dtm::UserInput::InputType::COOKIE_NAME
            'COOKIE_NAME'
          when ::Contrast::Api::Dtm::UserInput::InputType::COOKIE_VALUE
            'COOKIE_VALUE'
          when ::Contrast::Api::Dtm::UserInput::InputType::HEADER
            'HEADER'
          when ::Contrast::Api::Dtm::UserInput::InputType::PARAMETER_NAME
            'PARAMETER_NAME'
          when ::Contrast::Api::Dtm::UserInput::InputType::PARAMETER_VALUE
            'PARAMETER_VALUE'
          when ::Contrast::Api::Dtm::UserInput::InputType::QUERYSTRING
            'QUERYSTRING'
          when ::Contrast::Api::Dtm::UserInput::InputType::URI
            'URI'
          when ::Contrast::Api::Dtm::UserInput::InputType::SOCKET
            'SOCKET'
          when ::Contrast::Api::Dtm::UserInput::InputType::JSON_VALUE
            'JSON_VALUE'
          when ::Contrast::Api::Dtm::UserInput::InputType::JSON_ARRAYED_VALUE
            'JSON_ARRAYED_VALUE'
          when ::Contrast::Api::Dtm::UserInput::InputType::MULTIPART_CONTENT_TYPE
            'MULTIPART_CONTENT_TYPE'
          when ::Contrast::Api::Dtm::UserInput::InputType::MULTIPART_VALUE
            'MULTIPART_VALUE'
          when ::Contrast::Api::Dtm::UserInput::InputType::MULTIPART_FIELD_NAME
            'MULTIPART_FIELD_NAME'
          when ::Contrast::Api::Dtm::UserInput::InputType::MULTIPART_NAME
            'MULTIPART_NAME'
          when ::Contrast::Api::Dtm::UserInput::InputType::XML_VALUE
            'XML_VALUE'
          when ::Contrast::Api::Dtm::UserInput::InputType::DWR_VALUE
            'DWR_VALUE'
          when ::Contrast::Api::Dtm::UserInput::InputType::METHOD
            'METHOD'
          when ::Contrast::Api::Dtm::UserInput::InputType::REQUEST
            'REQUEST'
          when ::Contrast::Api::Dtm::UserInput::InputType::URL_PARAMETER
            'URL_PARAMETER'
          else # ::Contrast::Api::Dtm::UserInput::InputType::UNKNOWN
            'UNKNOWN'
          end
        end

        # This is a temporary method to facilitate the translation of API details to Reporting details. As we move off
        # of protobuf, this responsibility should shift from this class to the individual rules, as they do today when
        # they populate their details in Contrast::Api::Dtm::RaspRuleSample
        #
        # @param rule_id [String]
        # @param attack_sample [Contrast::Api::Dtm::RaspRuleSample]
        # @return [Hash] the details for this specific rule
        def build_details rule_id, attack_sample
          case rule_id
          when Contrast::Agent::Protect::Rule::CmdInjection::NAME
            Contrast::Agent::Protect::Rule::CmdInjection.extract_details(attack_sample)
          when Contrast::Agent::Protect::Rule::Deserialization::NAME
            Contrast::Agent::Protect::Rule::Deserialization.extract_details(attack_sample)
          when Contrast::Agent::Protect::Rule::HttpMethodTampering::NAME
            Contrast::Agent::Protect::Rule::HttpMethodTampering.extract_details(attack_sample)
          when Contrast::Agent::Protect::Rule::NoSqli::NAME
            Contrast::Agent::Protect::Rule::NoSqli.extract_details(attack_sample)
          when Contrast::Agent::Protect::Rule::PathTraversal::NAME
            Contrast::Agent::Protect::Rule::PathTraversal.extract_details(attack_sample)
          when Contrast::Agent::Protect::Rule::Sqli::NAME
            Contrast::Agent::Protect::Rule::Sqli.extract_details(attack_sample)
          when Contrast::Agent::Protect::Rule::Xss::NAME
            Contrast::Agent::Protect::Rule::Xss.extract_details(attack_sample)
          when Contrast::Agent::Protect::Rule::Xxe::NAME
            Contrast::Agent::Protect::Rule::Xxe.extract_details(attack_sample)
          else # something unknown or Contrast::Agent::Protect::Rule::UnsafeFileUpload::NAME
            Contrast::Utils::ObjectShare::EMPTY_HASH
          end
        end
      end
    end
  end
end