/*
 * Licensed to Elasticsearch under one or more contributor
 * license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright
 * ownership. Elasticsearch 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.
 */
package org.elasticsearch.search.aggregations.bucket;

import org.elasticsearch.action.search.SearchPhaseExecutionException;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.search.aggregations.Aggregator.SubAggCollectionMode;
import org.elasticsearch.search.aggregations.bucket.filter.Filter;
import org.elasticsearch.search.aggregations.bucket.nested.Nested;
import org.elasticsearch.search.aggregations.bucket.nested.ReverseNested;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.metrics.valuecount.ValueCount;
import org.elasticsearch.test.ElasticsearchIntegrationTest;
import org.hamcrest.Matchers;
import org.junit.Test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import static org.elasticsearch.cluster.metadata.IndexMetaData.SETTING_NUMBER_OF_REPLICAS;
import static org.elasticsearch.cluster.metadata.IndexMetaData.SETTING_NUMBER_OF_SHARDS;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.elasticsearch.index.query.FilterBuilders.termFilter;
import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
import static org.elasticsearch.search.aggregations.AggregationBuilders.*;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.*;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailures;
import static org.hamcrest.Matchers.*;
import static org.hamcrest.core.IsNull.notNullValue;

/**
 *
 */
@ElasticsearchIntegrationTest.SuiteScopeTest
public class ReverseNestedTests extends ElasticsearchIntegrationTest {

    public void setupSuiteScopeCluster() throws Exception {
        assertAcked(prepareCreate("idx")
                .addMapping(
                        "type1",
                        jsonBuilder().startObject().startObject("properties")
                                .startObject("field1").field("type", "string").endObject()
                                .startObject("nested1").field("type", "nested").startObject("properties")
                                    .startObject("field2").field("type", "string").endObject()
                                .endObject().endObject()
                                .endObject().endObject()
                )
                .addMapping(
                        "type2",
                        jsonBuilder().startObject().startObject("properties")
                                .startObject("nested1").field("type", "nested").startObject("properties")
                                    .startObject("field1").field("type", "string").endObject()
                                        .startObject("nested2").field("type", "nested").startObject("properties")
                                            .startObject("field2").field("type", "string").endObject()
                                        .endObject().endObject()
                                    .endObject().endObject()
                                .endObject().endObject()
                )
        );

        insertType1(Arrays.asList("a", "b", "c"), Arrays.asList("1", "2", "3", "4"));
        insertType1(Arrays.asList("b", "c", "d"), Arrays.asList("4", "5", "6", "7"));
        insertType1(Arrays.asList("c", "d", "e"), Arrays.asList("7", "8", "9", "1"));
        refresh();
        insertType1(Arrays.asList("a", "e"), Arrays.asList("7", "4", "1", "1"));
        insertType1(Arrays.asList("a", "c"), Arrays.asList("2", "1"));
        insertType1(Arrays.asList("a"), Arrays.asList("3", "4"));
        refresh();
        insertType1(Arrays.asList("x", "c"), Arrays.asList("1", "8"));
        insertType1(Arrays.asList("y", "c"), Arrays.asList("6"));
        insertType1(Arrays.asList("z"), Arrays.asList("5", "9"));
        refresh();

        insertType2(new String[][]{new String[]{"a", "0", "0", "1", "2"}, new String[]{"b", "0", "1", "1", "2"}, new String[]{"a", "0"}});
        insertType2(new String[][]{new String[]{"c", "1", "1", "2", "2"}, new String[]{"d", "3", "4"}});
        refresh();

        insertType2(new String[][]{new String[]{"a", "0", "0", "0", "0"}, new String[]{"b", "0", "0", "0", "0"}});
        insertType2(new String[][]{new String[]{"e", "1", "2"}, new String[]{"f", "3", "4"}});
        refresh();

        ensureSearchable();
    }

    private void insertType1(List<String> values1, List<String> values2) throws Exception {
        XContentBuilder source = jsonBuilder()
                .startObject()
                .array("field1", values1.toArray())
                .startArray("nested1");
        for (String value1 : values2) {
            source.startObject().field("field2", value1).endObject();
        }
        source.endArray().endObject();
        indexRandom(false, client().prepareIndex("idx", "type1").setRouting("1").setSource(source));
    }

    private void insertType2(String[][] values) throws Exception {
        XContentBuilder source = jsonBuilder()
                .startObject()
                .startArray("nested1");
        for (String[] value : values) {
            source.startObject().field("field1", value[0]).startArray("nested2");
            for (int i = 1; i < value.length; i++) {
                source.startObject().field("field2", value[i]).endObject();
            }
            source.endArray().endObject();
        }
        source.endArray().endObject();
        indexRandom(false, client().prepareIndex("idx", "type2").setRouting("1").setSource(source));
    }

    @Test
    public void simple_reverseNestedToRoot() throws Exception {
        SearchResponse response = client().prepareSearch("idx").setTypes("type1")
                .addAggregation(nested("nested1").path("nested1")
                        .subAggregation(
                                terms("field2").field("nested1.field2")
                                        .subAggregation(
                                                reverseNested("nested1_to_field1")
                                                        .subAggregation(
                                                                terms("field1").field("field1")
                                                                .collectMode(randomFrom(SubAggCollectionMode.values()))
                                                        )
                                        )
                        )
                ).get();

        assertSearchResponse(response);

        Nested nested = response.getAggregations().get("nested1");
        assertThat(nested, notNullValue());
        assertThat(nested.getName(), equalTo("nested1"));
        assertThat(nested.getDocCount(), equalTo(25l));
        assertThat(nested.getAggregations().asList().isEmpty(), is(false));

        Terms usernames = nested.getAggregations().get("field2");
        assertThat(usernames, notNullValue());
        assertThat(usernames.getBuckets().size(), equalTo(9));
        List<Terms.Bucket> usernameBuckets = new ArrayList<>(usernames.getBuckets());

        // nested.field2: 1
        Terms.Bucket bucket = usernameBuckets.get(0);
        assertThat(bucket.getKey(), equalTo("1"));
        assertThat(bucket.getDocCount(), equalTo(6l));
        ReverseNested reverseNested = bucket.getAggregations().get("nested1_to_field1");
        Terms tags = reverseNested.getAggregations().get("field1");
        List<Terms.Bucket> tagsBuckets = new ArrayList<>(tags.getBuckets());
        assertThat(tagsBuckets.size(), equalTo(6));
        assertThat(tagsBuckets.get(0).getKey(), equalTo("c"));
        assertThat(tagsBuckets.get(0).getDocCount(), equalTo(4l));
        assertThat(tagsBuckets.get(1).getKey(), equalTo("a"));
        assertThat(tagsBuckets.get(1).getDocCount(), equalTo(3l));
        assertThat(tagsBuckets.get(2).getKey(), equalTo("e"));
        assertThat(tagsBuckets.get(2).getDocCount(), equalTo(2l));
        assertThat(tagsBuckets.get(3).getKey(), equalTo("b"));
        assertThat(tagsBuckets.get(3).getDocCount(), equalTo(1l));
        assertThat(tagsBuckets.get(4).getKey(), equalTo("d"));
        assertThat(tagsBuckets.get(4).getDocCount(), equalTo(1l));
        assertThat(tagsBuckets.get(5).getKey(), equalTo("x"));
        assertThat(tagsBuckets.get(5).getDocCount(), equalTo(1l));

        // nested.field2: 4
        bucket = usernameBuckets.get(1);
        assertThat(bucket.getKey(), equalTo("4"));
        assertThat(bucket.getDocCount(), equalTo(4l));
        reverseNested = bucket.getAggregations().get("nested1_to_field1");
        tags = reverseNested.getAggregations().get("field1");
        tagsBuckets = new ArrayList<>(tags.getBuckets());
        assertThat(tagsBuckets.size(), equalTo(5));
        assertThat(tagsBuckets.get(0).getKey(), equalTo("a"));
        assertThat(tagsBuckets.get(0).getDocCount(), equalTo(3l));
        assertThat(tagsBuckets.get(1).getKey(), equalTo("b"));
        assertThat(tagsBuckets.get(1).getDocCount(), equalTo(2l));
        assertThat(tagsBuckets.get(2).getKey(), equalTo("c"));
        assertThat(tagsBuckets.get(2).getDocCount(), equalTo(2l));
        assertThat(tagsBuckets.get(3).getKey(), equalTo("d"));
        assertThat(tagsBuckets.get(3).getDocCount(), equalTo(1l));
        assertThat(tagsBuckets.get(4).getKey(), equalTo("e"));
        assertThat(tagsBuckets.get(4).getDocCount(), equalTo(1l));

        // nested.field2: 7
        bucket = usernameBuckets.get(2);
        assertThat(bucket.getKey(), equalTo("7"));
        assertThat(bucket.getDocCount(), equalTo(3l));
        reverseNested = bucket.getAggregations().get("nested1_to_field1");
        tags = reverseNested.getAggregations().get("field1");
        tagsBuckets = new ArrayList<>(tags.getBuckets());
        assertThat(tagsBuckets.size(), equalTo(5));
        assertThat(tagsBuckets.get(0).getKey(), equalTo("c"));
        assertThat(tagsBuckets.get(0).getDocCount(), equalTo(2l));
        assertThat(tagsBuckets.get(1).getKey(), equalTo("d"));
        assertThat(tagsBuckets.get(1).getDocCount(), equalTo(2l));
        assertThat(tagsBuckets.get(2).getKey(), equalTo("e"));
        assertThat(tagsBuckets.get(2).getDocCount(), equalTo(2l));
        assertThat(tagsBuckets.get(3).getKey(), equalTo("a"));
        assertThat(tagsBuckets.get(3).getDocCount(), equalTo(1l));
        assertThat(tagsBuckets.get(4).getKey(), equalTo("b"));
        assertThat(tagsBuckets.get(4).getDocCount(), equalTo(1l));

        // nested.field2: 2
        bucket = usernameBuckets.get(3);
        assertThat(bucket.getKey(), equalTo("2"));
        assertThat(bucket.getDocCount(), equalTo(2l));
        reverseNested = bucket.getAggregations().get("nested1_to_field1");
        tags = reverseNested.getAggregations().get("field1");
        tagsBuckets = new ArrayList<>(tags.getBuckets());
        assertThat(tagsBuckets.size(), equalTo(3));
        assertThat(tagsBuckets.get(0).getKey(), equalTo("a"));
        assertThat(tagsBuckets.get(0).getDocCount(), equalTo(2l));
        assertThat(tagsBuckets.get(1).getKey(), equalTo("c"));
        assertThat(tagsBuckets.get(1).getDocCount(), equalTo(2l));
        assertThat(tagsBuckets.get(2).getKey(), equalTo("b"));
        assertThat(tagsBuckets.get(2).getDocCount(), equalTo(1l));

        // nested.field2: 3
        bucket = usernameBuckets.get(4);
        assertThat(bucket.getKey(), equalTo("3"));
        assertThat(bucket.getDocCount(), equalTo(2l));
        reverseNested = bucket.getAggregations().get("nested1_to_field1");
        tags = reverseNested.getAggregations().get("field1");
        tagsBuckets = new ArrayList<>(tags.getBuckets());
        assertThat(tagsBuckets.size(), equalTo(3));
        assertThat(tagsBuckets.get(0).getKey(), equalTo("a"));
        assertThat(tagsBuckets.get(0).getDocCount(), equalTo(2l));
        assertThat(tagsBuckets.get(1).getKey(), equalTo("b"));
        assertThat(tagsBuckets.get(1).getDocCount(), equalTo(1l));
        assertThat(tagsBuckets.get(2).getKey(), equalTo("c"));
        assertThat(tagsBuckets.get(2).getDocCount(), equalTo(1l));

        // nested.field2: 5
        bucket = usernameBuckets.get(5);
        assertThat(bucket.getKey(), equalTo("5"));
        assertThat(bucket.getDocCount(), equalTo(2l));
        reverseNested = bucket.getAggregations().get("nested1_to_field1");
        tags = reverseNested.getAggregations().get("field1");
        tagsBuckets = new ArrayList<>(tags.getBuckets());
        assertThat(tagsBuckets.size(), equalTo(4));
        assertThat(tagsBuckets.get(0).getKey(), equalTo("b"));
        assertThat(tagsBuckets.get(0).getDocCount(), equalTo(1l));
        assertThat(tagsBuckets.get(1).getKey(), equalTo("c"));
        assertThat(tagsBuckets.get(1).getDocCount(), equalTo(1l));
        assertThat(tagsBuckets.get(2).getKey(), equalTo("d"));
        assertThat(tagsBuckets.get(2).getDocCount(), equalTo(1l));
        assertThat(tagsBuckets.get(3).getKey(), equalTo("z"));
        assertThat(tagsBuckets.get(3).getDocCount(), equalTo(1l));

        // nested.field2: 6
        bucket = usernameBuckets.get(6);
        assertThat(bucket.getKey(), equalTo("6"));
        assertThat(bucket.getDocCount(), equalTo(2l));
        reverseNested = bucket.getAggregations().get("nested1_to_field1");
        tags = reverseNested.getAggregations().get("field1");
        tagsBuckets = new ArrayList<>(tags.getBuckets());
        assertThat(tagsBuckets.size(), equalTo(4));
        assertThat(tagsBuckets.get(0).getKey(), equalTo("c"));
        assertThat(tagsBuckets.get(0).getDocCount(), equalTo(2l));
        assertThat(tagsBuckets.get(1).getKey(), equalTo("b"));
        assertThat(tagsBuckets.get(1).getDocCount(), equalTo(1l));
        assertThat(tagsBuckets.get(2).getKey(), equalTo("d"));
        assertThat(tagsBuckets.get(2).getDocCount(), equalTo(1l));
        assertThat(tagsBuckets.get(3).getKey(), equalTo("y"));
        assertThat(tagsBuckets.get(3).getDocCount(), equalTo(1l));

        // nested.field2: 8
        bucket = usernameBuckets.get(7);
        assertThat(bucket.getKey(), equalTo("8"));
        assertThat(bucket.getDocCount(), equalTo(2l));
        reverseNested = bucket.getAggregations().get("nested1_to_field1");
        tags = reverseNested.getAggregations().get("field1");
        tagsBuckets = new ArrayList<>(tags.getBuckets());
        assertThat(tagsBuckets.size(), equalTo(4));
        assertThat(tagsBuckets.get(0).getKey(), equalTo("c"));
        assertThat(tagsBuckets.get(0).getDocCount(), equalTo(2l));
        assertThat(tagsBuckets.get(1).getKey(), equalTo("d"));
        assertThat(tagsBuckets.get(1).getDocCount(), equalTo(1l));
        assertThat(tagsBuckets.get(2).getKey(), equalTo("e"));
        assertThat(tagsBuckets.get(2).getDocCount(), equalTo(1l));
        assertThat(tagsBuckets.get(3).getKey(), equalTo("x"));
        assertThat(tagsBuckets.get(3).getDocCount(), equalTo(1l));

        // nested.field2: 9
        bucket = usernameBuckets.get(8);
        assertThat(bucket.getKey(), equalTo("9"));
        assertThat(bucket.getDocCount(), equalTo(2l));
        reverseNested = bucket.getAggregations().get("nested1_to_field1");
        tags = reverseNested.getAggregations().get("field1");
        tagsBuckets = new ArrayList<>(tags.getBuckets());
        assertThat(tagsBuckets.size(), equalTo(4));
        assertThat(tagsBuckets.get(0).getKey(), equalTo("c"));
        assertThat(tagsBuckets.get(0).getDocCount(), equalTo(1l));
        assertThat(tagsBuckets.get(1).getKey(), equalTo("d"));
        assertThat(tagsBuckets.get(1).getDocCount(), equalTo(1l));
        assertThat(tagsBuckets.get(2).getKey(), equalTo("e"));
        assertThat(tagsBuckets.get(2).getDocCount(), equalTo(1l));
        assertThat(tagsBuckets.get(3).getKey(), equalTo("z"));
        assertThat(tagsBuckets.get(3).getDocCount(), equalTo(1l));
    }

    @Test
    public void simple_nested1ToRootToNested2() throws Exception {
        SearchResponse response = client().prepareSearch("idx").setTypes("type2")
                .addAggregation(nested("nested1").path("nested1")
                                .subAggregation(
                                        reverseNested("nested1_to_root")
                                                .subAggregation(nested("root_to_nested2").path("nested1.nested2"))
                                        )
                                )
                .get();

        assertSearchResponse(response);
        Nested nested = response.getAggregations().get("nested1");
        assertThat(nested.getName(), equalTo("nested1"));
        assertThat(nested.getDocCount(), equalTo(9l));
        ReverseNested reverseNested = nested.getAggregations().get("nested1_to_root");
        assertThat(reverseNested.getName(), equalTo("nested1_to_root"));
        assertThat(reverseNested.getDocCount(), equalTo(4l));
        nested = reverseNested.getAggregations().get("root_to_nested2");
        assertThat(nested.getName(), equalTo("root_to_nested2"));
        assertThat(nested.getDocCount(), equalTo(27l));
    }

    @Test
    public void simple_reverseNestedToNested1() throws Exception {
        SearchResponse response = client().prepareSearch("idx").setTypes("type2")
                .addAggregation(nested("nested1").path("nested1.nested2")
                                .subAggregation(
                                        terms("field2").field("nested1.nested2.field2").order(Terms.Order.term(true))
                                                .collectMode(randomFrom(SubAggCollectionMode.values()))
                                                .size(0)
                                                .subAggregation(
                                                        reverseNested("nested1_to_field1").path("nested1")
                                                                .subAggregation(
                                                                        terms("field1").field("nested1.field1").order(Terms.Order.term(true))
                                                                                .collectMode(randomFrom(SubAggCollectionMode.values()))
                                                                )
                                                )
                                )
                ).get();

        assertSearchResponse(response);

        Nested nested = response.getAggregations().get("nested1");
        assertThat(nested, notNullValue());
        assertThat(nested.getName(), equalTo("nested1"));
        assertThat(nested.getDocCount(), equalTo(27l));
        assertThat(nested.getAggregations().asList().isEmpty(), is(false));

        Terms usernames = nested.getAggregations().get("field2");
        assertThat(usernames, notNullValue());
        assertThat(usernames.getBuckets().size(), equalTo(5));
        List<Terms.Bucket> usernameBuckets = new ArrayList<>(usernames.getBuckets());

        Terms.Bucket bucket = usernameBuckets.get(0);
        assertThat(bucket.getKey(), equalTo("0"));
        assertThat(bucket.getDocCount(), equalTo(12l));
        ReverseNested reverseNested = bucket.getAggregations().get("nested1_to_field1");
        assertThat(reverseNested.getDocCount(), equalTo(5l));
        Terms tags = reverseNested.getAggregations().get("field1");
        List<Terms.Bucket> tagsBuckets = new ArrayList<>(tags.getBuckets());
        assertThat(tagsBuckets.size(), equalTo(2));
        assertThat(tagsBuckets.get(0).getKey(), equalTo("a"));
        assertThat(tagsBuckets.get(0).getDocCount(), equalTo(3l));
        assertThat(tagsBuckets.get(1).getKey(), equalTo("b"));
        assertThat(tagsBuckets.get(1).getDocCount(), equalTo(2l));

        bucket = usernameBuckets.get(1);
        assertThat(bucket.getKey(), equalTo("1"));
        assertThat(bucket.getDocCount(), equalTo(6l));
        reverseNested = bucket.getAggregations().get("nested1_to_field1");
        assertThat(reverseNested.getDocCount(), equalTo(4l));
        tags = reverseNested.getAggregations().get("field1");
        tagsBuckets = new ArrayList<>(tags.getBuckets());
        assertThat(tagsBuckets.size(), equalTo(4));
        assertThat(tagsBuckets.get(0).getKey(), equalTo("a"));
        assertThat(tagsBuckets.get(0).getDocCount(), equalTo(1l));
        assertThat(tagsBuckets.get(1).getKey(), equalTo("b"));
        assertThat(tagsBuckets.get(1).getDocCount(), equalTo(1l));
        assertThat(tagsBuckets.get(2).getKey(), equalTo("c"));
        assertThat(tagsBuckets.get(2).getDocCount(), equalTo(1l));
        assertThat(tagsBuckets.get(3).getKey(), equalTo("e"));
        assertThat(tagsBuckets.get(3).getDocCount(), equalTo(1l));

        bucket = usernameBuckets.get(2);
        assertThat(bucket.getKey(), equalTo("2"));
        assertThat(bucket.getDocCount(), equalTo(5l));
        reverseNested = bucket.getAggregations().get("nested1_to_field1");
        assertThat(reverseNested.getDocCount(), equalTo(4l));
        tags = reverseNested.getAggregations().get("field1");
        tagsBuckets = new ArrayList<>(tags.getBuckets());
        assertThat(tagsBuckets.size(), equalTo(4));
        assertThat(tagsBuckets.get(0).getKey(), equalTo("a"));
        assertThat(tagsBuckets.get(0).getDocCount(), equalTo(1l));
        assertThat(tagsBuckets.get(1).getKey(), equalTo("b"));
        assertThat(tagsBuckets.get(1).getDocCount(), equalTo(1l));
        assertThat(tagsBuckets.get(2).getKey(), equalTo("c"));
        assertThat(tagsBuckets.get(2).getDocCount(), equalTo(1l));
        assertThat(tagsBuckets.get(3).getKey(), equalTo("e"));
        assertThat(tagsBuckets.get(3).getDocCount(), equalTo(1l));

        bucket = usernameBuckets.get(3);
        assertThat(bucket.getKey(), equalTo("3"));
        assertThat(bucket.getDocCount(), equalTo(2l));
        reverseNested = bucket.getAggregations().get("nested1_to_field1");
        assertThat(reverseNested.getDocCount(), equalTo(2l));
        tags = reverseNested.getAggregations().get("field1");
        tagsBuckets = new ArrayList<>(tags.getBuckets());
        assertThat(tagsBuckets.size(), equalTo(2));
        assertThat(tagsBuckets.get(0).getKey(), equalTo("d"));
        assertThat(tagsBuckets.get(0).getDocCount(), equalTo(1l));
        assertThat(tagsBuckets.get(1).getKey(), equalTo("f"));

        bucket = usernameBuckets.get(4);
        assertThat(bucket.getKey(), equalTo("4"));
        assertThat(bucket.getDocCount(), equalTo(2l));
        reverseNested = bucket.getAggregations().get("nested1_to_field1");
        assertThat(reverseNested.getDocCount(), equalTo(2l));
        tags = reverseNested.getAggregations().get("field1");
        tagsBuckets = new ArrayList<>(tags.getBuckets());
        assertThat(tagsBuckets.size(), equalTo(2));
        assertThat(tagsBuckets.get(0).getKey(), equalTo("d"));
        assertThat(tagsBuckets.get(0).getDocCount(), equalTo(1l));
        assertThat(tagsBuckets.get(1).getKey(), equalTo("f"));
    }

    @Test(expected = SearchPhaseExecutionException.class)
    public void testReverseNestedAggWithoutNestedAgg() throws Exception {
        client().prepareSearch("idx")
                .addAggregation(terms("field2").field("nested1.nested2.field2")
                        .collectMode(randomFrom(SubAggCollectionMode.values()))
                                .subAggregation(
                                        reverseNested("nested1_to_field1")
                                                .subAggregation(
                                                        terms("field1").field("nested1.field1")
                                                        .collectMode(randomFrom(SubAggCollectionMode.values()))
                                                )
                                )
                ).get();
    }

    @Test
    public void nonExistingNestedField() throws Exception {
        SearchResponse searchResponse = client().prepareSearch("idx")
                .setQuery(matchAllQuery())
                .addAggregation(nested("nested2").path("nested1.nested2").subAggregation(reverseNested("incorrect").path("nested3")))
                .execute().actionGet();

        Nested nested = searchResponse.getAggregations().get("nested2");
        assertThat(nested, Matchers.notNullValue());
        assertThat(nested.getName(), equalTo("nested2"));

        ReverseNested reverseNested = nested.getAggregations().get("incorrect");
        assertThat(reverseNested.getDocCount(), is(0l));
    }

    @Test
    public void testSameParentDocHavingMultipleBuckets() throws Exception {
        XContentBuilder mapping = jsonBuilder().startObject().startObject("product").field("dynamic", "strict").startObject("properties")
                .startObject("id").field("type", "long").endObject()
                .startObject("category")
                    .field("type", "nested")
                    .startObject("properties")
                        .startObject("name").field("type", "string").endObject()
                    .endObject()
                .endObject()
                .startObject("sku")
                    .field("type", "nested")
                    .startObject("properties")
                        .startObject("sku_type").field("type", "string").endObject()
                            .startObject("colors")
                                .field("type", "nested")
                                .startObject("properties")
                                    .startObject("name").field("type", "string").endObject()
                                .endObject()
                            .endObject()
                    .endObject()
                .endObject()
                .endObject().endObject().endObject();
        assertAcked(
                prepareCreate("idx3")
                        .setSettings(ImmutableSettings.builder().put(SETTING_NUMBER_OF_SHARDS, 1).put(SETTING_NUMBER_OF_REPLICAS, 0))
                        .addMapping("product", mapping)
        );

        client().prepareIndex("idx3", "product", "1").setRefresh(true).setSource(
                jsonBuilder().startObject()
                        .startArray("sku")
                            .startObject()
                                .field("sku_type", "bar1")
                                .startArray("colors")
                                    .startObject().field("name", "red").endObject()
                                    .startObject().field("name", "green").endObject()
                                    .startObject().field("name", "yellow").endObject()
                                .endArray()
                            .endObject()
                            .startObject()
                                .field("sku_type", "bar1")
                                .startArray("colors")
                                .startObject().field("name", "red").endObject()
                                .startObject().field("name", "blue").endObject()
                                .startObject().field("name", "white").endObject()
                                .endArray()
                            .endObject()
                            .startObject()
                                .field("sku_type", "bar1")
                                .startArray("colors")
                                .startObject().field("name", "black").endObject()
                                .startObject().field("name", "blue").endObject()
                                .endArray()
                            .endObject()
                            .startObject()
                                .field("sku_type", "bar2")
                                .startArray("colors")
                                .startObject().field("name", "orange").endObject()
                                .endArray()
                            .endObject()
                            .startObject()
                                .field("sku_type", "bar2")
                                .startArray("colors")
                                .startObject().field("name", "pink").endObject()
                                .endArray()
                            .endObject()
                        .endArray()
                        .startArray("category")
                            .startObject().field("name", "abc").endObject()
                            .startObject().field("name", "klm").endObject()
                            .startObject().field("name", "xyz").endObject()
                        .endArray()
                        .endObject()
        ).get();

        SearchResponse response = client().prepareSearch("idx3")
                .addAggregation(
                        nested("nested_0").path("category").subAggregation(
                                terms("group_by_category").field("category.name").subAggregation(
                                        reverseNested("to_root").subAggregation(
                                                nested("nested_1").path("sku").subAggregation(
                                                        filter("filter_by_sku").filter(termFilter("sku.sku_type", "bar1")).subAggregation(
                                                                count("sku_count").field("sku_type")
                                                        )
                                                )
                                        )
                                )
                        )
                ).get();
        assertNoFailures(response);
        assertHitCount(response, 1);

        Nested nested0 = response.getAggregations().get("nested_0");
        assertThat(nested0.getDocCount(), equalTo(3l));
        Terms terms = nested0.getAggregations().get("group_by_category");
        assertThat(terms.getBuckets().size(), equalTo(3));
        for (String bucketName : new String[]{"abc", "klm", "xyz"}) {
            logger.info("Checking results for bucket {}", bucketName);
            Terms.Bucket bucket = terms.getBucketByKey(bucketName);
            assertThat(bucket.getDocCount(), equalTo(1l));
            ReverseNested toRoot = bucket.getAggregations().get("to_root");
            assertThat(toRoot.getDocCount(), equalTo(1l));
            Nested nested1 = toRoot.getAggregations().get("nested_1");
            assertThat(nested1.getDocCount(), equalTo(5l));
            Filter filterByBar = nested1.getAggregations().get("filter_by_sku");
            assertThat(filterByBar.getDocCount(), equalTo(3l));
            ValueCount barCount = filterByBar.getAggregations().get("sku_count");
            assertThat(barCount.getValue(), equalTo(3l));
        }

        response = client().prepareSearch("idx3")
                .addAggregation(
                        nested("nested_0").path("category").subAggregation(
                                terms("group_by_category").field("category.name").subAggregation(
                                        reverseNested("to_root").subAggregation(
                                                nested("nested_1").path("sku").subAggregation(
                                                        filter("filter_by_sku").filter(termFilter("sku.sku_type", "bar1")).subAggregation(
                                                                nested("nested_2").path("sku.colors").subAggregation(
                                                                        filter("filter_sku_color").filter(termFilter("sku.colors.name", "red")).subAggregation(
                                                                                reverseNested("reverse_to_sku").path("sku").subAggregation(
                                                                                        count("sku_count").field("sku_type")
                                                                                )
                                                                        )
                                                                )
                                                        )
                                                )
                                        )
                                )
                        )
                ).get();
        assertNoFailures(response);
        assertHitCount(response, 1);

        nested0 = response.getAggregations().get("nested_0");
        assertThat(nested0.getDocCount(), equalTo(3l));
        terms = nested0.getAggregations().get("group_by_category");
        assertThat(terms.getBuckets().size(), equalTo(3));
        for (String bucketName : new String[]{"abc", "klm", "xyz"}) {
            logger.info("Checking results for bucket {}", bucketName);
            Terms.Bucket bucket = terms.getBucketByKey(bucketName);
            assertThat(bucket.getDocCount(), equalTo(1l));
            ReverseNested toRoot = bucket.getAggregations().get("to_root");
            assertThat(toRoot.getDocCount(), equalTo(1l));
            Nested nested1 = toRoot.getAggregations().get("nested_1");
            assertThat(nested1.getDocCount(), equalTo(5l));
            Filter filterByBar = nested1.getAggregations().get("filter_by_sku");
            assertThat(filterByBar.getDocCount(), equalTo(3l));
            Nested nested2 = filterByBar.getAggregations().get("nested_2");
            assertThat(nested2.getDocCount(), equalTo(8l));
            Filter filterBarColor = nested2.getAggregations().get("filter_sku_color");
            assertThat(filterBarColor.getDocCount(), equalTo(2l));
            ReverseNested reverseToBar = filterBarColor.getAggregations().get("reverse_to_sku");
            assertThat(reverseToBar.getDocCount(), equalTo(2l));
            ValueCount barCount = reverseToBar.getAggregations().get("sku_count");
            assertThat(barCount.getValue(), equalTo(2l));
        }
    }
}
