/*
 * 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.test.eql;

import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;

import org.elasticsearch.Build;
import org.elasticsearch.client.Request;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.ResponseException;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.eql.EqlSearchRequest;
import org.elasticsearch.client.eql.EqlSearchResponse;
import org.elasticsearch.client.eql.EqlSearchResponse.Hits;
import org.elasticsearch.client.eql.EqlSearchResponse.Sequence;
import org.elasticsearch.common.Strings;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.test.rest.ESRestTestCase;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import static java.util.stream.Collectors.toList;
import static org.elasticsearch.test.eql.DataLoader.testIndexName;

public abstract class CommonEqlActionTestCase extends ESRestTestCase {

    protected static final String PARAM_FORMATTING = "%1$s.test -> %2$s";
    private RestHighLevelClient highLevelClient;

    @BeforeClass
    public static void checkForSnapshot() {
        assumeTrue("Only works on snapshot builds for now", Build.CURRENT.isSnapshot());
    }

    @Before
    public void setup() throws Exception {
        if (client().performRequest(new Request("HEAD", "/" + testIndexName)).getStatusLine().getStatusCode() == 404) {
            DataLoader.loadDatasetIntoEs(highLevelClient(), (t, u) -> createParser(t, u));
        }
    }

    @AfterClass
    public static void cleanup() throws Exception {
        try {
            adminClient().performRequest(new Request("DELETE", "/*"));
        } catch (ResponseException e) {
            // 404 here just means we had no indexes
            if (e.getResponse().getStatusLine().getStatusCode() != 404) {
                throw e;
            }
        }
    }

    @ParametersFactory(shuffle = false, argumentFormatting = PARAM_FORMATTING)
    public static List<Object[]> readTestSpecs() throws Exception {

        // Load EQL validation specs
        List<EqlSpec> specs = EqlSpecLoader.load("/test_queries.toml", true);
        specs.addAll(EqlSpecLoader.load("/additional_test_queries.toml", true));
        List<EqlSpec> unsupportedSpecs = EqlSpecLoader.load("/test_queries_unsupported.toml", false);

        // Validate only currently supported specs
        List<EqlSpec> filteredSpecs = new ArrayList<>();

        for (EqlSpec spec : specs) {
            boolean supported = true;
            // Check if spec is supported, simple iteration, cause the list is short.
            for (EqlSpec unSpec : unsupportedSpecs) {
                if (spec.equals(unSpec)) {
                    supported = false;
                    break;
                }
            }

            if (supported) {
                filteredSpecs.add(spec);
            }
        }
        return asArray(filteredSpecs);
    }

    public static List<Object[]> asArray(List<EqlSpec> specs) {
        AtomicInteger counter = new AtomicInteger();
        return specs.stream().map(spec -> {
            String name = spec.description();
            if (Strings.isNullOrEmpty(name)) {
                name = spec.note();
            }
            if (Strings.isNullOrEmpty(name)) {
                name = "" + (counter.get() + 1);
            }

            return new Object[] { counter.incrementAndGet(), name, spec };
        }).collect(toList());
    }

    private final int num;
    private final String name;
    private final EqlSpec spec;

    public CommonEqlActionTestCase(int num, String name, EqlSpec spec) {
        this.num = num;
        this.name = name;
        this.spec = spec;
    }

    public void test() throws Exception {
        // run both tests if case sensitivity doesn't matter
        if (spec.caseSensitive() == null) {
            assertResponse(runQuery(testIndexName, spec.query(), true));
            assertResponse(runQuery(testIndexName, spec.query(), false));
        }
        // run only the case sensitive test
        else if (spec.caseSensitive()) {
            assertResponse(runQuery(testIndexName, spec.query(), true));
        }
        // run only the case insensitive test
        else {
            assertResponse(runQuery(testIndexName, spec.query(), false));
        }
    }

    protected void assertResponse(EqlSearchResponse response) {
        Hits hits = response.hits();
        if (hits.events() != null) {
            assertSearchHits(hits.events());
        }
        else if (hits.sequences() != null) {
            assertSequences(hits.sequences());
        }
        else {
            fail("No events or sequences found");
        }
    }

    protected EqlSearchResponse runQuery(String index, String query, boolean isCaseSensitive) throws Exception {
        EqlSearchRequest request = new EqlSearchRequest(testIndexName, query);
        request.isCaseSensitive(isCaseSensitive);
        request.tiebreakerField("event.sequence");
        return highLevelClient().eql().search(request, RequestOptions.DEFAULT);
    }

    private RestHighLevelClient highLevelClient() {
        if (highLevelClient == null) {
            highLevelClient = new RestHighLevelClient(
                    client(),
                    ignore -> {
                    },
                    Collections.emptyList()) {
            };
        }
        return highLevelClient;
    }

    protected void assertSearchHits(List<SearchHit> events) {
        assertNotNull(events);
        assertArrayEquals("unexpected result for spec: [" + spec.toString() + "]", spec.expectedEventIds(), extractIds(events));
    }

    private static long[] extractIds(List<SearchHit> events) {
        final int len = events.size();
        final long ids[] = new long[len];
        for (int i = 0; i < len; i++) {
            ids[i] = ((Number) events.get(i).getSourceAsMap().get("serial_event_id")).longValue();
        }
        return ids;
    }

    protected void assertSequences(List<Sequence> sequences) {
        List<SearchHit> events = sequences.stream()
                .flatMap(s -> s.events().stream())
                .collect(toList());
        assertSearchHits(events);
    }

    @Override
    protected boolean preserveClusterUponCompletion() {
        // Need to preserve data between parameterized tests runs
        return true;
    }
}

@@ -472,59 +472,8 @@
 ns;%0A
-import org.elasticsearch.client.ResponseException;%0A
 impo
@@ -521,16 +521,16 @@
 Client;%0A
+
 import o
@@ -911,21 +911,16 @@
 it.After
-Class
 ;%0Aimport
@@ -1365,16 +1365,52 @@
  %252$s%22;%0A
+    private static int counter = 0;%0A
     priv
@@ -1901,21 +1901,16 @@
   @After
-Class
 %0A    pub
@@ -1909,31 +1909,24 @@
 %0A    public 
-static 
 void cleanup
@@ -1959,124 +1959,31 @@
     
-try %7B%0A            adminClient().performRequest(new Request(%22DELETE%22, %22/*%22));%0A        %7D catch (ResponseException e
+if (--counter == 0
 ) %7B
+  
 %0A   
@@ -1995,161 +1995,36 @@
     
-// 404 here just means we had no indexes%0A            if (e.getResponse().getStatusLine().getStatusCode() != 404) %7B%0A                throw e;%0A            %7D
+deleteIndex(testIndexName); 
 %0A   
@@ -3036,32 +3036,64 @@
     %7D%0A        %7D%0A
+        counter = specs.size();%0A
         return a
@@ -3123,29 +3123,30 @@
     %7D%0A%0A    p
-ublic
+rivate
  static List

Fix release tests (#58713)