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

require 'contrast/components/logger'
require 'contrast/agent/reporting/reporting_events/application_defend_attacker_activity'

module Contrast
  module Agent
    module Reporting
      # This is the new ApplicationDefendActivity class which includes information about the defense of the application
      # which was discovered during exercise of the application during this activity period.
      class ApplicationDefendActivity
        # @return [Array<Contrast::Agent::Reporting::ApplicationDefendAttackerActivity>]
        attr_reader :attackers

        # @return [Boolean]
        attr_reader :existing_attacker_activity

        def initialize
          @attackers = []
          @existing_attacker_activity = false
          @event_type = :application_defend_activity
        end

        def to_controlled_hash
          validate
          {
              attackers: attackers.map(&:to_controlled_hash)
          }
        end

        def validate
          raise(ArgumentError, 'Attackers data must be populated') if existing_attacker_activity && attackers.empty?
        end

        # @param attack_result [Contrast::Agent::Reporting::AttackResult]
        def attach_data attack_result
          return unless attack_result&.cs__is_a?(Contrast::Agent::Reporting::AttackResult)

          attacker_activity = Contrast::Agent::Reporting::ApplicationDefendAttackerActivity.new
          attacker_activity.attach_data(attack_result)

          @existing_attacker_activity = true
          if (existing_attacker_activity = find_existing_attacker_activity(attacker_activity))
            attach_existing(existing_attacker_activity, attacker_activity, attack_result.rule_id)
          else
            attackers << attacker_activity
          end
        end

        # Find an existing attacker if it matches on source details
        # @param new_attacker_activity [Contrast::Agent::Reporting::ApplicationDefendAttackerActivity]
        def find_existing_attacker_activity new_attacker_activity
          attackers.find do |existing|
            existing.source_forwarded_for == new_attacker_activity.source_forwarded_for &&
                existing.source_ip == new_attacker_activity.source_ip
          end
        end

        # @param existing_attacker_activity [Contrast::Agent::Reporting::ApplicationDefendAttackerActivity]
        # @param attacker_activity [Contrast::Agent::Reporting::ApplicationDefendAttackerActivity]
        # @param rule [String]
        def attach_existing existing_attacker_activity, attacker_activity, rule
          new_violation = attacker_activity.protection_rules[rule]
          sample_activity = Contrast::Agent::Reporting::ApplicationDefendAttackSampleActivity
          if (previously_violated = existing_attacker_activity.protection_rules[rule])
            if (new_blocked = new_violation.blocked)
              previously_violated.blocked ||= sample_activity.new
              previously_violated.blocked.samples.concat(new_blocked.samples) if new_blocked.samples
              previously_violated.blocked.merge_time_maps(new_blocked.time_map)
            end

            if (new_exploited = new_violation.exploited)
              previously_violated.exploited ||= sample_activity.new
              previously_violated.exploited.samples.concat(new_exploited.samples) if new_exploited.samples
              previously_violated.exploited.merge_time_maps(new_exploited.time_map)
            end

            if (new_ineffective = new_violation.ineffective)
              previously_violated.ineffective ||= sample_activity.new
              previously_violated.ineffective.samples.concat(new_ineffective.samples) if new_ineffective.samples
              previously_violated.ineffective.merge_time_maps(new_ineffective.time_map)
            end

            if (new_suspicious = new_violation.suspicious)
              previously_violated.suspicious ||= sample_activity.new
              previously_violated.suspicious.samples.concat(new_suspicious.samples) if new_suspicious.samples
              previously_violated.suspicious.merge_time_maps(new_suspicious.time_map)
            end
          else
            existing_attacker_activity.protection_rules[rule] = new_violation
          end
        end
      end
    end
  end
end