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

return unless RUBY_VERSION < '2.6.0' # TODO: RUBY-714 remove guard w/ EOL of 2.5

require 'contrast/agent/patching/policy/patch_status'
require 'contrast/agent/module_data'
require 'contrast/agent/rewriter'
require 'contrast/components/logger'
require 'contrast/utils/object_share'

module Contrast
  module Agent
    module Assess
      module Policy
        # This is our interface from the Patcher to the Rewriter
        # functionality
        #
        # TODO: RUBY-714 remove w/ EOL of 2.5
        # @deprecated Changes to this class are discouraged as this approach is
        #   being phased out with support for those language versions.
        module RewriterPatch
          extend Contrast::Components::Logger::InstanceMethods


          class << self
            def rewrite_interpolations
              return unless agent_should_rewrite?

              logger.debug_with_time('Running Assess interpolation rewrite') do
                ObjectSpace.each_object(Module) do |mod|
                  rewrite_interpolation(mod)
                end
              end
            end

            def rewrite_interpolation mod, redo_rewrite = false
              return unless should_rewrite?(mod)

              status = Contrast::Agent::Patching::Policy::PatchStatus.get_status(mod)
              return if (status&.rewritten? || status&.rewriting?) && !redo_rewrite

              module_name = mod.cs__name
              return unless module_name

              if module_name.start_with?(Contrast::Utils::ObjectShare::CONTRAST_MODULE_START,
                                         Contrast::Utils::ObjectShare::ANONYMOUS_CLASS_MARKER)

                status.no_rewrite!
                return

              end
              module_data = Contrast::Agent::ModuleData.new(mod, module_name)
              logger.trace_with_time('Rewriting module', module: module_name) do
                Contrast::Agent::Rewriter.rewrite_class(module_data, redo_rewrite)
              end
            rescue StandardError => e
              logger.error('Unable to patch for assess', e, module: mod)
            end

            private

            # Rails is being a jerk, again. It passes in a class before it is
            # done being defined. There is a state where the files have been
            # loaded, but the class definition is not complete, so the
            # methods of the class are not defined despite the class existing
            #
            # To get around this, we have those methods tell us the class
            # isn't ready
            def mid_defining? mod
              mod.instance_variable_defined?(:@cs__defining_class) && mod.instance_variable_get(:@cs__defining_class)
            end

            def agent_should_rewrite?
              return false unless ::Contrast::ASSESS.enabled?
              return false unless ::Contrast::AGENT.rewrite_interpolation?
              return false unless ::Contrast::AGENT.interpolation_enabled?

              true
            end

            def should_rewrite? mod
              return false unless agent_should_rewrite?
              return false if ::Contrast::AGENT.skip_instrumentation? mod.cs__name
              return false if mod.cs__frozen?
              return false if mod.singleton_class?
              return false if mid_defining?(mod)

              true
            end
          end
        end
      end
    end
  end
end