# 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 '../test_helper'

module OpenSearch
  module Test
    class AggregationsIntegrationTest < ::OpenSearch::Test::IntegrationTestCase
      include OpenSearch::DSL::Search

      context 'Aggregations integration' do
        setup do
          @client.indices.create index: 'test', body: {
            mappings: {
              properties: {
                tags: { type: 'keyword' }
              }
            }
          }
          @client.index index: 'test', id: '1', body: { title: 'A', tags: %w[one], clicks: 5 }
          @client.index index: 'test', id: '2', body: { title: 'B', tags: %w[one two], clicks: 15 }
          @client.index index: 'test', id: '3', body: { title: 'C', tags: %w[one three], clicks: 20 }
          @client.indices.refresh index: 'test'
        end

        context 'with a terms aggregation' do
          should 'return tag counts' do
            response = @client.search index: 'test', body: search {
              aggregation :tags do
                terms field: 'tags'
              end
            }.to_hash

            assert_equal 3, response['aggregations']['tags']['buckets'].size
            assert_equal 'one', response['aggregations']['tags']['buckets'][0]['key']
          end

          should 'return approximate tag counts' do
            response = @client.search index: 'test', body: search {
              aggregation :tags do
                cardinality field: 'tags'
              end
            }.to_hash

            assert_equal 3, response['aggregations']['tags']['value']
          end

          should 'return tag counts per clicks range as an inner (nested) aggregation' do
            response = @client.search index: 'test', body: search {
              aggregation :clicks do
                range field: 'clicks' do
                  key :low, to: 10
                  key :mid, from: 10, to: 20

                  aggregation :tags do
                    terms field: 'tags'
                  end
                end
              end
            }.to_hash

            assert_equal 2, response['aggregations']['clicks']['buckets'].size
            assert_equal 1, response['aggregations']['clicks']['buckets']['low']['doc_count']
            assert_equal 'one', response['aggregations']['clicks']['buckets']['low']['tags']['buckets'][0]['key']
          end

          should 'define multiple aggregations' do
            response = @client.search index: 'test', body: search {
              aggregation :clicks do
                range field: 'clicks' do
                  key :low, to: 10
                  key :mid, from: 10, to: 20

                  aggregation :tags do
                    terms { field 'tags' }
                  end
                end
              end

              aggregation :min_clicks do
                min field: 'clicks'
              end

              aggregation :max_clicks do
                max field: 'clicks'
              end

              aggregation :sum_clicks do
                sum field: 'clicks'
              end

              aggregation :avg_clicks do
                avg field: 'clicks'
              end
            }.to_hash

            assert_equal 2, response['aggregations']['clicks']['buckets'].size
            assert_equal 1, response['aggregations']['clicks']['buckets']['low']['doc_count']
            assert_equal 'one', response['aggregations']['clicks']['buckets']['low']['tags']['buckets'][0]['key']

            assert_equal 5,  response['aggregations']['min_clicks']['value']
            assert_equal 20, response['aggregations']['max_clicks']['value']
            assert_equal 40, response['aggregations']['sum_clicks']['value']
            assert_equal 13, response['aggregations']['avg_clicks']['value'].to_i
          end

          should 'define a global aggregation' do
            response = @client.search index: 'test', body: search {
              query do
                bool filter: { terms: { tags: ['two'] } }
              end

              aggregation :avg_clicks do
                avg field: 'clicks'
              end

              aggregation :all_documents do
                global do
                  aggregation :avg_clicks do
                    avg field: 'clicks'
                  end
                end
              end
            }.to_hash

            assert_equal 15, response['aggregations']['avg_clicks']['value'].to_i
            assert_equal 13, response['aggregations']['all_documents']['avg_clicks']['value'].to_i
          end

          should 'return statistics on clicks' do
            response = @client.search index: 'test', body: search {
              aggregation :stats_clicks do
                stats field: 'clicks'
              end
              aggregation :value_count do
                value_count field: 'clicks'
              end
            }.to_hash

            assert_equal 3,  response['aggregations']['stats_clicks']['count']
            assert_equal 5,  response['aggregations']['stats_clicks']['min']
            assert_equal 20, response['aggregations']['stats_clicks']['max']
            assert_equal 40, response['aggregations']['stats_clicks']['sum']
            assert_equal 13, response['aggregations']['stats_clicks']['avg'].to_i
            assert_equal 3,  response['aggregations']['value_count']['value']
          end

          should 'return percentiles on clicks' do
            response = @client.search index: 'test', body: search {
              aggregation :percentiles do
                percentiles field: 'clicks'
              end
            }.to_hash

            assert_equal 20, response['aggregations']['percentiles']['values']['99.0'].round
          end

          should 'return percentile ranks on clicks' do
            response = @client.search index: 'test', body: search {
              aggregation :percentiles do
                percentile_ranks field: 'clicks', values: [5]
              end
            }.to_hash

            assert_equal 17, response['aggregations']['percentiles']['values']['5.0'].round
          end

          should 'return top hits per tag' do
            response = @client.search index: 'test', body: search {
              aggregation :tags do
                terms do
                  field 'tags'
                  size  5

                  aggregation :top_hits do
                    top_hits sort: [clicks: { order: 'desc' }], _source: { include: 'title' }
                  end
                end
              end
            }.to_hash

            assert_equal 3, response['aggregations']['tags']['buckets'][0]['top_hits']['hits']['hits'].size
            assert_equal 'C',
                         response['aggregations']['tags']['buckets'][0]['top_hits']['hits']['hits'][0]['_source']['title']
          end

          should 'calculate clicks for a tag' do
            response = @client.search index: 'test', body: search {
              aggregation :clicks_for_one do
                scripted_metric do
                  init_script 'state.transactions = []'
                  map_script  "if (doc['tags'].value.contains('one')) { state.transactions.add(doc['clicks'].value) }"
                  combine_script 'double sum = 0; for (t in state.transactions) { sum += t } return sum'
                  reduce_script 'double sum = 0; for (a in states) { sum += a } return sum'
                end
              end
            }.to_hash

            assert_equal 40, response['aggregations']['clicks_for_one']['value']
          end

          should 'limit the scope with a filter' do
            response = @client.search index: 'test', body: search {
              aggregation :clicks_for_one do
                filter terms: { tags: ['one'] } do
                  aggregation :sum_clicks do
                    sum field: 'clicks'
                  end
                end
              end
            }.to_hash

            assert_equal 40, response['aggregations']['clicks_for_one']['sum_clicks']['value']
          end
        end

        should 'return aggregations for multiple filters' do
          response = @client.search index: 'test', body: search {
            aggregation :avg_clicks_per_tag do
              filters do
                filters one: { terms: { tags: ['one'] } },
                        two: { terms: { tags: ['two'] } }
                aggregation :avg do
                  avg field: 'clicks'
                end
              end
            end
          }.to_hash

          assert_equal 13, response['aggregations']['avg_clicks_per_tag']['buckets']['one']['avg']['value'].to_i
          assert_equal 15, response['aggregations']['avg_clicks_per_tag']['buckets']['two']['avg']['value'].to_i
        end

        should 'return a histogram on clicks' do
          response = @client.search index: 'test', body: search {
            aggregation :clicks_histogram do
              histogram do
                field   'clicks'
                interval 10
              end
            end
          }.to_hash

          assert_equal 3,  response['aggregations']['clicks_histogram']['buckets'].size
          assert_equal 10, response['aggregations']['clicks_histogram']['buckets'][1]['key']
          assert_equal 1,  response['aggregations']['clicks_histogram']['buckets'][1]['doc_count']
        end

        should 'return a histogram with empty buckets on clicks' do
          response = @client.search index: 'test', body: search {
            aggregation :clicks_histogram do
              histogram do
                field   'clicks'
                interval 2
                min_doc_count 0
              end
            end
          }.to_hash

          assert_equal 9, response['aggregations']['clicks_histogram']['buckets'].size
        end
      end
    end
  end
end
