# frozen_string_literal: true

require 'memory_profiler'
require 'redis_cluster_client'
require 'testing_constants'

module ProfMem
  module_function

  ATTEMPT_COUNT = 1000
  MAX_PIPELINE_SIZE = 100
  SLICED_NUMBERS = (1..ATTEMPT_COUNT).each_slice(MAX_PIPELINE_SIZE).freeze
  ORIGINAL_MGET = (%w[mget] + Array.new(40) { |i| "{key}#{i}" }).freeze
  EMULATED_MGET = (%w[mget] + Array.new(40) { |i| "key#{i}" }).freeze
  CLI_TYPES = %w[primary_only scale_read_random scale_read_latency pooled].freeze
  MODES = {
    single: lambda do |client_builder_method|
      cli = send(client_builder_method)
      ATTEMPT_COUNT.times { |i| cli.call('set', i, i) }
      ATTEMPT_COUNT.times { |i| cli.call('get', i) }
    end,
    excessive_pipelining: lambda do |client_builder_method|
      cli = send(client_builder_method)
      cli.pipelined do |pi|
        ATTEMPT_COUNT.times { |i| pi.call('set', i, i) }
      end

      cli.pipelined do |pi|
        ATTEMPT_COUNT.times { |i| pi.call('get', i) }
      end
    end,
    pipelining_in_moderation: lambda do |client_builder_method|
      cli = send(client_builder_method)
      SLICED_NUMBERS.each do |numbers|
        cli.pipelined do |pi|
          numbers.each { |i| pi.call('set', i, i) }
        end

        cli.pipelined do |pi|
          numbers.each { |i| pi.call('get', i) }
        end
      end
    end,
    original_mget: lambda do |client_builder_method|
      cli = send(client_builder_method)
      ATTEMPT_COUNT.times { cli.call_v(ORIGINAL_MGET) }
    end,
    emulated_mget: lambda do |client_builder_method|
      cli = send(client_builder_method)
      ATTEMPT_COUNT.times { cli.call_v(EMULATED_MGET) }
    end
  }.freeze

  def run
    mode = ENV.fetch('PROFILE_MODE', :single).to_sym
    subject = MODES.fetch(mode)

    CLI_TYPES.each do |cli_type|
      prepare
      print_letter(mode, cli_type)
      client_builder_method = "new_#{cli_type}_client".to_sym
      profile { subject.call(client_builder_method) }
    end
  end

  def prepare; end

  def print_letter(title, sub_titile)
    print "################################################################################\n"
    print "# #{title}: #{sub_titile}\n"
    print "################################################################################\n"
    print "\n"
  end

  def profile(&block)
    # https://github.com/SamSaffron/memory_profiler
    report = ::MemoryProfiler.report(top: 20, &block)
    report.pretty_print(color_output: true, normalize_paths: true)
  end

  def new_primary_only_client
    ::RedisClient.cluster(
      nodes: TEST_NODE_URIS,
      fixed_hostname: TEST_FIXED_HOSTNAME,
      **TEST_GENERIC_OPTIONS
    ).new_client
  end

  def new_scale_read_random_client
    ::RedisClient.cluster(
      nodes: TEST_NODE_URIS,
      replica: true,
      replica_affinity: :random,
      fixed_hostname: TEST_FIXED_HOSTNAME,
      **TEST_GENERIC_OPTIONS
    ).new_client
  end

  def new_scale_read_latency_client
    ::RedisClient.cluster(
      nodes: TEST_NODE_URIS,
      replica: true,
      replica_affinity: :latency,
      fixed_hostname: TEST_FIXED_HOSTNAME,
      **TEST_GENERIC_OPTIONS
    ).new_client
  end

  def new_pooled_client
    ::RedisClient.cluster(
      nodes: TEST_NODE_URIS,
      fixed_hostname: TEST_FIXED_HOSTNAME,
      **TEST_GENERIC_OPTIONS
    ).new_pool(
      timeout: TEST_TIMEOUT_SEC,
      size: 2
    )
  end
end

ProfMem.run
