/*
 * 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.cluster.metadata;

import org.elasticsearch.Version;
import org.elasticsearch.cluster.ClusterModule;
import org.elasticsearch.common.UUIDs;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.io.stream.BytesStreamOutput;
import org.elasticsearch.common.io.stream.NamedWriteableAwareStreamInput;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.index.Index;
import org.elasticsearch.test.ESTestCase;

import java.io.IOException;

import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;

public class MetaDataTests extends ESTestCase {

    public void testIndexAndAliasWithSameName() {
        IndexMetaData.Builder builder = IndexMetaData.builder("index")
                .settings(Settings.builder().put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT))
                .numberOfShards(1)
                .numberOfReplicas(0)
                .putAlias(AliasMetaData.builder("index").build());
        try {
            MetaData.builder().put(builder).build();
            fail("exception should have been thrown");
        } catch (IllegalStateException e) {
            assertThat(e.getMessage(), equalTo("index and alias names need to be unique, but alias [index] and index [index] have the same name"));
        }
    }

    public void testResolveIndexRouting() {
        IndexMetaData.Builder builder = IndexMetaData.builder("index")
                .settings(Settings.builder().put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT))
                .numberOfShards(1)
                .numberOfReplicas(0)
                .putAlias(AliasMetaData.builder("alias0").build())
                .putAlias(AliasMetaData.builder("alias1").routing("1").build())
                .putAlias(AliasMetaData.builder("alias2").routing("1,2").build());
        MetaData metaData = MetaData.builder().put(builder).build();

        // no alias, no index
        assertEquals(metaData.resolveIndexRouting(null, null, null), null);
        assertEquals(metaData.resolveIndexRouting(null, "0", null), "0");
        assertEquals(metaData.resolveIndexRouting("32", "0", null), "0");
        assertEquals(metaData.resolveIndexRouting("32", null, null), "32");

        // index, no alias
        assertEquals(metaData.resolveIndexRouting("32", "0", "index"), "0");
        assertEquals(metaData.resolveIndexRouting("32", null, "index"), "32");
        assertEquals(metaData.resolveIndexRouting(null, null, "index"), null);
        assertEquals(metaData.resolveIndexRouting(null, "0", "index"), "0");

        // alias with no index routing
        assertEquals(metaData.resolveIndexRouting(null, null, "alias0"), null);
        assertEquals(metaData.resolveIndexRouting(null, "0", "alias0"), "0");
        assertEquals(metaData.resolveIndexRouting("32", null, "alias0"), "32");
        assertEquals(metaData.resolveIndexRouting("32", "0", "alias0"), "0");

        // alias with index routing.
        assertEquals(metaData.resolveIndexRouting(null, null, "alias1"), "1");
        assertEquals(metaData.resolveIndexRouting("32", null, "alias1"), "1");
        assertEquals(metaData.resolveIndexRouting("32", "1", "alias1"), "1");
        try {
            metaData.resolveIndexRouting(null, "0", "alias1");
            fail("should fail");
        } catch (IllegalArgumentException ex) {
            assertThat(ex.getMessage(), is("Alias [alias1] has index routing associated with it [1], and was provided with routing value [0], rejecting operation"));
        }

        try {
            metaData.resolveIndexRouting("32", "0", "alias1");
            fail("should fail");
        } catch (IllegalArgumentException ex) {
            assertThat(ex.getMessage(), is("Alias [alias1] has index routing associated with it [1], and was provided with routing value [0], rejecting operation"));
        }

        // alias with invalid index routing.
        try {
            metaData.resolveIndexRouting(null, null, "alias2");
            fail("should fail");
        } catch (IllegalArgumentException ex) {
            assertThat(ex.getMessage(), is("index/alias [alias2] provided with routing value [1,2] that resolved to several routing values, rejecting operation"));
        }

        try {
            metaData.resolveIndexRouting(null, "1", "alias2");
            fail("should fail");
        } catch (IllegalArgumentException ex) {
            assertThat(ex.getMessage(), is("index/alias [alias2] provided with routing value [1,2] that resolved to several routing values, rejecting operation"));
        }

        try {
            metaData.resolveIndexRouting("32", null, "alias2");
            fail("should fail");
        } catch (IllegalArgumentException ex) {
            assertThat(ex.getMessage(), is("index/alias [alias2] provided with routing value [1,2] that resolved to several routing values, rejecting operation"));
        }
    }

    public void testUnknownFieldClusterMetaData() throws IOException {
        BytesReference metadata = JsonXContent.contentBuilder()
            .startObject()
                .startObject("meta-data")
                    .field("random", "value")
                .endObject()
            .endObject().bytes();
        XContentParser parser = createParser(JsonXContent.jsonXContent, metadata);
        try {
            MetaData.Builder.fromXContent(parser);
            fail();
        } catch (IllegalArgumentException e) {
            assertEquals("Unexpected field [random]", e.getMessage());
        }
    }

    public void testUnknownFieldIndexMetaData() throws IOException {
        BytesReference metadata = JsonXContent.contentBuilder()
            .startObject()
                .startObject("index_name")
                    .field("random", "value")
                .endObject()
            .endObject().bytes();
        XContentParser parser = createParser(JsonXContent.jsonXContent, metadata);
        try {
            IndexMetaData.Builder.fromXContent(parser);
            fail();
        } catch (IllegalArgumentException e) {
            assertEquals("Unexpected field [random]", e.getMessage());
        }
    }

    public void testMetaDataGlobalStateChangesOnIndexDeletions() {
        IndexGraveyard.Builder builder = IndexGraveyard.builder();
        builder.addTombstone(new Index("idx1", UUIDs.randomBase64UUID()));
        final MetaData metaData1 = MetaData.builder().indexGraveyard(builder.build()).build();
        builder = IndexGraveyard.builder(metaData1.indexGraveyard());
        builder.addTombstone(new Index("idx2", UUIDs.randomBase64UUID()));
        final MetaData metaData2 = MetaData.builder(metaData1).indexGraveyard(builder.build()).build();
        assertFalse("metadata not equal after adding index deletions", MetaData.isGlobalStateEquals(metaData1, metaData2));
        final MetaData metaData3 = MetaData.builder(metaData2).build();
        assertTrue("metadata equal when not adding index deletions", MetaData.isGlobalStateEquals(metaData2, metaData3));
    }

    public void testXContentWithIndexGraveyard() throws IOException {
        final IndexGraveyard graveyard = IndexGraveyardTests.createRandom();
        final MetaData originalMeta = MetaData.builder().indexGraveyard(graveyard).build();
        final XContentBuilder builder = JsonXContent.contentBuilder();
        builder.startObject();
        originalMeta.toXContent(builder, ToXContent.EMPTY_PARAMS);
        builder.endObject();
        XContentParser parser = createParser(JsonXContent.jsonXContent, builder.bytes());
        final MetaData fromXContentMeta = MetaData.fromXContent(parser);
        assertThat(fromXContentMeta.indexGraveyard(), equalTo(originalMeta.indexGraveyard()));
    }

    public void testSerializationWithIndexGraveyard() throws IOException {
        final IndexGraveyard graveyard = IndexGraveyardTests.createRandom();
        final MetaData originalMeta = MetaData.builder().indexGraveyard(graveyard).build();
        final BytesStreamOutput out = new BytesStreamOutput();
        originalMeta.writeTo(out);
        NamedWriteableRegistry namedWriteableRegistry = new NamedWriteableRegistry(ClusterModule.getNamedWriteables());
        final MetaData fromStreamMeta = MetaData.readFrom(
            new NamedWriteableAwareStreamInput(out.bytes().streamInput(), namedWriteableRegistry)
        );
        assertThat(fromStreamMeta.indexGraveyard(), equalTo(fromStreamMeta.indexGraveyard()));
    }
}
