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

require 'contrast/agent/protect/rule/base'
require 'contrast/components/logger'
require 'contrast/agent/reporting/input_analysis/input_type'
require 'contrast/agent/reporting/input_analysis/score_level'
require 'contrast/agent_lib/interface'
require 'contrast/agent/protect/rule/bot_blocker/bot_blocker_input_classification'

module Contrast
  module Agent
    module Protect
      module Rule
        # The Ruby implementation of the Protect BotBlocker rule.
        class BotBlocker < Contrast::Agent::Protect::Rule::Base
          include Contrast::Components::Logger::InstanceMethods
          include Contrast::Agent::Reporting::InputType

          NAME = 'bot-blocker'
          APPLICABLE_USER_INPUTS = [HEADER].cs__freeze

          def rule_name
            NAME
          end

          def applicable_user_inputs
            APPLICABLE_USER_INPUTS
          end

          # Bot blocker input classification
          #
          # @return [module<Contrast::Agent::Protect::Rule::BotBlockerInputClassification>]
          def classification
            @_classification ||= Contrast::Agent::Protect::Rule::BotBlockerInputClassification.cs__freeze
          end

          # BotBlocker prefilter:
          #
          # @param context [Contrast::Agent::RequestContext] current request contest
          # @raise [Contrast::SecurityException] if the rule mode ise set
          # to BLOCK and valid bot is detected.
          def prefilter context
            return unless prefilter?(context)
            # We expect only one result per request since the user-agent Header is one.
            # And the IA analysis explicitly searches for the key match before starting
            # the analysis.
            return unless (ia_result = gather_ia_results(context)[0]) &&
                ia_result.score_level == Contrast::Agent::Reporting::ScoreLevel::DEFINITEATTACK

            result = build_attack_without_match(context, ia_result, nil)
            append_to_activity(context, result) if result
            cef_logging(result, :successful_attack) if result
            return unless blocked?

            # Raise BotBlocker error
            exception_message = "#{ rule_name } rule triggered. Unsafe Bot blocked."
            raise(Contrast::SecurityException.new(self, exception_message))
          end

          # @param context [Contrast::Agent::RequestContext]
          # @return [Array<Contrast::Agent::Reporting::InputAnalysis>]
          def gather_ia_results context
            return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless context&.agent_input_analysis&.results

            context.agent_input_analysis.results.select do |ia_result|
              ia_result.rule_id == rule_name
            end
          end

          # Adding bot blocker details
          #
          # @param context [Contrast::Agent::RequestContext]
          # @param ia_result [Contrast::Agent::Reporting::InputAnalysisResult]
          # @param _candidate_string
          # @param **_kwargs
          # @return [Contrast::Agent::Reporting::RaspRuleSample]
          def build_sample context, ia_result, _candidate_string, **_kwargs
            sample = build_base_sample(context, ia_result)
            sample.details = Contrast::Agent::Reporting::BotBlockerDetails.new
            sample.details.bot = ia_result.value
            sample.details.user_agent = context&.request&.user_agent
            sample
          end
        end
      end
    end
  end
end