# frozen_string_literal: true

module Appsignal
  module Probes
    # @!visibility private
    class SidekiqProbe
      include Helpers

      class Sidekiq7Adapter
        def self.redis_info
          redis_info = nil
          ::Sidekiq.redis { |c| redis_info = c.info }
          redis_info
        end

        def self.hostname
          host = nil
          ::Sidekiq.redis do |c|
            host = c.config.host
          end
          host
        end
      end

      class Sidekiq6Adapter
        def self.redis_info
          return unless ::Sidekiq.respond_to?(:redis_info)

          ::Sidekiq.redis_info
        end

        def self.hostname
          host = nil
          ::Sidekiq.redis do |c|
            host = c.connection[:host] if c.respond_to? :connection
          end
          host
        end
      end

      # @!visibility private
      attr_reader :config

      # @!visibility private
      def self.sidekiq7_and_greater?
        Gem::Version.new(::Sidekiq::VERSION) >= Gem::Version.new("7.0.0")
      end

      # @!visibility private
      def self.dependencies_present?
        return true if sidekiq7_and_greater?
        return false unless defined?(::Redis::VERSION) # Sidekiq <= 6

        Gem::Version.new(::Redis::VERSION) >= Gem::Version.new("3.3.5")
      end

      def initialize(config = {})
        @config = config
        @cache = {}
        is_sidekiq7 = self.class.sidekiq7_and_greater?
        @adapter = is_sidekiq7 ? Sidekiq7Adapter : Sidekiq6Adapter

        unless config.empty?
          formatted_config =
            config.map { |key, value| "#{key}: #{value.inspect}" }.join(", ")
          config_string = " with config: #{formatted_config}"
        end
        Appsignal.internal_logger.debug("Initializing Sidekiq probe#{config_string}")
        require "sidekiq/api"
      end

      # @api private
      def call
        track_redis_info
        track_stats
        track_queues
      end

      private

      attr_reader :adapter, :cache

      def track_redis_info
        redis_info = adapter.redis_info
        return unless redis_info

        gauge "connection_count", redis_info["connected_clients"]
        gauge "memory_usage", redis_info["used_memory"]
        gauge "memory_usage_rss", redis_info["used_memory_rss"]
      end

      def track_stats
        stats = ::Sidekiq::Stats.new

        gauge "worker_count", stats.workers_size
        gauge "process_count", stats.processes_size
        gauge_delta :jobs_processed, stats.processed do |jobs_processed|
          gauge "job_count", jobs_processed, :status => :processed
        end
        gauge_delta :jobs_failed, stats.failed do |jobs_failed|
          gauge "job_count", jobs_failed, :status => :failed
        end
        gauge "job_count", stats.retry_size, :status => :retry_queue
        gauge_delta :jobs_dead, stats.dead_size do |jobs_dead|
          gauge "job_count", jobs_dead, :status => :died
        end
        gauge "job_count", stats.scheduled_size, :status => :scheduled
        gauge "job_count", stats.enqueued, :status => :enqueued
      end

      def track_queues
        ::Sidekiq::Queue.all.each do |queue|
          gauge "queue_length", queue.size, :queue => queue.name
          # Convert latency from seconds to milliseconds
          gauge "queue_latency", queue.latency * 1_000.0, :queue => queue.name
        end
      end

      # Track a gauge metric with the `sidekiq_` prefix
      def gauge(key, value, tags = {})
        return if value.nil?

        tags[:hostname] = hostname if hostname
        Appsignal.set_gauge "sidekiq_#{key}", value, tags
      end

      def hostname
        return @hostname if defined?(@hostname)

        if config.key?(:hostname)
          @hostname = config[:hostname]
          Appsignal.internal_logger.debug "Sidekiq probe: Using hostname " \
            "config option #{@hostname.inspect} as hostname"
          return @hostname
        end

        host = adapter.hostname
        Appsignal.internal_logger.debug "Sidekiq probe: Using Redis server " \
          "hostname #{host.inspect} as hostname"
        @hostname = host
      end
    end
  end
end
