/*
 * 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.cluster.health.ClusterHealthStatus;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSearchResponse;
import static org.hamcrest.Matchers.containsString;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.inject.internal.Nullable;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.plugins.ScriptPlugin;
import org.elasticsearch.script.AbstractSearchScript;
import org.elasticsearch.script.ExecutableScript;
import org.elasticsearch.script.NativeScriptFactory;
import org.elasticsearch.script.Script;
import org.elasticsearch.script.ScriptType;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.range.Range;
import org.elasticsearch.test.ESIntegTestCase;

@ESIntegTestCase.SuiteScopeTestCase
public class IpRangeIT extends ESIntegTestCase {

    @Override
    protected Collection<Class<? extends Plugin>> nodePlugins() {
        return Arrays.asList(DummyScriptPlugin.class);
    }

    @Override
    public void setupSuiteScopeCluster() throws Exception {
        assertAcked(prepareCreate("idx")
                .addMapping("type", "ip", "type=ip", "ips", "type=ip"));
        waitForRelocation(ClusterHealthStatus.GREEN);

        indexRandom(true,
                client().prepareIndex("idx", "type", "1").setSource(
                        "ip", "192.168.1.7",
                        "ips", Arrays.asList("192.168.0.13", "192.168.1.2")),
                client().prepareIndex("idx", "type", "2").setSource(
                        "ip", "192.168.1.10",
                        "ips", Arrays.asList("192.168.1.25", "192.168.1.28")),
                client().prepareIndex("idx", "type", "3").setSource(
                        "ip", "2001:db8::ff00:42:8329",
                        "ips", Arrays.asList("2001:db8::ff00:42:8329", "2001:db8::ff00:42:8380")));

        assertAcked(prepareCreate("idx_unmapped"));
        waitForRelocation(ClusterHealthStatus.GREEN);
        refresh();
    }

    public void testSingleValuedField() {
        SearchResponse rsp = client().prepareSearch("idx").addAggregation(
                AggregationBuilders.ipRange("my_range")
                    .field("ip")
                    .addUnboundedTo("192.168.1.0")
                    .addRange("192.168.1.0", "192.168.1.10")
                    .addUnboundedFrom("192.168.1.10")).get();
        assertSearchResponse(rsp);
        Range range = rsp.getAggregations().get("my_range");
        assertEquals(3, range.getBuckets().size());

        Range.Bucket bucket1 = range.getBuckets().get(0);
        assertNull(bucket1.getFrom());
        assertEquals("192.168.1.0", bucket1.getTo());
        assertEquals(0, bucket1.getDocCount());

        Range.Bucket bucket2 = range.getBuckets().get(1);
        assertEquals("192.168.1.0", bucket2.getFrom());
        assertEquals("192.168.1.10", bucket2.getTo());
        assertEquals(1, bucket2.getDocCount());

        Range.Bucket bucket3 = range.getBuckets().get(2);
        assertEquals("192.168.1.10", bucket3.getFrom());
        assertNull(bucket3.getTo());
        assertEquals(2, bucket3.getDocCount());
    }

    public void testMultiValuedField() {
        SearchResponse rsp = client().prepareSearch("idx").addAggregation(
                AggregationBuilders.ipRange("my_range")
                    .field("ips")
                    .addUnboundedTo("192.168.1.0")
                    .addRange("192.168.1.0", "192.168.1.10")
                    .addUnboundedFrom("192.168.1.10")).get();
        assertSearchResponse(rsp);
        Range range = rsp.getAggregations().get("my_range");
        assertEquals(3, range.getBuckets().size());

        Range.Bucket bucket1 = range.getBuckets().get(0);
        assertNull(bucket1.getFrom());
        assertEquals("192.168.1.0", bucket1.getTo());
        assertEquals(1, bucket1.getDocCount());

        Range.Bucket bucket2 = range.getBuckets().get(1);
        assertEquals("192.168.1.0", bucket2.getFrom());
        assertEquals("192.168.1.10", bucket2.getTo());
        assertEquals(1, bucket2.getDocCount());

        Range.Bucket bucket3 = range.getBuckets().get(2);
        assertEquals("192.168.1.10", bucket3.getFrom());
        assertNull(bucket3.getTo());
        assertEquals(2, bucket3.getDocCount());
    }

    public void testIpMask() {
        SearchResponse rsp = client().prepareSearch("idx").addAggregation(
                AggregationBuilders.ipRange("my_range")
                    .field("ips")
                    .addMaskRange("::/0")
                    .addMaskRange("0.0.0.0/0")
                    .addMaskRange("2001:db8::/64")).get();
        assertSearchResponse(rsp);
        Range range = rsp.getAggregations().get("my_range");
        assertEquals(3, range.getBuckets().size());

        Range.Bucket bucket1 = range.getBuckets().get(0);
        assertEquals("::/0", bucket1.getKey());
        assertEquals(3, bucket1.getDocCount());

        Range.Bucket bucket2 = range.getBuckets().get(1);
        assertEquals("0.0.0.0/0", bucket2.getKey());
        assertEquals(2, bucket2.getDocCount());

        Range.Bucket bucket3 = range.getBuckets().get(2);
        assertEquals("2001:db8::/64", bucket3.getKey());
        assertEquals(1, bucket3.getDocCount());
    }

    public void testPartiallyUnmapped() {
        SearchResponse rsp = client().prepareSearch("idx", "idx_unmapped").addAggregation(
                AggregationBuilders.ipRange("my_range")
                    .field("ip")
                    .addUnboundedTo("192.168.1.0")
                    .addRange("192.168.1.0", "192.168.1.10")
                    .addUnboundedFrom("192.168.1.10")).get();
        assertSearchResponse(rsp);
        Range range = rsp.getAggregations().get("my_range");
        assertEquals(3, range.getBuckets().size());

        Range.Bucket bucket1 = range.getBuckets().get(0);
        assertNull(bucket1.getFrom());
        assertEquals("192.168.1.0", bucket1.getTo());
        assertEquals(0, bucket1.getDocCount());

        Range.Bucket bucket2 = range.getBuckets().get(1);
        assertEquals("192.168.1.0", bucket2.getFrom());
        assertEquals("192.168.1.10", bucket2.getTo());
        assertEquals(1, bucket2.getDocCount());

        Range.Bucket bucket3 = range.getBuckets().get(2);
        assertEquals("192.168.1.10", bucket3.getFrom());
        assertNull(bucket3.getTo());
        assertEquals(2, bucket3.getDocCount());
    }

    public void testUnmapped() {
        SearchResponse rsp = client().prepareSearch("idx_unmapped").addAggregation(
                AggregationBuilders.ipRange("my_range")
                    .field("ip")
                    .addUnboundedTo("192.168.1.0")
                    .addRange("192.168.1.0", "192.168.1.10")
                    .addUnboundedFrom("192.168.1.10")).get();
        assertSearchResponse(rsp);
        Range range = rsp.getAggregations().get("my_range");
        assertEquals(3, range.getBuckets().size());

        Range.Bucket bucket1 = range.getBuckets().get(0);
        assertNull(bucket1.getFrom());
        assertEquals("192.168.1.0", bucket1.getTo());
        assertEquals(0, bucket1.getDocCount());

        Range.Bucket bucket2 = range.getBuckets().get(1);
        assertEquals("192.168.1.0", bucket2.getFrom());
        assertEquals("192.168.1.10", bucket2.getTo());
        assertEquals(0, bucket2.getDocCount());

        Range.Bucket bucket3 = range.getBuckets().get(2);
        assertEquals("192.168.1.10", bucket3.getFrom());
        assertNull(bucket3.getTo());
        assertEquals(0, bucket3.getDocCount());
    }

    public void testRejectsScript() {
        IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
                () -> client().prepareSearch("idx").addAggregation(
                        AggregationBuilders.ipRange("my_range")
                        .script(new Script(ScriptType.INLINE, "native", DummyScript.NAME, Collections.emptyMap())) ).get());
        assertThat(e.getMessage(), containsString("[ip_range] does not support scripts"));
    }

    public void testRejectsValueScript() {
        IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
                () -> client().prepareSearch("idx").addAggregation(
                        AggregationBuilders.ipRange("my_range")
                        .field("ip")
                        .script(new Script(ScriptType.INLINE, "native", DummyScript.NAME, Collections.emptyMap())) ).get());
        assertThat(e.getMessage(), containsString("[ip_range] does not support scripts"));
    }

    public static class DummyScriptPlugin extends Plugin implements ScriptPlugin {
        @Override
        public List<NativeScriptFactory> getNativeScripts() {
            return Collections.singletonList(new DummyScriptFactory());
        }
    }

    public static class DummyScriptFactory implements NativeScriptFactory {
        public DummyScriptFactory() {}
        @Override
        public ExecutableScript newScript(@Nullable Map<String, Object> params) {
            return new DummyScript();
        }
        @Override
        public boolean needsScores() {
            return false;
        }

        @Override
        public String getName() {
            return DummyScript.NAME;
        }
    }

    private static class DummyScript extends AbstractSearchScript {

        public static final String NAME = "dummy";

        @Override
        public Object run() {
            return null;
        }
    }

}
