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

require 'contrast/api/communication/response_processor'
require 'contrast/api/decorators/application_settings'
require 'contrast/agent/reporting/reporting_utilities/response'
require 'contrast/agent/reporting/reporting_utilities/response_handler_utils'
require 'contrast/agent/reporting/reporting_utilities/response_handler_mode'
require 'contrast/api/decorators/server_features'
require 'contrast/components/logger'
require 'json'

module Contrast
  module Agent
    module Reporting
      # This class will facilitate the Response capture and analysis functionality.
      class ResponseHandler
        include Contrast::Components::Logger::InstanceMethods
        include Contrast::Agent::Reporting::ResponseHandlerUtils
        # 15 min
        TIMEOUT = 900.cs__freeze

        # Process the response from TS
        #
        # @param response [Net::HTTP::Response, nil]
        # @return response [Net::HTTP::Response, nil]
        def process response
          logger.debug('Reporter Received a response')
          return unless analyze_response?(response)

          # Handle the response body and obtain server_features or app_settings
          report_response = convert_response(response)
          return unless report_response

          # Update Server Features and Application Settings to provide current agent settings
          update_agent_settings(report_response)
          # Process any reactions, including message logging and shutting down
          update_reaction(report_response)
          update_ruleset(report_response)
          logger.trace('Agent settings updated in response to TeamServer', protect_on: ::Contrast::PROTECT.enabled?,
                                                                           assess_on: ::Contrast::ASSESS.enabled?)
          response
        rescue StandardError => e
          logger.error('Unable to process response from TeamServer', e)
          nil
        end

        # If sleep is true puts reporting service to sleep.
        def sleep?
          @_sleep = wake_up if @_sleep.nil?
          @_sleep
        end

        # For how long the agent should wait until retry
        # to send message
        #
        # @return timeout [Integer] the time for reporter
        # to be suspended.
        def timeout
          @_timeout ||= TIMEOUT
        end

        def mode
          @_mode ||= Contrast::Agent::Reporting::ResponseHandlerMode.new
        end

        # Wakes the reporting service
        def wake_up
          @_sleep = false
        end

        private

        # Handles the errors code received from TS and takes appropriate action.
        # If we are here the response.code is an error that needs handling [4XX]
        #
        # @param response [Net::HTTP::Response]
        def handle_error response
          case response&.code
          when ERROR_CODES[:message_not_sent]
            handle_response_errors(response, UNSUCCESSFULLY_RECEIVED_MSG, mode.running)
          when ERROR_CODES[:access_forbidden]
            handle_response_errors(response, FORBIDDEN_MSG, mode.running)
          when ERROR_CODES[:access_forbidden_no_action]
            handle_response_errors(response, FORBIDDEN_NO_ACTION_MSG, mode.running)
          when ERROR_CODES[:application_do_not_exist]
            handle_response_errors(response, APP_NON_EXISTENT_MSG, mode.disabled)
          when ERROR_CODES[:unprocessable_entity]
            handle_response_errors(response, UNPROCESSABLE_ENTITY_MSG, mode.disabled)
          when ERROR_CODES[:too_many_requests]
            handle_response_errors(response, RETRY_AFTER_MSG, mode.resending)
          else
            logger.error('Response Error code could not be processed')
          end
        end

        # Suspend the reporter and try again in time. If not set
        # the timeout is set to 15 min As default:
        #
        # @param message [String] Message to log.
        # @param timeout [Integer,nil] The timeout to wait and retry after.
        # @param error_message [String, nil] Error message if any received.
        def suspend_reporting message, timeout, error_message
          @_timeout = timeout || Contrast::Agent::Reporting::ResponseHandler::TIMEOUT
          log_debug_msg(message, timeout: @_timeout, error_message: error_message || 'none')
          @_sleep = true
        end

        # Log what we've received.
        #
        # @param message [String] Message to log
        # @param info_hash [Hash] information about the context to log.
        def log_debug_msg message, info_hash
          logger.debug(message, info_hash)
        end
      end
    end
  end
end