# frozen_string_literal: true

module Dynamoid #:nodoc:
  module Criteria
    class KeyFieldsDetector

      class Query
        def initialize(query_hash)
          @query_hash = query_hash
          @fields_with_operator = query_hash.keys.map(&:to_s)
          @fields = query_hash.keys.map(&:to_s).map { |s| s.split('.').first }
        end

        def contain?(field_name)
          @fields.include?(field_name.to_s)
        end

        def contain_with_eq_operator?(field_name)
          @fields_with_operator.include?(field_name.to_s)
        end
      end

      def initialize(query, source)
        @query = query
        @source = source
        @query = Query.new(query)
        @result = find_keys_in_query
      end

      def key_present?
        @result.present?
      end

      def hash_key
        @result && @result[:hash_key]
      end

      def range_key
        @result && @result[:range_key]
      end

      def index_name
        @result && @result[:index_name]
      end

      private

      def find_keys_in_query
        match_table_and_sort_key ||
          match_local_secondary_index ||
          match_global_secondary_index_and_sort_key ||
          match_table ||
          match_global_secondary_index
      end

      # Use table's default range key
      def match_table_and_sort_key
        return unless @query.contain_with_eq_operator?(@source.hash_key)
        return unless @source.range_key

        if @query.contain?(@source.range_key)
          {
            hash_key: @source.hash_key,
            range_key: @source.range_key
          }
        end
      end

      # See if can use any local secondary index range key
      # Chooses the first LSI found that can be utilized for the query
      def match_local_secondary_index
        return unless @query.contain_with_eq_operator?(@source.hash_key)

        lsi = @source.local_secondary_indexes.values.find do |lsi|
          @query.contain?(lsi.range_key)
        end

        if lsi.present?
          {
            hash_key: @source.hash_key,
            range_key: lsi.range_key,
            index_name: lsi.name,
          }
        end
      end

      # See if can use any global secondary index
      # Chooses the first GSI found that can be utilized for the query
      # GSI with range key involved into query conditions has higher priority
      # But only do so if projects ALL attributes otherwise we won't
      # get back full data
      def match_global_secondary_index_and_sort_key
        gsi = @source.global_secondary_indexes.values.find do |gsi|
          @query.contain_with_eq_operator?(gsi.hash_key) && gsi.projected_attributes == :all &&
            @query.contain?(gsi.range_key)
        end

        if gsi.present?
          {
            hash_key: gsi.hash_key,
            range_key: gsi.range_key,
            index_name: gsi.name,
          }
        end
      end

      def match_table
        return unless @query.contain_with_eq_operator?(@source.hash_key)

        {
          hash_key: @source.hash_key,
        }
      end

      def match_global_secondary_index
        gsi = @source.global_secondary_indexes.values.find do |gsi|
          @query.contain_with_eq_operator?(gsi.hash_key) && gsi.projected_attributes == :all
        end

        if gsi.present?
          {
            hash_key: gsi.hash_key,
            range_key: gsi.range_key,
            index_name: gsi.name,
          }
        end
      end
    end
  end
end