# frozen_string_literal: true

# This implementation is taken directly from https://github.com/rails/rails/blob/52ce6ece8c8f74064bb64e0a0b1ddd83092718e1/actionpack/lib/action_dispatch/http/parameter_filter.rb
# Adding actionpack to the gem dependencies would have been too heavy, so here is just what we need.
module Sidekiq
  module Logging
    # Class that allows to filter-out sensible arguments.
    class ArgumentFilter
      # String used to replace sensible arguments.
      FILTERED = '[FILTERED]'

      def initialize(filters = [])
        @filters = filters
      end

      # Filters argument by using the filters provided upon initialization.
      # @param args [Hash] the list of arguments in a hash with key :args.
      def filter(args)
        compiled_filter.call(args)
      end

      private

      def compiled_filter
        @compiled_filter ||= CompiledFilter.compile(@filters)
      end

      class CompiledFilter # :nodoc:
        def self.compile(filters)
          return lambda(&:dup) if filters.empty?

          strings = []
          regexps = []
          blocks = []
          filters.each do |item|
            case item
            when Proc
              blocks << item
            when Regexp
              regexps << item
            else
              strings << Regexp.escape(item.to_s)
            end
          end
          deep_regexps, regexps = regexps.partition { |r| r.to_s.include?('\\.') }
          deep_strings, strings = strings.partition { |s| s.include?('\\.') }
          regexps << Regexp.new(strings.join('|'), true) unless strings.empty?
          deep_regexps << Regexp.new(deep_strings.join('|'), true) unless deep_strings.empty?
          new regexps, deep_regexps, blocks
        end

        attr_reader :regexps, :deep_regexps, :blocks

        def initialize(regexps, deep_regexps, blocks)
          @regexps      = regexps
          @deep_regexps = deep_regexps.any? ? deep_regexps : nil
          @blocks       = blocks
        end

        def call(original_args, parents = [])
          filtered_args = {}
          original_args.each do |key, value|
            parents.push(key) if deep_regexps
            if regexps.any? { |r| key =~ r }
              value = FILTERED
            elsif deep_regexps && (joined = parents.join('.')) && deep_regexps.any? { |r| joined =~ r } # rubocop:disable Lint/DuplicateBranch
              value = FILTERED
            elsif value.is_a?(Hash)
              value = call(value, parents)
            elsif value.is_a?(Array)
              value = value.map { |v| v.is_a?(Hash) ? call(v, parents) : v }
            elsif blocks.any?
              key = begin
                key.dup
              rescue StandardError
                key
              end
              value = begin
                value.dup
              rescue StandardError
                value
              end
              blocks.each { |b| b.call(key, value) }
            end
            parents.pop if deep_regexps
            filtered_args[key] = value
          end
          filtered_args
        end
      end
    end
  end
end
