# frozen_string_literal: true

require "active_support"
require "active_support/core_ext"

require "jaeger/client"

module Labkit
  module Tracing
    # JaegerFactory will configure Jaeger distributed tracing
    class JaegerFactory
      # When the probabilistic sampler is used, by default 0.1% of requests will be traced
      DEFAULT_PROBABILISTIC_RATE = 0.001

      # The default port for the Jaeger agent UDP listener
      DEFAULT_UDP_PORT = 6831

      # Reduce this from default of 10 seconds as the Ruby jaeger
      # client doesn't have overflow control, leading to very large
      # messages which fail to send over UDP (max packet = 64k)
      # Flush more often, with smaller packets
      FLUSH_INTERVAL = 5

      def self.create_tracer(service_name, options)
        # The service_name parameter from GITLAB_TRACING takes precedence over the application one
        service_name = options[:service_name] if options[:service_name]

        # parse reporter headers as necessary
        headers = build_headers(options)

        kwargs = {
          service_name: service_name,
          sampler: get_sampler(options[:sampler], options[:sampler_param]),
          reporter: get_reporter(service_name, options[:http_endpoint], options[:udp_endpoint], headers),
        }.compact

        extra_params = options.except(:sampler, :sampler_param, :http_endpoint, :udp_endpoint, :strict_parsing, :debug)
        if extra_params.present?
          message = "jaeger tracer: invalid option: #{extra_params.keys.join(", ")}"

          raise message if options[:strict_parsing]

          warn message
        end

        Jaeger::Client.build(**kwargs)
      end

      def self.build_headers(options)
        return unless options&.key?(:http_endpoint)

        http_endpoint = options[:http_endpoint]
        parsed = URI.parse(http_endpoint)

        headers = {}
        # add basic auth header only when both user and password are setup correctly
        user = parsed.user
        password = parsed.password
        if user.present? && password.present?
          headers["Authorization"] = "Basic " + Base64.strict_encode64("#{user}:#{password}")
        end

        return headers
      end
      private_class_method :build_headers

      def self.get_sampler(sampler_type, sampler_param)
        case sampler_type
        when "probabilistic"
          sampler_rate = sampler_param ? sampler_param.to_f : DEFAULT_PROBABILISTIC_RATE
          Jaeger::Samplers::Probabilistic.new(rate: sampler_rate)
        when "const"
          const_value = sampler_param == "1"
          Jaeger::Samplers::Const.new(const_value)
        end
      end
      private_class_method :get_sampler

      def self.get_reporter(service_name, http_endpoint, udp_endpoint, headers)
        encoder = Jaeger::Encoders::ThriftEncoder.new(service_name: service_name)

        if http_endpoint.present?
          sender = get_http_sender(encoder, http_endpoint, headers)
        elsif udp_endpoint.present?
          sender = get_udp_sender(encoder, udp_endpoint)
        else
          return nil
        end

        Jaeger::Reporters::RemoteReporter.new(sender: sender, flush_interval: FLUSH_INTERVAL)
      end
      private_class_method :get_reporter

      def self.get_http_sender(encoder, address, headers)
        Jaeger::HttpSender.new(url: address, headers: headers, encoder: encoder, logger: Logger.new(STDOUT))
      end
      private_class_method :get_http_sender

      def self.get_udp_sender(encoder, address)
        pair = address.split(":", 2)
        host = pair[0]
        port = pair[1] ? pair[1].to_i : DEFAULT_UDP_PORT

        Jaeger::UdpSender.new(host: host, port: port, encoder: encoder, logger: Logger.new(STDOUT))
      end
      private_class_method :get_udp_sender
    end
  end
end
