# SPDX-License-Identifier: Apache-2.0
#
# The OpenSearch Contributors require contributions made to
# this file be licensed under the Apache-2.0 license or a
# compatible open source license.
#
# Modifications Copyright OpenSearch Contributors. See
# GitHub history for details.
#
# Licensed to Elasticsearch B.V. under one or more contributor
# license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright
# ownership. Elasticsearch B.V. licenses this file to you under
# the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.

require_relative '../../../../spec_helper'

describe OpenSearch::DSL::Search::Queries::Bool do
  describe '#to_hash' do
    let(:search) do
      described_class.new
    end

    it 'can be converted to a hash' do
      expect(search.to_hash).to eq(bool: {})
    end
  end

  describe '#initialize' do
    context 'when an object instance is provided' do
      let(:search) do
        described_class.new.must(OpenSearch::DSL::Search::Queries::Match.new(foo: 'bar'))
      end

      it 'applies the condition' do
        expect(search.to_hash).to eq(bool: { must: [{ match: { foo: 'bar' } }] })
      end

      context 'when multiple option methods are called' do
        let(:search) do
          described_class.new do
            should(OpenSearch::DSL::Search::Queries::Term.new(tag: 'wow'))
            should(OpenSearch::DSL::Search::Queries::Term.new(tag: 'opensearch'))

            minimum_should_match 1
            boost 1.0
          end
        end

        it 'defines all the options' do
          expect(search.to_hash).to eq(bool: {
                                         minimum_should_match: 1,
                                         boost: 1.0,
                                         should: [{ term: { tag: 'wow' } }, { term: { tag: 'opensearch' } }]
                                       })
        end
      end

      context 'when multiple conditions are provided' do
        let(:search) do
          described_class.new do
            must(OpenSearch::DSL::Search::Queries::Match.new(foo: 'bar'))
            must(OpenSearch::DSL::Search::Queries::Match.new(moo: 'bam'))

            should(OpenSearch::DSL::Search::Queries::Match.new(xoo: 'bax'))
            should(OpenSearch::DSL::Search::Queries::Match.new(zoo: 'baz'))
          end
        end

        it 'applies each condition' do
          expect(search.to_hash).to eq(bool:
                                         { must: [{ match: { foo: 'bar' } }, { match: { moo: 'bam' } }],
                                           should: [{ match: { xoo: 'bax' } }, { match: { zoo: 'baz' } }] })
        end

        context 'when #to_hash is called more than once' do
          it 'does not alter the hash' do
            expect(search.to_hash).to eq(search.to_hash)
          end
        end
      end
    end

    context 'when a block is provided' do
      let(:search) do
        described_class.new do
          must { match foo: 'bar' }
        end
      end

      it 'executes the block' do
        expect(search.to_hash).to eq(bool: { must: [{ match: { foo: 'bar' } }] })
      end

      context 'when multiple option methods are called' do
        let(:search) do
          described_class.new do
            should   { term tag: 'wow' }
            should   { term tag: 'opensearch' }

            minimum_should_match 1
            boost 1.0
          end
        end

        it 'defines all the options' do
          expect(search.to_hash).to eq(bool: {
                                         minimum_should_match: 1,
                                         boost: 1.0,
                                         should: [{ term: { tag: 'wow' } }, { term: { tag: 'opensearch' } }]
                                       })
        end
      end

      context 'when multiple conditions are provided' do
        let(:search) do
          described_class.new do
            must do
              match foo: 'bar'
            end

            must do
              match moo: 'bam'
            end

            should do
              match xoo: 'bax'
            end

            should do
              match zoo: 'baz'
            end
          end
        end

        it 'applies each condition' do
          expect(search.to_hash).to eq(bool:
                                           { must: [{ match: { foo: 'bar' } }, { match: { moo: 'bam' } }],
                                             should: [{ match: { xoo: 'bax' } }, { match: { zoo: 'baz' } }] })
        end

        context 'when #to_hash is called more than once' do
          it 'does not alter the hash' do
            expect(search.to_hash).to eq(search.to_hash)
          end
        end
      end
    end
  end

  context 'when options methods are called' do
    let(:search) do
      described_class.new
    end

    before do
      search.must { match foo: 'bar' }
      search.must { match moo: 'bam' }
      search.should { match xoo: 'bax' }
    end

    it 'applies the option' do
      expect(search.to_hash).to eq(bool: { must: [{ match: { foo: 'bar' } }, { match: { moo: 'bam' } }],
                                           should: [{ match: { xoo: 'bax' } }] })
    end
  end

  context 'when the filter method is called multiple times' do
    let(:search) do
      described_class.new
    end

    before do
      search.filter { term foo: 'bar' }
      search.filter { term zoo: 'baz' }
    end

    it 'combines the filter clauses' do
      expect(search.to_hash).to eq(bool:
                                       { filter: [
                                         { term: { foo: 'bar' } },
                                         { term: { zoo: 'baz' } }
                                       ] })
    end
  end

  context 'when methods are chained' do
    let(:search) do
      described_class.new
    end

    before do
      search.must { match foo: 'bar' }.must { match moo: 'bam' }.should { match xoo: 'bax' }
    end

    it 'applies the option' do
      expect(search.to_hash).to eq(bool: { must: [{ match: { foo: 'bar' } }, { match: { moo: 'bam' } }],
                                           should: [{ match: { xoo: 'bax' } }] })
    end
  end

  describe '#filter' do
    context 'when a block is used to define the filter' do
      let(:search) do
        described_class.new do
          filter do
            term foo: 'Foo!'
          end
        end
      end

      it 'applies the filter' do
        expect(search.to_hash).to eq(bool: { filter: [{ term: { foo: 'Foo!' } }] })
      end
    end

    context 'when a filter is passed as an argument' do
      context 'when the filter is a hash' do
        let(:search) do
          described_class.new do
            filter(term: { foo: 'Foo!' })
          end
        end

        it 'applies the filter' do
          expect(search.to_hash).to eq(bool: { filter: [{ term: { foo: 'Foo!' } }] })
        end
      end

      context 'when the filter is a `OpenSearch::DSL::Search::Filter` object' do
        let(:search) do
          filter_object = OpenSearch::DSL::Search::Filter.new do
            term bar: 'Bar!'
          end
          described_class.new do
            filter(filter_object)
          end
        end

        it 'applies the filter' do
          expect(search.to_hash).to eq(bool: { filter: [{ term: { bar: 'Bar!' } }] })
        end
      end
    end
  end
end
