#!/usr/bin/env ruby
# frozen_string_literal: true

require 'bundler/setup'
require 'redis_cluster_client'

module SinglePipTxDebug
  WAIT_SEC = 2.0

  module_function

  def spawn_single(client, key)
    Thread.new(client, key) do |cli, k|
      role = 'Single'

      loop do
        handle_errors(role) do
          reply = cli.call('incr', k)
          log(role, k, reply)
        end
      ensure
        sleep WAIT_SEC
      end
    rescue StandardError => e
      log(role, :dead, e.class, e.message)
      raise
    end
  end

  def spawn_pipeline(client, key)
    Thread.new(client, key) do |cli, k|
      role = 'Pipeline'

      loop do
        handle_errors(role) do
          reply = cli.pipelined do |pi|
            pi.call('incr', k)
            pi.call('incr', k)
          end

          log(role, k, reply.last)
        end
      ensure
        sleep WAIT_SEC
      end
    rescue StandardError => e
      log(role, :dead, e.class, e.message)
      raise
    end
  end

  def spawn_transaction(client, key)
    Thread.new(client, key) do |cli, k|
      role = 'Transaction'
      i = 0

      loop do
        handle_errors(role) do
          reply = cli.multi(watch: i.odd? ? [k] : nil) do |tx|
            tx.call('incr', k)
            tx.call('incr', k)
          end

          log(role, k, reply.last)
          i += 1
        end
      ensure
        sleep WAIT_SEC
      end
    rescue StandardError => e
      log(role, :dead, e.class, e.message)
      raise
    end
  end

  def handle_errors(role) # rubocop:disable Metrics/AbcSize
    yield
  rescue RedisClient::ConnectionError, RedisClient::Cluster::InitialSetupError, RedisClient::Cluster::NodeMightBeDown => e
    log(role, e.class)
  rescue RedisClient::CommandError => e
    log(role, e.class, e.message)
    raise unless e.message.start_with?('CLUSTERDOWN')
  rescue RedisClient::Cluster::ErrorCollection => e
    log(role, e.class, e.message)
    raise unless e.errors.values.all? do |err|
      err.message.start_with?('CLUSTERDOWN') || err.is_a?(::RedisClient::ConnectionError)
    end
  rescue StandardError => e
    log(role, e.class, e.message)
    raise
  end

  def log(*texts)
    return if texts.nil? || texts.empty?

    message = texts.map { |text| "#{' ' * [15 - text.to_s.size, 0].max}#{text}" }.join(': ')
    print "#{message}\n"
  end
end

nodes = (6379..6384).map { |port| "redis://127.0.0.1:#{port}" }.freeze
clients = Array.new(9) { RedisClient.cluster(nodes: nodes, connect_with_original_config: true).new_client }.freeze
threads = []

Signal.trap(:INT) do
  threads.each(&:exit)
  clients.each(&:close)
  SinglePipTxDebug.log("\nBye bye")
  exit 0
end

%w[single1 single3 single4].each_with_index do |key, i|
  threads << SinglePipTxDebug.spawn_single(clients[i], key)
end

%w[pipeline1 pipeline2 pipeline4].each_with_index do |key, i|
  threads << SinglePipTxDebug.spawn_pipeline(clients[i + 3], key)
end

%w[transaction1 transaction3 transaction4].each_with_index do |key, i|
  threads << SinglePipTxDebug.spawn_transaction(clients[i + 6], key)
end

threads.each(&:join)
