# frozen_string_literal: true

module SplitIoClient
  module Engine
    module Common
      class ImpressionManager
        def initialize(config,
                       impressions_repository,
                       impression_counter,
                       telemetry_runtime_producer,
                       impression_observer,
                       unique_keys_tracker)
          @config = config
          @impressions_repository = impressions_repository
          @impression_counter = impression_counter
          @impression_observer = impression_observer
          @telemetry_runtime_producer = telemetry_runtime_producer
          @unique_keys_tracker = unique_keys_tracker
        end

        def build_impression(matching_key, bucketing_key, split_name, treatment_data, impressions_disabled, params = {})
          impression_data = impression_data(matching_key, bucketing_key, split_name, treatment_data, params[:time])
          begin
            if @config.impressions_mode == :none || impressions_disabled
              @impression_counter.inc(split_name, impression_data[:m])
              @unique_keys_tracker.track(split_name, matching_key)
            elsif @config.impressions_mode == :debug #  In DEBUG mode we should calculate the pt only.
              impression_data[:pt] = @impression_observer.test_and_set(impression_data)
            else # In OPTIMIZED mode we should track the total amount of evaluations and deduplicate the impressions.
              impression_data[:pt] = @impression_observer.test_and_set(impression_data)
              @impression_counter.inc(split_name, impression_data[:m]) unless impression_data[:pt].nil?
            end
          rescue StandardError => e
            @config.log_found_exception(__method__.to_s, e)
          end

          impression(impression_data, params[:attributes])
        end

        def track(impressions_decorator)
          return if impressions_decorator.empty?

          impressions_decorator.each do |impression_decorator|
            impression_router.add_bulk([impression_decorator[:impression]])
            stats = { dropped: 0, queued: 0, dedupe: 0 }
            begin
              next if @config.impressions_mode == :none || impression_decorator[:disabled]

              if @config.impressions_mode == :debug
                track_debug_mode([impression_decorator[:impression]], stats)
              else
                track_optimized_mode([impression_decorator[:impression]], stats)
              end
            rescue StandardError => e
              @config.log_found_exception(__method__.to_s, e)
            ensure
              record_stats(stats)
            end
          end
        end

        private

        def impression_router
          @impression_router ||= SplitIoClient::ImpressionRouter.new(@config)
        rescue StandardError => e
          @config.log_found_exception(__method__.to_s, e)
        end

        def record_stats(stats)
          return if redis?

          imp_queued = Telemetry::Domain::Constants::IMPRESSIONS_QUEUED
          imp_dropped = Telemetry::Domain::Constants::IMPRESSIONS_DROPPED
          imp_dedupe = Telemetry::Domain::Constants::IMPRESSIONS_DEDUPE
          @telemetry_runtime_producer.record_impressions_stats(imp_queued, stats[:queued]) unless stats[:queued].zero?
          @telemetry_runtime_producer.record_impressions_stats(imp_dropped, stats[:dropped]) unless stats[:dropped].zero?
          @telemetry_runtime_producer.record_impressions_stats(imp_dedupe, stats[:dedupe]) unless stats[:dedupe].zero?
        end

        # added param time for test
        def impression_data(matching_key, bucketing_key, split_name, treatment, time = nil)
          {
            k: matching_key,
            b: bucketing_key,
            f: split_name,
            t: treatment[:treatment],
            r: applied_rule(treatment[:label]),
            c: treatment[:change_number],
            m: time || (Time.now.to_f * 1000.0).to_i,
            pt: nil
          }
        end

        def metadata
          {
            s: "#{@config.language}-#{@config.version}",
            i: @config.machine_ip,
            n: @config.machine_name
          }
        end

        def applied_rule(label)
          @config.labels_enabled ? label : nil
        end

        def should_queue_impression?(impression)
          impression[:pt].nil? ||
            (ImpressionCounter.truncate_time_frame(impression[:pt]) != ImpressionCounter.truncate_time_frame(impression[:m]))
        end

        def impression(impression_data, attributes)
          { m: metadata, i: impression_data, attributes: attributes }
        end

        def redis?
          @config.impressions_adapter.class.to_s == 'SplitIoClient::Cache::Adapters::RedisAdapter'
        end

        def track_debug_mode(impressions, stats)
          stats[:dropped] = @impressions_repository.add_bulk(impressions)
          stats[:queued] = impressions.length - stats[:dropped]
        end

        def track_optimized_mode(impressions, stats)
          optimized_impressions = impressions.select { |imp| should_queue_impression?(imp[:i]) }
          stats[:dedupe] = impressions.length - optimized_impressions.length
          return if optimized_impressions.empty?

          stats[:dropped] = @impressions_repository.add_bulk(optimized_impressions)
          stats[:queued] = optimized_impressions.length - stats[:dropped]
        end
      end
    end
  end
end