# Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
# frozen_string_literal: true

require 'contrast/config/env_variables'
require 'contrast/components/logger'
require 'contrast/utils/telemetry_client'
require 'contrast/agent/worker_thread'
require 'contrast/utils/telemetry'
require 'contrast/agent/telemetry/events/exceptions/telemetry_exceptions'

module Contrast
  module Agent
    module Telemetry
      # This class will initialize and hold everything needed for the telemetry
      class Base < WorkerThread
        include Contrast::Components::Logger::InstanceMethods

        # this is where we will send the data from the agents
        URL = 'https://telemetry.ruby.contrastsecurity.com/'
        # Suggested timeout after each send is to be 3 hours (10800 seconds)
        SUGGESTED_TIMEOUT = 10_800

        class << self
          include Contrast::Components::Logger::InstanceMethods
          include Contrast::Config::EnvVariables

          def application_id
            Contrast::Utils::Telemetry::Identifier.application_id
          end

          def instance_id
            Contrast::Utils::Telemetry::Identifier.instance_id
          end

          def enabled?
            @_enabled = telemetry_enabled? if @_enabled.nil?
            @_enabled
          end

          private

          def telemetry_enabled?
            opt_out_telemetry = return_value(:telemetry_opt_outs).to_s
            return false if opt_out_telemetry.casecmp?('true') || opt_out_telemetry == '1'

            # In case of connection error, do not create the background thread or queue,
            # as if the opt-out env var was set
            @_client = Contrast::Utils::TelemetryClient.new
            ip_opt_out_telemetry = @_client.initialize_connection(URL)
            if ip_opt_out_telemetry.nil?
              logger.warn("Connection was not established properly!!! \n Telemetry reporting will be disabled!")
              return false
            end

            true
          end
        end

        def client
          @_client ||= Contrast::Utils::TelemetryClient.new
        end

        def connection
          @_connection ||= client.initialize_connection(URL)
        end

        def attempt_to_start?
          unless cs__class.enabled?
            logger.warn('Telemetry service is disabled!')
            return false
          end

          logger.debug('Attempting to start telemetry thread') unless running?
          true
        end

        def start_thread!
          return if running?

          logger.debug('Starting background telemetry thread.')
          @_thread = create_thread
        end

        def send_event event
          if ::Contrast::AGENT.disabled?
            logger.warn('Attempted to queue event with Agent disabled', caller: caller, event: event)
            return
          end

          return unless cs__class.enabled?

          logger.debug('Enqueued event for sending', event_type: event.cs__class)
          queue << event if event
        end

        def delete_queue!
          @_queue&.close
          @_queue = nil
        end

        def stop!
          return unless running?

          @_enabled = false
          delete_queue!
          super
        end

        def request_with_response event
          res = client.send_request(event, connection)
          client.handle_response(res)
        end

        private

        def queue
          @_queue ||= Queue.new
        end

        # It is recommended that implementations send a single payload of general metrics every 3 hours, starting from
        # implementation startup. This returns a thread configured to do so.
        #
        # @return [Contrast::Agent::Thread]
        def create_thread
          Contrast::Agent::Thread.new do
            loop do
              next unless client && connection

              # Start pushing exceptions to queue for reporting.
              Contrast::TELEMETRY_EXCEPTIONS.each_value { |value| queue << value }
              Contrast::TELEMETRY_EXCEPTIONS.clear
              until queue.empty?
                event = queue.pop
                begin
                  logger.debug('This is the current processed event', event)
                  sleep_time = request_with_response(event)
                  if sleep_time
                    sleep(sleep_time)
                    logger.debug('Retrying to process event', event)
                    retry_sleep_time = request_with_response(event)
                    sleep(retry_sleep_time) unless retry_sleep_time.nil?
                  end
                rescue StandardError => e
                  logger.error('Could not send message to service from telemetry queue.', e)
                  stop!
                end
              end
              sleep(SUGGESTED_TIMEOUT)
            end
          end
        end
      end
    end
  end
end