/*
 * 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.discovery.file;

import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.network.NetworkService;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.BoundTransportAddress;
import org.elasticsearch.common.transport.TransportAddress;
import org.elasticsearch.common.util.BigArrays;
import org.elasticsearch.env.Environment;
import org.elasticsearch.indices.breaker.NoneCircuitBreakerService;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.transport.MockTransportService;
import org.elasticsearch.threadpool.TestThreadPool;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.MockTcpTransport;
import org.elasticsearch.transport.TransportService;
import org.junit.After;
import org.junit.Before;

import java.io.BufferedWriter;
import java.io.IOException;
import java.net.InetAddress;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import static org.elasticsearch.discovery.file.FileBasedUnicastHostsProvider.UNICAST_HOSTS_FILE;
import static org.elasticsearch.discovery.file.FileBasedUnicastHostsProvider.UNICAST_HOST_PREFIX;

/**
 * Tests for {@link FileBasedUnicastHostsProvider}.
 */
public class FileBasedUnicastHostsProviderTests extends ESTestCase {

    private ThreadPool threadPool;
    private ExecutorService executorService;
    private MockTransportService transportService;

    @Before
    public void setUp() throws Exception {
        super.setUp();
        threadPool = new TestThreadPool(FileBasedUnicastHostsProviderTests.class.getName());
        executorService = Executors.newSingleThreadExecutor();
    }

    @After
    public void tearDown() throws Exception {
        try {
            terminate(executorService);
        } finally {
            try {
                terminate(threadPool);
            } finally {
                super.tearDown();
            }
        }
    }

    @Before
    public void createTransportSvc() {
        MockTcpTransport transport =
            new MockTcpTransport(Settings.EMPTY,
                                    threadPool,
                                    BigArrays.NON_RECYCLING_INSTANCE,
                                    new NoneCircuitBreakerService(),
                                    new NamedWriteableRegistry(Collections.emptyList()),
                                    new NetworkService(Settings.EMPTY, Collections.emptyList())) {
                @Override
                public BoundTransportAddress boundAddress() {
                    return new BoundTransportAddress(
                        new TransportAddress[]{new TransportAddress(InetAddress.getLoopbackAddress(), 9300)},
                        new TransportAddress(InetAddress.getLoopbackAddress(), 9300)
                    );
                }
            };
        transportService = new MockTransportService(Settings.EMPTY, transport, threadPool, TransportService.NOOP_TRANSPORT_INTERCEPTOR,
                null);
    }

    public void testBuildDynamicNodes() throws Exception {
        final List<String> hostEntries = Arrays.asList("#comment, should be ignored", "192.168.0.1", "192.168.0.2:9305", "255.255.23.15");
        final List<DiscoveryNode> nodes = setupAndRunHostProvider(hostEntries);
        assertEquals(hostEntries.size() - 1, nodes.size()); // minus 1 because we are ignoring the first line that's a comment
        assertEquals("192.168.0.1", nodes.get(0).getAddress().getAddress());
        assertEquals(9300, nodes.get(0).getAddress().getPort());
        assertEquals(UNICAST_HOST_PREFIX + "192.168.0.1_0#", nodes.get(0).getId());
        assertEquals("192.168.0.2", nodes.get(1).getAddress().getAddress());
        assertEquals(9305, nodes.get(1).getAddress().getPort());
        assertEquals(UNICAST_HOST_PREFIX + "192.168.0.2:9305_0#", nodes.get(1).getId());
        assertEquals("255.255.23.15", nodes.get(2).getAddress().getAddress());
        assertEquals(9300, nodes.get(2).getAddress().getPort());
        assertEquals(UNICAST_HOST_PREFIX + "255.255.23.15_0#", nodes.get(2).getId());
    }

    public void testEmptyUnicastHostsFile() throws Exception {
        final List<String> hostEntries = Collections.emptyList();
        final List<DiscoveryNode> nodes = setupAndRunHostProvider(hostEntries);
        assertEquals(0, nodes.size());
    }

    public void testUnicastHostsDoesNotExist() throws Exception {
        final Settings settings = Settings.builder()
                                      .put(Environment.PATH_HOME_SETTING.getKey(), createTempDir())
                                      .build();
        final FileBasedUnicastHostsProvider provider = new FileBasedUnicastHostsProvider(settings, transportService, executorService);
        final List<DiscoveryNode> nodes = provider.buildDynamicNodes();
        assertEquals(0, nodes.size());
    }

    public void testInvalidHostEntries() throws Exception {
        List<String> hostEntries = Arrays.asList("192.168.0.1:9300:9300");
        List<DiscoveryNode> nodes = setupAndRunHostProvider(hostEntries);
        assertEquals(0, nodes.size());
    }

    public void testSomeInvalidHostEntries() throws Exception {
        List<String> hostEntries = Arrays.asList("192.168.0.1:9300:9300", "192.168.0.1:9301");
        List<DiscoveryNode> nodes = setupAndRunHostProvider(hostEntries);
        assertEquals(1, nodes.size()); // only one of the two is valid and will be used
        assertEquals("192.168.0.1", nodes.get(0).getAddress().getAddress());
        assertEquals(9301, nodes.get(0).getAddress().getPort());
    }

    // sets up the config dir, writes to the unicast hosts file in the config dir,
    // and then runs the file-based unicast host provider to get the list of discovery nodes
    private List<DiscoveryNode> setupAndRunHostProvider(final List<String> hostEntries) throws IOException {
        final Path homeDir = createTempDir();
        final Settings settings = Settings.builder()
                                      .put(Environment.PATH_HOME_SETTING.getKey(), homeDir)
                                      .build();
        final Path configDir = homeDir.resolve("config").resolve("discovery-file");
        Files.createDirectories(configDir);
        final Path unicastHostsPath = configDir.resolve(UNICAST_HOSTS_FILE);
        try (BufferedWriter writer = Files.newBufferedWriter(unicastHostsPath)) {
            writer.write(String.join("\n", hostEntries));
        }

        return new FileBasedUnicastHostsProvider(settings, transportService, executorService).buildDynamicNodes();
    }
}
