# frozen_string_literal: true
# rubocop:todo all

module Mongo
  module CRUD
    class Requirement
      YAML_KEYS = %w(auth minServerVersion maxServerVersion topology topologies serverParameters csfle).freeze

      def initialize(spec)
        spec = spec.dup
        # Legacy tests have the requirements mixed with other test fields
        spec.delete('data')
        spec.delete('tests')

        unless (unhandled_keys = spec.keys - YAML_KEYS).empty?
          raise "Unhandled requirement specification keys: #{unhandled_keys}"
        end

        @min_server_version = spec['minServerVersion']
        @max_server_version = spec['maxServerVersion']
        # topologies is for unified test format.
        # topology is for legacy tests.
        @topologies = if topologies = spec['topology'] || spec['topologies']
          topologies.map do |topology|
            {
              'replicaset' => :replica_set,
              'single' => :single,
              'sharded' => :sharded,
              'sharded-replicaset' => :sharded,
              'load-balanced' => :load_balanced,
            }[topology].tap do |v|
              unless v
                raise "Unknown topology #{topology}"
              end
            end
          end
        else
          nil
        end
        @server_parameters = spec['serverParameters']
        @auth = spec['auth']
        @csfle = !!spec['csfle'] if spec['csfle']
      end

      attr_reader :min_server_version
      attr_reader :max_server_version
      attr_reader :topologies

      # `serverless` is a deprecated field. This module is a crutch to help
      # us through the transition period where some specs still have it.
      module DeprecatedServerless
        def self.prepended(base)
          new_list = [ *base::YAML_KEYS, 'serverless' ].freeze

          base.send(:remove_const, :YAML_KEYS)
          base.send(:const_set, :YAML_KEYS, new_list)
        end

        attr_reader :serverless

        def initialize(spec)
          super
          initialize_serverless(spec)
        end

        private

        def initialize_serverless(spec)
          @serverless = if serverless = spec['serverless']
            case spec['serverless']
            when 'require' then :require
            when 'forbid' then :forbid
            when 'allow' then :allow
            else raise "Unknown serverless requirement: #{serverless}"
            end
          else
            nil
          end

          if @serverless && @serverless != :forbid
            warn "The `serverless` requirement is deprecated."
          end
        end
      end

      prepend DeprecatedServerless

      def short_min_server_version
        if min_server_version
          min_server_version.split('.')[0..1].join('.')
        else
          nil
        end
      end

      def short_max_server_version
        if max_server_version
          max_server_version.split('.')[0..1].join('.')
        else
          nil
        end
      end

      def satisfied?
        cc = ClusterConfig.instance
        ok = true
        if min_server_version
          ok &&= Gem::Version.new(cc.fcv_ish) >= Gem::Version.new(min_server_version)
        end
        if max_server_version
          ok &&= Gem::Version.new(cc.server_version) <= Gem::Version.new(max_server_version)
        end
        if topologies
          ok &&= topologies.include?(cc.topology)
        end
        if @server_parameters
          @server_parameters.each do |k, required_v|
            actual_v = cc.server_parameters[k]
            if actual_v.nil? && !required_v.nil?
              ok = false
            elsif actual_v != required_v
              if Numeric === actual_v && Numeric === required_v
                if actual_v.to_f != required_v.to_f
                  ok = false
                end
              else
                ok = false
              end
            end
          end
        end
        if @auth == true
          ok &&= SpecConfig.instance.auth?
        elsif @auth == false
          ok &&= !SpecConfig.instance.auth?
        end
        if @csfle
          ok &&= !!(ENV['LIBMONGOCRYPT_PATH'] || ENV['FLE'])
          ok &&= Gem::Version.new(cc.fcv_ish) >= Gem::Version.new('4.2.0')
        end
        ok
      end

      def description
        versions = [min_server_version, max_server_version].compact
        if versions.any?
          versions = versions.join('-')
        else
          versions = nil
        end
        topologies = if self.topologies
          self.topologies.map(&:to_s).join(',')
        else
          nil
        end
        [versions, topologies].compact.join('/')
      end
    end
  end
end
