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

module Contrast
  module Utils
    # helper methods for Contrast::Agent::Middleware
    # including disclaimers, deprecation notices, error handling, setup
    module MiddlewareUtils
      private

      # TODO: RUBY-1609 update this part of the method for Ruby Version and Year
      LANGUAGE_DEPRECATION_VERSION = '2.7'
      LANGUAGE_DEPRECATION_YEAR = '2023'
      LANGUAGE_DEPRECATION_WARNING =
        "[Contrast Security] [DEPRECATION] Support for Ruby #{ LANGUAGE_DEPRECATION_VERSION } will be removed in "\
        "April #{ LANGUAGE_DEPRECATION_YEAR }. Please contact Customer Support prior if you require continued support."

      def setup_agent
        ::Contrast::SETTINGS.reset_state

        inform_deprecations
        telemetry_disclaimer

        if ::Contrast::CONFIG.invalid?
          ::Contrast::AGENT.disable!
          logger.error('!!! CONFIG FILE IS INVALID - DISABLING CONTRAST AGENT !!!')
        elsif ::Contrast::AGENT.disabled?
          logger.warn('Contrast disabled by configuration. Continuing without instrumentation.')
        else
          ::Contrast::AGENT.enable!
        end
      end

      SECURITY_EXCEPTION_MARKER = 'Contrast::SecurityException'
      # We're only going to suppress SecurityExceptions indicating a blocked attack. And, only if the
      # config.agent.ruby.exceptions.capture? is set
      def handle_exception exception
        if security_exception?(exception)
          exception_control = ::Contrast::AGENT.exception_control
          raise exception unless exception_control[:enable]

          [exception_control[:status], {}, [exception_control[:message]]]
        else
          logger.debug('Re-throwing original error', exception)
          raise exception
        end
      end

      # Is the given exception one raised by our Protect code?
      #
      # @param exception [Exception]
      # @return [Boolean]
      def security_exception? exception
        exception.is_a?(Contrast::SecurityException) || exception.message&.include?(SECURITY_EXCEPTION_MARKER)
      end

      # As we deprecate support to prepare to remove dead code, we need to inform our users still relying on the now
      # deprecated and soon to be removed functionality. This method handles doing that by leveraging the standard
      # Kernel#warn approach
      def inform_deprecations
        # Warn customers that they're on a version that's losing support in the next year.
        Kernel.warn(LANGUAGE_DEPRECATION_WARNING) if RUBY_VERSION.start_with?(LANGUAGE_DEPRECATION_VERSION)
      end

      # Displays Telemetry disclaimer if Telemetry is enabled.
      # if .telemetry file doesn't exist we create one and then show the disclaimer.
      # if the file already exists we do nothing.
      def telemetry_disclaimer
        return unless Contrast::Agent::Telemetry::Base.enabled?
        return unless Contrast::Utils::Telemetry.create_telemetry_file

        logger.info Contrast::Utils::Telemetry.disclaimer
        $stdout.print Contrast::Utils::Telemetry.disclaimer
        true
      end

      def application_code env
        logger.trace_with_time('application') do
          app.call(env)
        end
      rescue Contrast::SecurityException => e
        logger.trace('Security Exception raised during application lifecycle to prevent an attack', e)
        raise e
      end
    end
  end
end