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

require 'contrast/agent/worker_thread'
require 'contrast/agent/reporting/report'
require 'contrast/components/logger'
require 'contrast/agent/reporting/reporting_events/agent_startup'

module Contrast
  module Agent
    # This module will hold everything essential to reporting to TeamServer
    class Reporter < WorkerThread
      include Contrast::Components::Logger::InstanceMethods
      include Contrast::Utils::ObjectShare

      class << self
        # check if we can report to TS
        #
        # @return[Boolean] true if bypass is enabled, or false if bypass disabled
        def enabled?
          @_enabled = Contrast::CONTRAST_SERVICE.use_agent_communication? if @_enabled.nil?
          @_enabled
        end
      end

      def client
        @_client ||= Contrast::Agent::Reporting::ReporterClient.new
      end

      def connection
        @_connection ||= client.initialize_connection
      end

      def start_thread!
        return if running?

        client.startup!(connection)
        @_thread = Contrast::Agent::Thread.new do
          logger.debug('Starting background Reporter thread.')
          loop do
            next unless connected?
            next unless app_create_complete?

            process_event(queue.pop)
          rescue StandardError => e
            logger.debug('Reporter thread could not process because of:', e)
          end
        end
      end

      # Suspend the Reporter and try sending the event after the timeout.
      # The timeout is either default 15 min or received via TS response.
      #
      # @param event [Contrast::Agent::Reporting::ReportingEvent] Freshly pop-ed event.
      def handle_resend event
        sleep(client.timeout) if client.sleep?
        # Retry once than discard the event. This is trigger on too many events of
        # same kind error.
        client.send_event(event, connection) if client.mode.status == client.mode.resending
        client.mode.reset_mode
        client.wake_up
      end

      # @param event [Contrast::Agent::Reporting::ReportingEvent]
      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 event

        queue << event
      end

      # Use this to bypass the messaging queue and leave response processing to the caller
      #
      # @param event [Contrast::Agent::Reporting::ReportingEvent]
      # @return [Net::HTTPResponse, nil]
      def send_event_immediately event
        if ::Contrast::AGENT.disabled?
          logger.warn('Reporter attempted to send event immediately with Agent disabled', caller: caller, event: event)
          return
        end
        return unless event

        client.send_event(event, connection)
      rescue StandardError => e
        logger.error('Could not send message to TeamServer from Reporter queue.', e)
      end

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

      def stop!
        return unless running?

        super
        delete_queue!
      end

      private

      def queue
        @_queue ||= Queue.new
      end

      # TODO: RUBY-99999
      # The client and connection are being used in multiple threads/ concurrently, and that's not okay. We need
      # to figure out why that is and lock it so that it isn't.
      #
      # @return [Boolean]
      def connected?
        return true if client && connection

        logger.debug('No client/connection; sleeping', client: client, connection: connection)
        sleep(5)
        false
      end

      # Unless we're in bypass mode, we need to make sure the service has started and built the application on
      # TeamServer since we're doing a split style here.
      #
      # @return [Boolean]
      def app_create_complete?
        return true if Contrast::CONTRAST_SERVICE.use_agent_communication?
        return true if Contrast::Agent.messaging_queue&.speedracer&.status&.startup_messages_sent?

        logger.debug('Service startup incomplete; Application may not be created; sleeping')
        sleep(5)
        false
      end

      # @param event [Contrast::Agent::Reporting::ReportingEvent]
      def process_event event
        client.send_event(event, connection)
        handle_resend(event) if client.mode.status == client.mode.resending
      rescue StandardError => e
        logger.error('Could not send message to TeamServer from Reporter queue.', e)
      end
    end
  end
end