# frozen_string_literal: true

class Mastodon::RedisConfiguration
  DEFAULTS = {
    host: 'localhost',
    port: 6379,
    db: 0,
  }.freeze

  def base
    @base ||= setup_config(prefix: nil, defaults: DEFAULTS)
              .merge(namespace: base_namespace)
  end

  def sidekiq
    @sidekiq ||= setup_config(prefix: 'SIDEKIQ_')
                 .merge(namespace: sidekiq_namespace)
  end

  def cache
    @cache ||= setup_config(prefix: 'CACHE_')
               .merge({
                 namespace: cache_namespace,
                 expires_in: 10.minutes,
                 connect_timeout: 5,
                 pool: {
                   size: Sidekiq.server? ? Sidekiq[:concurrency] : Integer(ENV['MAX_THREADS'] || 5),
                   timeout: 5,
                 },
               })
  end

  private

  def driver
    ENV['REDIS_DRIVER'] == 'ruby' ? :ruby : :hiredis
  end

  def namespace
    @namespace ||= ENV.fetch('REDIS_NAMESPACE', nil)
  end

  def base_namespace
    return "mastodon_test#{ENV.fetch('TEST_ENV_NUMBER', nil)}" if Rails.env.test?

    namespace
  end

  def sidekiq_namespace
    namespace
  end

  def cache_namespace
    namespace ? "#{namespace}_cache" : 'cache'
  end

  def setup_config(prefix: nil, defaults: {})
    prefix = "#{prefix}REDIS_"

    url      = ENV.fetch("#{prefix}URL", nil)
    user     = ENV.fetch("#{prefix}USER", nil)
    password = ENV.fetch("#{prefix}PASSWORD", nil)
    host     = ENV.fetch("#{prefix}HOST", defaults[:host])
    port     = ENV.fetch("#{prefix}PORT", defaults[:port])
    db       = ENV.fetch("#{prefix}DB", defaults[:db])

    return { url:, driver: } if url

    sentinel_options = setup_sentinels(prefix, default_user: user, default_password: password)

    if sentinel_options.present?
      host = sentinel_options[:name]
      port = nil
      db ||= 0
    end

    url = construct_uri(host, port, db, user, password)

    if url.present?
      { url:, driver: }.merge(sentinel_options)
    else
      # Fall back to base config, which has defaults for the URL
      # so this cannot lead to endless recursion.
      base
    end
  end

  def setup_sentinels(prefix, default_user: nil, default_password: nil)
    name              = ENV.fetch("#{prefix}SENTINEL_MASTER", nil)
    sentinel_port     = ENV.fetch("#{prefix}SENTINEL_PORT", 26_379)
    sentinel_list     = ENV.fetch("#{prefix}SENTINELS", nil)
    sentinel_username = ENV.fetch("#{prefix}SENTINEL_USERNAME", default_user)
    sentinel_password = ENV.fetch("#{prefix}SENTINEL_PASSWORD", default_password)

    sentinels = parse_sentinels(sentinel_list, default_port: sentinel_port)

    if name.present? && sentinels.present?
      { name:, sentinels:, sentinel_username:, sentinel_password: }
    else
      {}
    end
  end

  def construct_uri(host, port, db, user, password)
    return nil if host.blank?

    Addressable::URI.parse("redis://#{host}:#{port}/#{db}").tap do |uri|
      uri.user = user if user.present?
      uri.password = password if password.present?
    end.normalize.to_str
  end

  def parse_sentinels(sentinels_string, default_port: 26_379)
    (sentinels_string || '').split(',').map do |sentinel|
      host, port = sentinel.split(':')
      port = (port || default_port).to_i
      { host: host, port: port }
    end.presence
  end
end
