# typed: true

# Copyright (c) 2015 Sqreen. All Rights Reserved.
# Please refer to our terms for more information: https://www.sqreen.com/terms.html

# TODO: Sqreen::Attack => sqreen/events
# TODO: Sqreen::RemoteException  => sqreen/events
# TODO: Sqreen::RequestRecord => sqreen/events
# TODO: Sqreen.time

require 'sqreen/aggregated_metric'
require 'sqreen/events/attack'
require 'sqreen/events/remote_exception'
require 'sqreen/mono_time'
require 'sqreen/deliveries/simple'

module Sqreen
  module Deliveries
    # Simple delivery method that batch event already seen in a batch
    class Batch < Simple
      attr_accessor :max_batch, :max_staleness
      attr_accessor :current_batch, :first_seen

      def initialize(session,
                     max_batch,
                     max_staleness,
                     randomize_staleness = true)
        super(session)
        self.max_batch = max_batch
        self.max_staleness = max_staleness
        @original_max_staleness = max_staleness
        self.current_batch = []
        @first_seen = {}
        @randomize_staleness = randomize_staleness
      end

      def post_event(event)
        current_batch.push(event)
        post_batch if post_batch_needed?(event)
      end

      def drain
        post_batch unless current_batch.empty?
      end

      def tick
        post_batch if !current_batch.empty? && stale?
      end

      protected

      def stale?
        min = @first_seen.values.min
        return false if min.nil?
        (min + max_staleness) < Sqreen.time
      end

      def post_batch_needed?(event)
        now = Sqreen.time
        # do not use any? {} due to side effects inside block
        event_keys(event).map do |key|
          was = @first_seen[key]
          @first_seen[key] ||= now
          was.nil? || current_batch.size > max_batch || now > (was + max_staleness)
        end.any?
      end

      def post_batch
        session.post_batch(current_batch)
        current_batch.clear
        now = Sqreen.time
        @first_seen.each_key do |key|
          @first_seen[key] = now
        end
        return unless @randomize_staleness
        self.max_staleness = @original_max_staleness
        # Adds up to 10% of lateness
        self.max_staleness += rand(@original_max_staleness / 10)
      end

      def event_keys(event)
        return [event_key(event)] unless event.is_a?(Sqreen::RequestRecord)
        res = []
        res += event.observed.fetch(:attacks, []).map { |e| "att-#{e[:rule_name]}" }
        res += event.observed.fetch(:sqreen_exceptions, []).map { |e| "rex-#{e[:exception].class}" }
        res += event.observed.fetch(:sdk, []).select { |e|
            e[0] == :track
        }.map { |e| "sdk-track".freeze }
        return res
      end

      def event_key(event)
        case event
        when Sqreen::Attack
          "att-#{event.rule_name}"
        when Sqreen::RemoteException
          "rex-#{event.klass}"
        when Sqreen::AggregatedMetric
          "agg-metric"
        end
      end
    end
  end
end