/*
 * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
 * or more contributor license agreements. Licensed under the Elastic License;
 * you may not use this file except in compliance with the Elastic License.
 */
package org.elasticsearch.alerts;

import org.elasticsearch.ExceptionsHelper;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.alerts.transport.actions.delete.DeleteAlertResponse;
import org.elasticsearch.alerts.transport.actions.stats.AlertsStatsResponse;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.common.base.Predicate;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.discovery.DiscoverySettings;
import org.elasticsearch.discovery.MasterNotDiscoveredException;
import org.elasticsearch.discovery.zen.elect.ElectMasterService;
import org.elasticsearch.test.ElasticsearchIntegrationTest;
import org.elasticsearch.test.discovery.ClusterDiscoveryConfiguration;
import org.junit.Test;

import static org.elasticsearch.index.query.QueryBuilders.termQuery;
import static org.elasticsearch.search.builder.SearchSourceBuilder.searchSource;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.core.Is.is;

/**
 */
@ElasticsearchIntegrationTest.ClusterScope(scope = ElasticsearchIntegrationTest.Scope.TEST, numClientNodes = 0, transportClientRatio = 0, numDataNodes = 0)
public class NoMasterNodeTests extends AbstractAlertingTests {

    private ClusterDiscoveryConfiguration.UnicastZen config;

    @Override
    protected Settings nodeSettings(int nodeOrdinal) {
        Settings settings = super.nodeSettings(nodeOrdinal);
        Settings unicastSettings = config.node(nodeOrdinal);
        return ImmutableSettings.builder()
                .put(settings)
                .put(unicastSettings)
                .put(ElectMasterService.DISCOVERY_ZEN_MINIMUM_MASTER_NODES, 2)
                .put("discovery.type", "zen")
                .build();
    }

    @Test
    public void testSimpleFailure() throws Exception {
        config = new ClusterDiscoveryConfiguration.UnicastZen(2);
        internalTestCluster().startNodesAsync(2).get();
        createIndex("my-index");
        // Have a sample document in the index, the alert is going to evaluate
        client().prepareIndex("my-index", "my-type").setSource("field", "value").get();
        SearchRequest searchRequest = createTriggerSearchRequest("my-index").source(searchSource().query(termQuery("field", "value")));
        BytesReference alertSource = createAlertSource("0/5 * * * * ? *", searchRequest, "hits.total == 1");
        alertClient().preparePutAlert("my-first-alert")
                .setAlertSource(alertSource)
                .get();
        assertAlertTriggered("my-first-alert", 1);

        // Stop the elected master, no new master will be elected b/c of m_m_n is set to 2
        stopElectedMasterNodeAndWait();
        try {
            // any alerting action should fail, because there is no elected master node
            alertClient().prepareDeleteAlert("my-first-alert").setMasterNodeTimeout(TimeValue.timeValueSeconds(1)).get();
            fail();
        } catch (Exception e) {
            assertThat(ExceptionsHelper.unwrapCause(e), instanceOf(MasterNotDiscoveredException.class));
        }
        // Bring back the 2nd node and wait for elected master node to come back and alerting to work as expected.
        startElectedMasterNodeAndWait();

        // Delete an existing alert
        DeleteAlertResponse response = alertClient().prepareDeleteAlert("my-first-alert").get();
        assertThat(response.deleteResponse().isFound(), is(true));
        // Add a new alert and wait for it get triggered
        alertClient().preparePutAlert("my-second-alert")
                .setAlertSource(alertSource)
                .get();
        assertAlertTriggered("my-second-alert", 2);
    }

    @Test
    public void testMultipleFailures() throws Exception {
        // TODO: increase number of times we kill the elected master.
        // It is not good enough yet: after multi a couple of kills errors occur and assertions fail.
        int numberOfFailures = 1;//scaledRandomIntBetween(2, 9);
        int numberOfAlerts = scaledRandomIntBetween(numberOfFailures, 12);
        config = new ClusterDiscoveryConfiguration.UnicastZen(2 + numberOfFailures);
        internalTestCluster().startNodesAsync(2).get();
        createIndex("my-index");
        client().prepareIndex("my-index", "my-type").setSource("field", "value").get();

        for (int i = 1; i <= numberOfAlerts; i++) {
            String alertName = "alert" + i;
            SearchRequest searchRequest = createTriggerSearchRequest("my-index").source(searchSource().query(termQuery("field", "value")));
            BytesReference alertSource = createAlertSource("0/5 * * * * ? *", searchRequest, "hits.total == 1");
            alertClient().preparePutAlert(alertName)
                    .setAlertSource(alertSource)
                    .get();
        }

        for (int i = 1; i <= numberOfFailures; i++) {
            logger.info("Failure round {}", i);

            for (int j = 1; j < numberOfAlerts; j++) {
                String alertName = "alert" + i;
                assertAlertTriggered(alertName, i);
            }
            stopElectedMasterNodeAndWait();
            startElectedMasterNodeAndWait();

            AlertsStatsResponse statsResponse = alertClient().prepareAlertsStats().get();
            assertThat(statsResponse.getNumberOfRegisteredAlerts(), equalTo((long) numberOfAlerts));
        }
    }

    private void stopElectedMasterNodeAndWait() throws Exception {
        internalTestCluster().stopCurrentMasterNode();
        // Can't use ensureAlertingStopped, b/c that relies on the alerts stats api which requires an elected master node
        assertThat(awaitBusy(new Predicate<Object>() {
            public boolean apply(Object obj) {
                ClusterState state = client().admin().cluster().prepareState().setLocal(true).get().getState();
                return state.blocks().hasGlobalBlock(DiscoverySettings.NO_MASTER_BLOCK_ID);
            }
        }), equalTo(true));
        // Ensure that the alert manager doesn't run elsewhere
        for (AlertManager alertManager : internalTestCluster().getInstances(AlertManager.class)) {
            assertThat(alertManager.isStarted(), is(false));
        }
    }

    private void startElectedMasterNodeAndWait() throws Exception {
        internalTestCluster().startNode();
        ensureAlertingStarted();
    }

}

@@ -514,32 +514,72 @@
 sStatsResponse;%0A
+import org.elasticsearch.client.Client;%0A
 import org.elast
@@ -4245,180 +4245,8 @@
 n %7B%0A
-        // TODO: increase number of times we kill the elected master.%0A        // It is not good enough yet: after multi a couple of kills errors occur and assertions fail.%0A
     
@@ -4276,12 +4276,8 @@
 s = 
-1;//
 scal
@@ -6042,24 +6042,78 @@
 ject obj) %7B%0A
+                for (Client client : clients()) %7B%0A    
             
@@ -6143,18 +6143,16 @@
 = client
-()
 .admin()
@@ -6226,23 +6226,25 @@
         
-return 
+    if (!
 state.bl
@@ -6302,16 +6302,125 @@
 LOCK_ID)
+) %7B%0A                        return false;%0A                    %7D%0A                %7D%0A                return true
 ;%0A      

check no master block on all nodes