# 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/input_classification/utils'
require 'contrast/agent/protect/rule/input_classification/match_rates'
require 'contrast/agent/telemetry/input_analysis_cache_event'
require 'contrast/agent/reporting/input_analysis/score_level'
require 'contrast/agent/reporting/input_analysis/input_type'

module Contrast
  module Agent
    module Protect
      module Rule
        module InputClassification
          # This class will hold match information for each rule when input classification is being saved in LRU cache.
          class Statistics
            include Contrast::Agent::Protect::Rule::InputClassification::Utils
            include Contrast::Components::Logger::InstanceMethods

            attr_reader :data

            # Protect rules will always be fixed number, on other hand the number of inputs will grow,
            # we need to limit the number of inputs to be cached.
            CAPACITY = 30

            def initialize
              @data = {}
            end

            # This method will handle the statistics for the input match
            #
            # @param rule_id [String] the Protect rule name.
            # @param cached [Contrast::Agent::Protect::InputClassification::CachedResult]
            # @param request [Contrast::Agent::Request] the current request.
            def match! rule_id, cached, request
              return unless Contrast::Agent::Telemetry::Base.enabled?

              push(rule_id, cached)
              fetch(rule_id, cached.result.input_type)&.increase_match_for_input
              return unless cached.request_id == request.__id__

              fetch(rule_id, cached.result.input_type)&.increase_match_for_request
            end

            # This method will handle the statistics for the input mismatch.
            # Skip if this is the called with empty cache since it's not fair.
            #
            # @param rule_id [String] the Protect rule name.
            # @param input_type [Symbol] Type of the input
            def mismatch! rule_id, input_type
              return unless Contrast::Agent::Telemetry::Base.enabled?
              return if Contrast::Agent::Protect::InputAnalyzer.lru_cache.empty?

              fetch(rule_id, input_type)&.increase_mismatch_for_input
            end

            # @return [Array<Contrast::Agent::Telemetry::InputAnalysisCacheEvent>] the events to be sent.
            def to_events
              events = []
              data.each do |_rule_id, match_rates|
                match_rates.each do |match_rate|
                  event = Contrast::Agent::Telemetry::InputAnalysisCacheEvent.new(match_rate.rule_id, match_rate)
                  next if event.empty?

                  events << event
                end
              end
              events
            rescue StandardError => e
              logger.error("[IA_LRU_Cache] Error while creating events: #{ e }", stacktrace: e.backtrace)
              []
            end

            # Creates new statisctics for protect rule.
            #
            # @param rule_id [String] the Protect rule name.
            # @param cached [Contrast::Agent::Protect::InputClassification::CachedResult]
            def push rule_id, cached
              new_entry = Contrast::Agent::Protect::Rule::InputClassification::MatchRates.
                  new(rule_id, cached.result.input_type, cached.result.score_level)

              @data[rule_id] = [] if Contrast::Utils::DuckUtils.empty_duck?(@data[rule_id])
              @data[rule_id].shift if @data[rule_id].length >= CAPACITY
              @data[rule_id] << new_entry unless saved?(rule_id, cached)
            end

            # Get the statistics for the protect rule.
            #
            # @param rule_id [String] the Protect rule name.
            # @param input_type [Symbol] Type of the input
            def fetch rule_id, input_type
              safe_extract(@data[rule_id]&.select { |e| e.input_type == input_type })
            end

            private

            # Checks if the rate is saved for the input.
            #
            # @param rule_id [String] the Protect rule name.
            # @param new_entry [Contrast::Agent::Protect::InputClassification::CachedResult]
            def saved? rule_id, new_entry
              !fetch(rule_id, new_entry&.result&.input_type).nil?
            end

            # Call this method from the LRU Cache to get the statistics for a given rule_id, so it could be
            # thread safe.
            def clear
              @data.clear
            end
          end
        end
      end
    end
  end
end