# frozen_string_literal: true

require 'bson'
require 'date'
require 'opentracing/instrumentation/mongo/direct_sanitazer'

module OpenTracing
  module Instrumentation
    module Mongo
      # QuerySanitazer clean private data from requests.
      class QuerySanitazer < DirectSanitazer
        DEFAULT_SAFE_CLASSES = [
          TrueClass,
          FalseClass,
          Numeric,
          Date,
          Time,
        ].freeze
        DEFAULT_EXCLUDE_KEYS = %w[lsid].freeze

        def initialize(
          safety_argument_checker: SampleSafetyArgumentChecker.new,
          safe_classes: DEFAULT_SAFE_CLASSES,
          exclude_keys: DEFAULT_EXCLUDE_KEYS
        )
          super()
          @safety_argument_checker = safety_argument_checker
          @safe_classes = safe_classes
          @exclude_keys = exclude_keys
        end

        def sanitaze(command, command_name)
          command_without_command_name = super(command, command_name)
          exclude_keys.each do |key|
            command_without_command_name.delete(key)
          end
          sanitaze_value(command_without_command_name)
        end

        private

        attr_reader :safe_classes,
                    :exclude_keys,
                    :safety_argument_checker

        OBJECT_ID_PLACEHOLDER = '$oid'
        STRING_PLACEHOLDER = '$string'
        PLACEHOLDER = '?'

        def sanitaze_value(value)
          case value
          when Hash
            sanitaze_hash(value)
          when Enumerable
            sanitaze_array(value)
          else
            sanitaze_simple(value)
          end
        end

        def sanitaze_simple(value)
          case value
          when BSON::ObjectId
            OBJECT_ID_PLACEHOLDER
          when String
            STRING_PLACEHOLDER
          when *safe_classes
            value
          else
            PLACEHOLDER
          end
        end

        def sanitaze_hash(hash)
          hash.map do |(key, value)|
            # TODO: pass command name.
            # TODO: recursive build path to key
            safe_value =
              if safety_argument_checker.argument_safe?(nil, key, value)
                value
              else
                sanitaze_value(value)
              end

            [key, safe_value]
          end.to_h
        end

        def sanitaze_array(array)
          array.map do |value|
            sanitaze_value(value)
          end
        end
      end
    end
  end
end