/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF 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
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * 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.apache.atlas.discovery;

import org.apache.atlas.ApplicationProperties;
import org.apache.atlas.AtlasClient;
import org.apache.atlas.BasicTestSetup;
import org.apache.atlas.SortOrder;
import org.apache.atlas.TestModules;
import org.apache.atlas.exception.AtlasBaseException;
import org.apache.atlas.model.discovery.AtlasAggregationEntry;
import org.apache.atlas.model.discovery.AtlasQuickSearchResult;
import org.apache.atlas.model.discovery.AtlasSearchResult;
import org.apache.atlas.model.discovery.QuickSearchParameters;
import org.apache.atlas.model.discovery.RelationshipSearchParameters;
import org.apache.atlas.model.discovery.SearchParameters;
import org.apache.atlas.model.instance.AtlasClassification;
import org.apache.atlas.model.instance.AtlasEntity;
import org.apache.atlas.model.instance.AtlasEntityHeader;
import org.apache.atlas.model.instance.EntityMutationResponse;
import org.apache.atlas.repository.graph.AtlasGraphProvider;
import org.apache.atlas.repository.store.graph.v2.AtlasEntityStream;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Guice;
import org.testng.annotations.Test;

import javax.inject.Inject;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static org.apache.atlas.model.discovery.SearchParameters.ALL_CLASSIFICATION_TYPES;
import static org.apache.atlas.model.discovery.SearchParameters.ALL_ENTITY_TYPES;
import static org.apache.atlas.model.discovery.SearchParameters.NO_CLASSIFICATIONS;
import static org.apache.atlas.model.discovery.SearchParameters.Operator;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertSame;
import static org.testng.Assert.assertTrue;

@Guice(modules = TestModules.TestOnlyModule.class)
public class AtlasDiscoveryServiceTest extends BasicTestSetup {
    String salesFactGuid;
    String spChar1 = "default.test_dot_name";
    String spChar2 = "default.test_dot_name@db.test_db";
    String spChar3  = "default.test_dot_name_12.col1@db1";
    String spChar4  = "default_.test_dot_name";
    String spChar5  = "default.test_colon_name:test_db";
    String spChar6  = "default.test_colon_name:-test_db";
    String spChar7  = "crn:def:default:name-76778-87e7-23@test";
    String spChar8  = "default.:test_db_name";
    String spChar9  = "default.customer's_name";
    String spChar10 = "default.customers'_data_name";
    String spChar11 = "search_with space@name";
    String spChar12 = "search_with space 123@name";
    String spChar13 = "search_with_special-char#having$and%inthename=attr";
    String spChar14 = "search_with_specialChar!name";
    String spChar15 = "search_with_star*in_name";
    String spChar16 = "search_with_star5.*5_inname";
    String spChar17 = "search_quest?n_name";
    String spChar18 = "/warehouse/tablespace/external/hive/name/hortonia_bank";
    String spChar19 = "/warehouse/tablespace/external/hive/name/exis_bank";
    String spChar20 = "search_name_with nameblank@namecluster";

    @Inject
    private AtlasDiscoveryService discoveryService;

    @BeforeClass
    public void setup() throws Exception {
        super.initialize();

        ApplicationProperties.get().setProperty(ApplicationProperties.ENABLE_FREETEXT_SEARCH_CONF, true);

        setupTestData();

        createDimensionalTaggedEntity("sales");
        createSpecialCharTestEntities();
        setupRelationshipTestData();
        createJapaneseEntityWithDescription();
    }

    @Test
    public void term() throws AtlasBaseException {
        SearchParameters params = new SearchParameters();

        params.setTermName(SALES_TERM + "@" + SALES_GLOSSARY);

        assertSearchProcessorWithoutMarker(params, 10);
    }

    // TSP execute and CSP,ESP filter
    @Test
    public void termTag() throws AtlasBaseException {
        SearchParameters params = new SearchParameters();

        params.setTermName(SALES_TERM + "@" + SALES_GLOSSARY);
        params.setClassification(METRIC_CLASSIFICATION);

        assertSearchProcessorWithoutMarker(params, 4);
    }

    @Test
    public void termEntity() throws AtlasBaseException {
        SearchParameters params = new SearchParameters();

        params.setTermName(SALES_TERM + "@" + SALES_GLOSSARY);
        params.setTypeName(HIVE_TABLE_TYPE);

        assertSearchProcessorWithoutMarker(params, 10);
    }

    @Test
    public void termEntityTag() throws AtlasBaseException {
        SearchParameters params = new SearchParameters();

        params.setTermName(SALES_TERM + "@" + SALES_GLOSSARY);
        params.setTypeName(HIVE_TABLE_TYPE);
        params.setClassification(DIMENSIONAL_CLASSIFICATION);

        List<AtlasEntityHeader> entityHeaders = discoveryService.searchWithParameters(params).getEntities();

        assertTrue(CollectionUtils.isEmpty(entityHeaders));
    }

    //FSP execute and CSP,ESP filter
    @Test
    public void queryALLTag() throws AtlasBaseException {
        SearchParameters params = new SearchParameters();

        params.setClassification(ALL_CLASSIFICATION_TYPES);
        params.setQuery("sales");

        assertSearchProcessorWithoutMarker(params, 5);
    }

    @Test
    public void queryALLTagTagFilter() throws AtlasBaseException {
        SearchParameters.FilterCriteria fc     = getSingleFilterCondition("__typeName", Operator.NOT_CONTAINS, METRIC_CLASSIFICATION);
        SearchParameters                params = new SearchParameters();

        params.setClassification(ALL_CLASSIFICATION_TYPES);
        //typeName will check for only classification name not propogated classification
        params.setTagFilters(fc);
        params.setQuery("sales");

        assertSearchProcessorWithoutMarker(params, 4);
    }

    @Test
    public void queryNOTCLASSIFIEDTag() throws AtlasBaseException {
        SearchParameters params = new SearchParameters();

        params.setClassification(NO_CLASSIFICATIONS);
        params.setQuery("sales");

        assertSearchProcessorWithoutMarker(params, 11);
    }

    @Test
    public void queryALLWildcardTag() throws AtlasBaseException {
        SearchParameters params = new SearchParameters();

        params.setClassification("*");
        params.setQuery("sales");

        assertSearchProcessorWithoutMarker(params, 5);
    }

    @Test
    public void queryWildcardTag() throws AtlasBaseException {
        SearchParameters params = new SearchParameters();

        params.setClassification("Dimen*on");
        params.setQuery("sales");

        assertSearchProcessorWithoutMarker(params, 2);
    }

    @Test
    public void queryTag() throws AtlasBaseException {
        SearchParameters params = new SearchParameters();

        params.setClassification(METRIC_CLASSIFICATION);
        params.setQuery("sales");

        assertSearchProcessorWithoutMarker(params, 3);
    }

    @Test
    public void queryTagTagFilter() throws AtlasBaseException {
        SearchParameters.FilterCriteria fc     = getSingleFilterCondition("__timestamp", SearchParameters.Operator.LT, String.valueOf(System.currentTimeMillis()));
        SearchParameters                params = new SearchParameters();

        params.setClassification(METRIC_CLASSIFICATION);
        params.setTagFilters(fc);
        params.setQuery("sales");

        assertSearchProcessorWithoutMarker(params, 3);
    }

    @Test
    public void queryEntity() throws AtlasBaseException {
        SearchParameters params = new SearchParameters();

        params.setTypeName(HIVE_TABLE_TYPE);
        params.setQuery("sales");

        assertSearchProcessorWithoutMarker(params, 4);
    }

    @Test
    public void queryEntityEntityFilter() throws AtlasBaseException {
        SearchParameters.FilterCriteria fc     = getSingleFilterCondition("tableType", Operator.NOT_NULL, "null");
        SearchParameters                params = new SearchParameters();

        params.setTypeName(HIVE_TABLE_TYPE);
        params.setEntityFilters(fc);
        params.setQuery("sales");

        assertSearchProcessorWithoutMarker(params, 3);
    }

    @Test
    public void queryEntityEntityFilterTag() throws AtlasBaseException {
        SearchParameters.FilterCriteria fc     = getSingleFilterCondition("tableType", Operator.IS_NULL, "null");
        SearchParameters                params = new SearchParameters();

        params.setTypeName(HIVE_TABLE_TYPE);
        params.setEntityFilters(fc);
        params.setClassification(DIMENSIONAL_CLASSIFICATION);
        params.setQuery("sales");

        assertSearchProcessorWithoutMarker(params, 1);
    }

    @Test
    public void queryEntityEntityFilterTagTagFilter() throws AtlasBaseException {
        SearchParameters.FilterCriteria fcE    = getSingleFilterCondition("tableType", Operator.IS_NULL, "null");
        SearchParameters.FilterCriteria fcC    = getSingleFilterCondition("attr1", Operator.EQ, "value1");
        SearchParameters                params = new SearchParameters();

        params.setTypeName(HIVE_TABLE_TYPE);
        params.setEntityFilters(fcE);
        params.setClassification(DIMENSIONAL_CLASSIFICATION);
        params.setQuery("sales");
        params.setTagFilters(fcC);

        assertSearchProcessorWithoutMarker(params, 1);
    }

    @Test
    public void queryEntityTagTagFilter() throws AtlasBaseException {
        SearchParameters.FilterCriteria fc     = getSingleFilterCondition("__timestamp", SearchParameters.Operator.LT, String.valueOf(System.currentTimeMillis()));
        SearchParameters                params = new SearchParameters();

        params.setTypeName(HIVE_TABLE_TYPE);
        params.setClassification(METRIC_CLASSIFICATION);
        params.setTagFilters(fc);
        params.setQuery("sales");

        assertSearchProcessorWithoutMarker(params, 2);
    }

    @Test
    public void queryEntityTag() throws AtlasBaseException {
        SearchParameters params = new SearchParameters();

        params.setTypeName(HIVE_TABLE_TYPE);
        params.setClassification(METRIC_CLASSIFICATION);
        params.setQuery("sales");

        assertSearchProcessorWithoutMarker(params, 2);
    }

    // CSP Execute and ESP filter
    @Test
    public void entityEntityFilterTagTagFilter() throws AtlasBaseException {
        SearchParameters.FilterCriteria fcE    = getSingleFilterCondition("tableType", Operator.EQ, "Managed");
        SearchParameters.FilterCriteria fcC    = getSingleFilterCondition("__timestamp", SearchParameters.Operator.LT, String.valueOf(System.currentTimeMillis()));
        SearchParameters                params = new SearchParameters();

        params.setTypeName(HIVE_TABLE_TYPE);
        params.setEntityFilters(fcE);
        params.setClassification(METRIC_CLASSIFICATION);
        params.setTagFilters(fcC);

        assertSearchProcessorWithoutMarker(params, 4);
    }

    @Test
    public void entityTagTagFilter() throws AtlasBaseException {
        SearchParameters.FilterCriteria fc     = getSingleFilterCondition("__timestamp", SearchParameters.Operator.LT, String.valueOf(System.currentTimeMillis()));
        SearchParameters                params = new SearchParameters();

        params.setTypeName(HIVE_TABLE_TYPE);
        params.setClassification(METRIC_CLASSIFICATION);
        params.setTagFilters(fc);

        assertSearchProcessorWithoutMarker(params, 4);
    }

    @Test
    public void searchWith0offsetMarker() throws AtlasBaseException {
        SearchParameters params = new SearchParameters();

        params.setTypeName(HIVE_TABLE_TYPE);
        params.setOffset(0);
        params.setMarker(SearchContext.MarkerUtil.MARKER_START);
        params.setLimit(5);

        assertSearchProcessorWithMarker(params, 5);
    }

    @Test
    public void searchWithNoOffsetMarker() throws AtlasBaseException {
        SearchParameters params = new SearchParameters();

        params.setTypeName(HIVE_TABLE_TYPE);
        params.setMarker(SearchContext.MarkerUtil.MARKER_START);
        params.setLimit(5);

        assertSearchProcessorWithMarker(params, 5);
    }

    @Test
    public void searchWithGreaterThan0OffsetBlankMarker() throws AtlasBaseException {
        SearchParameters params = new SearchParameters();

        params.setTypeName(HIVE_TABLE_TYPE);
        params.setOffset(1);
        params.setMarker("");
        params.setLimit(5);

        assertSearchProcessorWithoutMarker(params, 5);
    }

    @Test(expectedExceptions = AtlasBaseException.class, expectedExceptionsMessageRegExp = "Marker can be used only if offset=0.")
    public void searchWithGreaterThan0OffsetMarker() throws AtlasBaseException {
        SearchParameters params = new SearchParameters();

        params.setTypeName(HIVE_TABLE_TYPE);
        params.setOffset(1);
        params.setMarker(SearchContext.MarkerUtil.MARKER_START);
        params.setLimit(5);

        List<AtlasEntityHeader> entityHeaders = discoveryService.searchWithParameters(params).getEntities();

        assertNotNull(entityHeaders);
    }

    @Test
    public void searchWithMarkerSet() throws AtlasBaseException {
        SearchParameters params = new SearchParameters();

        params.setTypeName(HIVE_TABLE_TYPE);
        params.setMarker(SearchContext.MarkerUtil.MARKER_START);
        params.setLimit(5);

        AtlasSearchResult       searchResult  = discoveryService.searchWithParameters(params);
        List<AtlasEntityHeader> entityHeaders = searchResult.getEntities();

        assertTrue(CollectionUtils.isNotEmpty(entityHeaders));
        assertEquals(entityHeaders.size(), 5);
        assertTrue(StringUtils.isNotEmpty(searchResult.getNextMarker()));

        //get next marker and set in marker of subsequent request
        params.setMarker(searchResult.getNextMarker());

        AtlasSearchResult       nextsearchResult  = discoveryService.searchWithParameters(params);
        List<AtlasEntityHeader> nextentityHeaders = nextsearchResult.getEntities();

        assertTrue(CollectionUtils.isNotEmpty(nextentityHeaders));
        assertTrue(StringUtils.isNotEmpty(nextsearchResult.getNextMarker()));

        if (entityHeaders.size() < params.getLimit()) {
            assertSame(nextsearchResult.getNextMarker(), String.valueOf(-1));
        }
    }

    @Test(expectedExceptions = AtlasBaseException.class, expectedExceptionsMessageRegExp = "Invalid marker!")
    public void searchWithInvalidMarker() throws AtlasBaseException {
        SearchParameters params = new SearchParameters();

        params.setTypeName(HIVE_TABLE_TYPE);
        params.setMarker(SearchContext.MarkerUtil.MARKER_START);
        params.setLimit(5);

        AtlasSearchResult       searchResult  = discoveryService.searchWithParameters(params);
        List<AtlasEntityHeader> entityHeaders = searchResult.getEntities();

        assertTrue(CollectionUtils.isNotEmpty(entityHeaders));
        assertEquals(entityHeaders.size(), 5);
        assertTrue(StringUtils.isNotEmpty(searchResult.getNextMarker()));

        //get next marker and set in marker of subsequent request
        params.setMarker(searchResult.getNextMarker() + "abc");

        discoveryService.searchWithParameters(params);
    }

    @Test
    public void searchWithLastPageMarker() throws AtlasBaseException {
        SearchParameters params = new SearchParameters();

        params.setTypeName(HIVE_TABLE_TYPE);
        params.setExcludeDeletedEntities(true);
        params.setMarker(SearchContext.MarkerUtil.MARKER_START);

        int totalCount = discoveryService.searchWithParameters(params).getEntities().size();

        params.setMarker(SearchContext.MarkerUtil.MARKER_START);
        params.setLimit(5);

        AtlasSearchResult       searchResult  = discoveryService.searchWithParameters(params);
        List<AtlasEntityHeader> entityHeaders = searchResult.getEntities();

        assertTrue(CollectionUtils.isNotEmpty(entityHeaders));
        assertEquals(entityHeaders.size(), 5);
        assertTrue(StringUtils.isNotEmpty(searchResult.getNextMarker()));

        //get next marker and set in marker of subsequent request
        params.setMarker(SearchContext.MarkerUtil.MARKER_START);
        params.setLimit(totalCount + 10);

        AtlasSearchResult nextsearchResult = discoveryService.searchWithParameters(params);

        assertEquals(nextsearchResult.getNextMarker(), "-1");
    }

    @Test //marker functionality is not supported
    public void termMarker() throws AtlasBaseException {
        SearchParameters params = new SearchParameters();

        params.setTermName(SALES_TERM + "@" + SALES_GLOSSARY);
        params.setMarker("*");

        assertSearchProcessorWithoutMarker(params, 10);
    }

    @Test
    public void queryEntityTagMarker() throws AtlasBaseException {
        SearchParameters params = new SearchParameters();

        params.setTypeName(HIVE_TABLE_TYPE);
        params.setClassification(METRIC_CLASSIFICATION);
        params.setQuery("sales");
        params.setMarker("*");
        params.setLimit(5);

        assertSearchProcessorWithMarker(params, 2);
    }

    // CSP Execute and ESP filter
    @Test
    public void entityEntityFilterTagTagFilterMarker() throws AtlasBaseException {
        SearchParameters.FilterCriteria fcE    = getSingleFilterCondition("tableType", Operator.EQ, "Managed");
        SearchParameters.FilterCriteria fcC    = getSingleFilterCondition("__timestamp", SearchParameters.Operator.LT, String.valueOf(System.currentTimeMillis()));
        SearchParameters                params = new SearchParameters();

        params.setTypeName(HIVE_TABLE_TYPE);
        params.setEntityFilters(fcE);
        params.setClassification(METRIC_CLASSIFICATION);
        params.setTagFilters(fcC);
        params.setMarker("*");

        assertSearchProcessorWithoutMarker(params, 4);
    }

    public void createSpecialCharTestEntities() throws AtlasBaseException {
        List<String> nameList = Arrays.asList(spChar1, spChar2, spChar3, spChar4, spChar5, spChar6, spChar7, spChar8, spChar9, spChar10, spChar11, spChar12, spChar13, spChar14, spChar15, spChar16, spChar17, spChar18, spChar19, spChar20);

        for (String nameStr : nameList) {
            AtlasEntity entityToDelete = new AtlasEntity(HIVE_TABLE_TYPE);

            entityToDelete.setAttribute("name", nameStr);
            entityToDelete.setAttribute(AtlasClient.REFERENCEABLE_ATTRIBUTE_NAME, "qualifiedName" + System.currentTimeMillis());

            //create entity
            entityStore.createOrUpdate(new AtlasEntityStream(new AtlasEntity.AtlasEntitiesWithExtInfo(entityToDelete)), false);
        }

        List<String> qfList = nameList;

        for (String qfStr : qfList) {
            qfStr = qfStr.replace("name", "qf");

            AtlasEntity entityToDelete = new AtlasEntity(HIVE_TABLE_TYPE);

            entityToDelete.setAttribute("name", "name" + System.currentTimeMillis());
            entityToDelete.setAttribute(AtlasClient.REFERENCEABLE_ATTRIBUTE_NAME, qfStr);

            //create entity
            entityStore.createOrUpdate(new AtlasEntityStream(new AtlasEntity.AtlasEntitiesWithExtInfo(entityToDelete)), false);
        }
    }

    @Test(dataProvider = "specialCharSearchEQ")
    public void specialCharSearchAssertEq(String attrName, SearchParameters.Operator operator, String attrValue, int expected) throws AtlasBaseException {
        SearchParameters.FilterCriteria filterCriteria = getSingleFilterCondition(attrName, operator, attrValue);
        SearchParameters                params         = new SearchParameters();

        params.setTypeName(HIVE_TABLE_TYPE);
        params.setEntityFilters(filterCriteria);
        params.setLimit(20);

        AtlasSearchResult searchResult = discoveryService.searchWithParameters(params);

        assertSearchResult(searchResult, expected, attrValue);
    }

    @Test(dataProvider = "specialCharSearchContains")
    public void specialCharSearchAssertContains(String attrName, SearchParameters.Operator operator, String attrValue, int expected) throws AtlasBaseException {
        SearchParameters.FilterCriteria filterCriteria = getSingleFilterCondition(attrName, operator, attrValue);
        SearchParameters                params         = new SearchParameters();

        params.setTypeName(HIVE_TABLE_TYPE);
        params.setEntityFilters(filterCriteria);
        params.setLimit(20);

        AtlasSearchResult searchResult = discoveryService.searchWithParameters(params);

        assertSearchResult(searchResult, expected, attrValue);
    }

    @Test(dataProvider = "specialCharSearchContains")
    public void specialCharQuickSearchAssertContains(String attrName, SearchParameters.Operator operator, String attrValue, int expected) throws AtlasBaseException {
        SearchParameters.FilterCriteria filterCriteria = getSingleFilterCondition(attrName, operator, attrValue);
        QuickSearchParameters           params         = new QuickSearchParameters();

        params.setTypeName(HIVE_TABLE_TYPE);
        params.setEntityFilters(filterCriteria);
        params.setLimit(20);

        AtlasQuickSearchResult searchResult = discoveryService.quickSearch(params);

        assertSearchResult(searchResult.getSearchResults(), expected, attrValue);

        List<String> failedCases = Arrays.asList("def:default:qf-", "star*in", "ith spac", "778-87");

        if (attrName.equals("qualifiedName") && failedCases.contains(attrValue)) {
            expected = 0;
        }

        if (expected > 0) {
            assertAggregationMetrics(searchResult);
        }
    }

    @Test(dataProvider = "specialCharSearchName")
    public void specialCharSearchAssertName(String attrName, SearchParameters.Operator operator, String attrValue, int expected) throws AtlasBaseException {
        SearchParameters.FilterCriteria filterCriteria = getSingleFilterCondition(attrName, operator, attrValue);
        SearchParameters                params         = new SearchParameters();

        params.setTypeName(HIVE_TABLE_TYPE);
        params.setEntityFilters(filterCriteria);
        params.setLimit(20);

        AtlasSearchResult searchResult = discoveryService.searchWithParameters(params);

        assertSearchResult(searchResult, expected, attrValue);
    }

    @Test(dataProvider = "specialCharSearchName")
    public void specialCharQuickSearchAssertName(String attrName, SearchParameters.Operator operator, String attrValue, int expected) throws AtlasBaseException {
        SearchParameters.FilterCriteria filterCriteria = getSingleFilterCondition(attrName, operator, attrValue);
        QuickSearchParameters           params         = new QuickSearchParameters();

        params.setTypeName(HIVE_TABLE_TYPE);
        params.setEntityFilters(filterCriteria);
        params.setLimit(20);

        AtlasQuickSearchResult searchResult = discoveryService.quickSearch(params);

        assertSearchResult(searchResult.getSearchResults(), expected, attrValue);

        if (expected > 0) {
            assertAggregationMetrics(searchResult);
        }
    }

    @Test(dataProvider = "specialCharSearchQFName")
    public void specialCharSearchAssertQFName(String attrName, SearchParameters.Operator operator, String attrValue, int expected) throws AtlasBaseException {
        SearchParameters.FilterCriteria filterCriteria = getSingleFilterCondition(attrName, operator, attrValue);
        SearchParameters                params         = new SearchParameters();

        params.setTypeName(HIVE_TABLE_TYPE);
        params.setEntityFilters(filterCriteria);
        params.setLimit(20);

        AtlasSearchResult searchResult = discoveryService.searchWithParameters(params);

        assertSearchResult(searchResult, expected, attrValue);
    }

    @Test(dataProvider = "specialCharSearchQFName")
    public void specialCharQuickSearchAssertQFName(String attrName, SearchParameters.Operator operator, String attrValue, int expected) throws AtlasBaseException {
        SearchParameters.FilterCriteria filterCriteria = getSingleFilterCondition(attrName, operator, attrValue);
        QuickSearchParameters           params         = new QuickSearchParameters();

        params.setTypeName(HIVE_TABLE_TYPE);
        params.setEntityFilters(filterCriteria);
        params.setLimit(20);

        AtlasQuickSearchResult searchResult = discoveryService.quickSearch(params);

        assertSearchResult(searchResult.getSearchResults(), expected, attrValue);

        if (expected > 0) {
            assertAggregationMetrics(searchResult);
        }
    }

    @Test(dataProvider = "specialCharQuickSearch")
    public void specialCharQuickSearch(String searchValue, int expected) throws AtlasBaseException {
        QuickSearchParameters params = new QuickSearchParameters();

        params.setQuery(searchValue);
        params.setLimit(5);
        params.setOffset(0);

        AtlasQuickSearchResult searchResult = discoveryService.quickSearch(params);

        assertSearchResult(searchResult.getSearchResults(), expected, searchValue);

        if (expected > 0) {
            assertAggregationMetrics(searchResult);
        }
    }

    @Test
    public void searchWithEntityQuickSearchSortAsc() throws AtlasBaseException {
        QuickSearchParameters params = new QuickSearchParameters();

        params.setTypeName("hive_table");
        params.setQuery("sales");
        params.setExcludeDeletedEntities(true);
        params.setLimit(3);
        params.setSortBy("owner");
        params.setSortOrder(SortOrder.ASCENDING);

        AtlasQuickSearchResult  searchResult = discoveryService.quickSearch(params);
        List<AtlasEntityHeader> list         = searchResult.getSearchResults().getEntities();

        assertTrue(CollectionUtils.isNotEmpty(list));
        assertEquals(list.size(), 3);
        assertTrue(list.get(0).getAttribute("owner").toString().equalsIgnoreCase("Jane BI"));
        assertTrue(list.get(1).getAttribute("owner").toString().equalsIgnoreCase("Joe"));
    }

    @Test
    public void searchWithEntityQuickSearchSortDesc() throws AtlasBaseException {
        QuickSearchParameters params = new QuickSearchParameters();

        params.setTypeName("hive_table");
        params.setQuery("sales");
        params.setExcludeDeletedEntities(true);
        params.setLimit(3);
        params.setSortBy("name");
        params.setSortOrder(SortOrder.DESCENDING);

        AtlasQuickSearchResult  searchResult = discoveryService.quickSearch(params);
        List<AtlasEntityHeader> list         = searchResult.getSearchResults().getEntities();

        assertTrue(CollectionUtils.isNotEmpty(list));
        assertEquals(list.size(), 3);
        assertTrue(list.get(0).getDisplayText().equalsIgnoreCase("time_dim"));
        assertTrue(list.get(1).getDisplayText().equalsIgnoreCase("sales_fact_monthly_mv"));
    }

    @Test
    public void searchRelatedEntitiesSortAsc() throws AtlasBaseException {
        String           guid   = gethiveTableSalesFactGuid();
        SearchParameters params = new SearchParameters();

        params.setLimit(10);

        AtlasSearchResult       relResult = discoveryService.searchRelatedEntities(guid, "__hive_table.columns", false, params, false);
        List<AtlasEntityHeader> list      = relResult.getEntities();

        assertTrue(CollectionUtils.isNotEmpty(list));
        assertEquals(list.size(), 6);
        assertTrue(list.get(0).getDisplayText().equalsIgnoreCase("customer_id"));
        assertTrue(list.get(5).getDisplayText().equalsIgnoreCase("time_id"));
    }

    @Test
    public void searchRelatedEntitiesSortDesc() throws AtlasBaseException {
        String           guid   = gethiveTableSalesFactGuid();
        SearchParameters params = new SearchParameters();

        params.setLimit(10);
        params.setSortOrder(SortOrder.DESCENDING);

        AtlasSearchResult       relResult = discoveryService.searchRelatedEntities(guid, "columns", false, params, false);
        List<AtlasEntityHeader> list      = relResult.getEntities();

        assertTrue(CollectionUtils.isNotEmpty(list));
        assertEquals(list.size(), 6);
        assertTrue(list.get(5).getDisplayText().equalsIgnoreCase("customer_id"));
        assertTrue(list.get(0).getDisplayText().equalsIgnoreCase("time_id"));
    }

    @Test
    public void testSearchRelatedEntitiesWithDisableDefaultSorting() throws AtlasBaseException {
        String           guid   = gethiveTableSalesFactGuid();
        SearchParameters params = new SearchParameters();

        params.setLimit(10);

        // Test 1: With disableDefaultSorting=true (no sorting applied)
        AtlasSearchResult unsortedResult = discoveryService.searchRelatedEntities(
                guid, "__hive_table.columns", false, params, true);
        List<AtlasEntityHeader> unsortedList = unsortedResult.getEntities();

        assertTrue(CollectionUtils.isNotEmpty(unsortedList));
        assertEquals(unsortedList.size(), 6);

        // Test 2: With disableDefaultSorting=false (default name sorting applied)
        AtlasSearchResult sortedResult = discoveryService.searchRelatedEntities(
                guid, "__hive_table.columns", false, params, false);
        List<AtlasEntityHeader> sortedList = sortedResult.getEntities();

        assertTrue(CollectionUtils.isNotEmpty(sortedList));
        assertEquals(sortedList.size(), 6);

        // Both should return same number of entities (order may differ)
        assertEquals(unsortedList.size(), sortedList.size());

        // Verify all entities are present in both results
        Set<String> unsortedGuids = unsortedList.stream()
                .map(AtlasEntityHeader::getGuid)
                .collect(java.util.stream.Collectors.toSet());
        Set<String> sortedGuids = sortedList.stream()
                .map(AtlasEntityHeader::getGuid)
                .collect(java.util.stream.Collectors.toSet());

        assertEquals(unsortedGuids, sortedGuids, "Both methods should return same entities");
    }

    @Test
    public void testSearchRelatedEntitiesWithOffset() throws AtlasBaseException {
        String           guid   = gethiveTableSalesFactGuid();
        SearchParameters params = new SearchParameters();

        // Fetch first page (entities 0-2)
        params.setOffset(0);
        params.setLimit(2);
        AtlasSearchResult page1 = discoveryService.searchRelatedEntities(
                guid, "__hive_table.columns", false, params, true);
        List<AtlasEntityHeader> page1List = page1.getEntities();

        assertTrue(CollectionUtils.isNotEmpty(page1List));
        assertEquals(page1List.size(), 2);

        // Fetch second page (entities 2-4)
        params.setOffset(2);
        params.setLimit(2);
        AtlasSearchResult page2 = discoveryService.searchRelatedEntities(
                guid, "__hive_table.columns", false, params, true);
        List<AtlasEntityHeader> page2List = page2.getEntities();

        assertTrue(CollectionUtils.isNotEmpty(page2List));
        assertEquals(page2List.size(), 2);

        // Fetch third page (entities 4-6)
        params.setOffset(4);
        params.setLimit(2);
        AtlasSearchResult page3 = discoveryService.searchRelatedEntities(
                guid, "__hive_table.columns", false, params, true);
        List<AtlasEntityHeader> page3List = page3.getEntities();

        assertTrue(CollectionUtils.isNotEmpty(page3List));
        assertEquals(page3List.size(), 2);

        // Verify pages are different
        assertTrue(!page1List.get(0).getGuid().equals(page2List.get(0).getGuid()),
                "Page 1 and Page 2 should have different first entities");
        assertTrue(!page2List.get(0).getGuid().equals(page3List.get(0).getGuid()),
                "Page 2 and Page 3 should have different first entities");

        // Verify offset beyond available entities returns empty
        params.setOffset(100);
        params.setLimit(10);
        AtlasSearchResult emptyPage = discoveryService.searchRelatedEntities(
                guid, "__hive_table.columns", false, params, true);

        assertTrue(CollectionUtils.isEmpty(emptyPage.getEntities()) || emptyPage.getEntities().size() == 0);
    }

    @Test
    public void testSearchRelatedEntitiesWithDeletedEntities() throws AtlasBaseException {
        String           guid   = gethiveTableSalesFactGuid();
        SearchParameters params = new SearchParameters();

        params.setLimit(20);
        params.setExcludeDeletedEntities(true);

        // Test with disableDefaultSorting=true and excludeDeletedEntities=true
        AtlasSearchResult result = discoveryService.searchRelatedEntities(
                guid, "__hive_table.columns", false, params, true);

        assertNotNull(result);
        List<AtlasEntityHeader> entities = result.getEntities();
        assertTrue(CollectionUtils.isNotEmpty(entities));

        // Verify all returned entities are ACTIVE
        for (AtlasEntityHeader entity : entities) {
            assertTrue(!AtlasEntity.Status.DELETED.equals(entity.getStatus()),
                    "Should not return DELETED entities when excludeDeletedEntities=true");
        }

        // Test with excludeDeletedEntities=false to get all entities
        params.setExcludeDeletedEntities(false);
        AtlasSearchResult resultWithDeleted = discoveryService.searchRelatedEntities(
                guid, "__hive_table.columns", false, params, true);

        assertNotNull(resultWithDeleted);
        // Result should have same or more entities when not excluding deleted
        assertTrue(resultWithDeleted.getEntities().size() >= result.getEntities().size());
    }

    @Test
    public void testApproximateCountExcludesDeleted() throws AtlasBaseException {
        String           guid   = gethiveTableSalesFactGuid();
        SearchParameters params = new SearchParameters();

        params.setLimit(10);
        params.setExcludeDeletedEntities(true);

        // Test with disableDefaultSorting=true and getApproximateCount=true
        AtlasSearchResult result = discoveryService.searchRelatedEntities(
                guid, "__hive_table.columns", true, params, true);

        assertNotNull(result);
        assertTrue(result.getApproximateCount() > 0, "Approximate count should be greater than 0");

        // The count should reflect only ACTIVE entities
        long approximateCount = result.getApproximateCount();
        long actualCount = result.getEntities().size();

        // Approximate count should be close to actual entity count
        // (may differ if there are more entities beyond limit)
        assertTrue(approximateCount >= actualCount,
                "Approximate count should be >= actual returned entities");

        // Test without excluding deleted entities
        params.setExcludeDeletedEntities(false);
        AtlasSearchResult resultWithDeleted = discoveryService.searchRelatedEntities(
                guid, "__hive_table.columns", true, params, true);

        assertNotNull(resultWithDeleted);
        long countWithDeleted = resultWithDeleted.getApproximateCount();

        // Count including deleted should be >= count excluding deleted
        assertTrue(countWithDeleted >= approximateCount,
                "Count with deleted entities should be >= count without deleted");
    }

    //test excludeHeaderAttributes
    @Test
    public void excludeHeaderAttributesStringAttr() throws AtlasBaseException {
        SearchParameters.FilterCriteria filterCriteria = getSingleFilterCondition("tableType", Operator.EQ, "Managed");
        SearchParameters                params         = new SearchParameters();

        params.setTypeName(HIVE_TABLE_TYPE);
        params.setExcludeHeaderAttributes(true);
        params.setEntityFilters(filterCriteria);
        params.setSortBy("name");
        params.setAttributes(new HashSet<>(Collections.singletonList("name")));
        params.setLimit(1);

        AtlasSearchResult                       searchResult = discoveryService.searchWithParameters(params);
        AtlasSearchResult.AttributeSearchResult expected     = new AtlasSearchResult.AttributeSearchResult();

        expected.setName(Collections.singletonList("name"));
        expected.setValues(Collections.singletonList(Collections.singletonList("log_fact_daily_mv")));

        assertSearchResult(searchResult, expected);
    }

    @Test
    public void excludeHeaderAttributesRelationAttr() throws AtlasBaseException {
        SearchParameters.FilterCriteria filterCriteria = getSingleFilterCondition("name", Operator.EQ, "time_dim");
        SearchParameters                params         = new SearchParameters();

        params.setTypeName(HIVE_TABLE_TYPE);
        params.setExcludeHeaderAttributes(true);
        params.setEntityFilters(filterCriteria);
        params.setAttributes(new HashSet<>(Arrays.asList("name", "db")));
        params.setLimit(1);

        AtlasSearchResult searchResult = discoveryService.searchWithParameters(params);

        assertNotNull(searchResult);
        assertNotNull(searchResult.getEntities());
        assertNotNull(searchResult.getReferredEntities());
    }

    @Test
    public void excludeHeaderAttributesSystemAttr() throws AtlasBaseException {
        SearchParameters.FilterCriteria filterCriteria = getSingleFilterCondition("tableType", Operator.EQ, "Managed");
        SearchParameters                params         = new SearchParameters();

        params.setTypeName(HIVE_TABLE_TYPE);
        params.setExcludeHeaderAttributes(true);
        params.setAttributes(new HashSet<>(Arrays.asList("name", "__state")));
        params.setLimit(1);
        params.setEntityFilters(filterCriteria);
        params.setSortBy("name");

        AtlasSearchResult                       searchResult = discoveryService.searchWithParameters(params);
        AtlasSearchResult.AttributeSearchResult expected     = new AtlasSearchResult.AttributeSearchResult();

        expected.setName(Arrays.asList("name", "__state"));
        expected.setValues(Arrays.asList(Arrays.asList("log_fact_daily_mv", "ACTIVE")));

        assertSearchResult(searchResult, expected);
    }

    @Test
    public void excludeHeaderAttributesAllEntityTypeSysAttr() throws AtlasBaseException {
        SearchParameters params = new SearchParameters();

        params.setTypeName(HIVE_TABLE_TYPE + "," + ALL_ENTITY_TYPES);
        params.setExcludeHeaderAttributes(true);
        params.setAttributes(new HashSet<>(Collections.singletonList("__state")));
        params.setLimit(2);

        AtlasSearchResult                       searchResult = discoveryService.searchWithParameters(params);
        AtlasSearchResult.AttributeSearchResult expected     = new AtlasSearchResult.AttributeSearchResult();

        expected.setName(Arrays.asList("__state"));
        expected.setValues(Arrays.asList(Arrays.asList("ACTIVE"), Arrays.asList("ACTIVE")));

        assertSearchResult(searchResult, expected);
    }

    @Test
    public void excludeHeaderAttributesAllEntityTypeSysAttrs() throws AtlasBaseException {
        SearchParameters params = new SearchParameters();

        params.setTypeName(HIVE_TABLE_TYPE + "," + ALL_ENTITY_TYPES);
        params.setExcludeHeaderAttributes(true);
        params.setAttributes(new HashSet<>(Arrays.asList("__state", "__guid")));
        params.setLimit(2);

        AtlasSearchResult searchResult = discoveryService.searchWithParameters(params);
        assertEquals(searchResult.getAttributes().getValues().size(), 2);
    }

    @Test(expectedExceptions = AtlasBaseException.class, expectedExceptionsMessageRegExp = "Attribute name not found for type __ENTITY_ROOT")
    public void excludeHeaderAttributesAllEntityType() throws AtlasBaseException {
        SearchParameters params = new SearchParameters();

        params.setTypeName(HIVE_TABLE_TYPE + "," + ALL_ENTITY_TYPES);
        params.setExcludeHeaderAttributes(true);
        params.setAttributes(new HashSet<>(Collections.singletonList("name")));
        params.setLimit(1);

        discoveryService.searchWithParameters(params);
    }

    @Test(expectedExceptions = AtlasBaseException.class, expectedExceptionsMessageRegExp = "Attribute name1 not found for type hive_table")
    public void excludeHeaderAttributesInvalidAttr() throws AtlasBaseException {
        SearchParameters params = new SearchParameters();

        params.setTypeName(HIVE_TABLE_TYPE);
        params.setExcludeHeaderAttributes(true);
        params.setAttributes(new HashSet<>(Collections.singletonList("name1")));
        params.setLimit(1);

        discoveryService.searchWithParameters(params);
    }

    @Test
    public void searchRelations() throws AtlasBaseException {
        RelationshipSearchParameters sp = new RelationshipSearchParameters();
        sp.setRelationshipName("highlight_post");
        sp.setMarker("*");
        sp.setLimit(10);
        sp.setRelationshipFilters(getSingleFilterCondition("highlight_name", Operator.EQ, "year2021@Ajay"));

        AtlasSearchResult sr = discoveryService.searchRelationsWithParameters(sp);
        assertEquals(sr.getRelations().size(), 4);
    }

    @Test
    public void cjkCharQuickSearch() throws AtlasBaseException {
        addLabels();
        String searchValue = "你";
        int    expected    = 1;

        QuickSearchParameters params = new QuickSearchParameters();
        params.setQuery(searchValue);
        params.setLimit(5);
        params.setOffset(0);

        AtlasQuickSearchResult searchResult = discoveryService.quickSearch(params);
        assertSearchResult(searchResult.getSearchResults(), expected, searchValue);
    }

    @Test(dependsOnMethods = "cjkCharQuickSearch")
    public void cjkCharBasicSearch() throws AtlasBaseException {
        String searchValue = "你好";
        int    expected    = 1;

        SearchParameters params = new SearchParameters();
        params.setQuery(searchValue);
        params.setLimit(5);
        params.setOffset(0);

        AtlasSearchResult searchResult = discoveryService.searchWithParameters(params);
        assertSearchResult(searchResult, expected, searchValue);
    }

    @Test
    public void japaneseReceiptStarSearch() throws AtlasBaseException {
        String searchValue = "レシート";
        int    expected    = 1;

        SearchParameters params = new SearchParameters();
        params.setQuery(searchValue);
        params.setTypeName(HDFS_PATH);
        params.setLimit(5);
        params.setOffset(0);

        AtlasSearchResult searchResult = discoveryService.searchWithParameters(params);
        assertSearchResult(searchResult, expected, searchValue);
    }

    @Test
    public void japaneseLedgerSearch() throws AtlasBaseException {
        String searchValue = "元帳";
        int    expected    = 2;

        SearchParameters params = new SearchParameters();
        params.setQuery(searchValue);
        params.setLimit(5);
        params.setOffset(0);

        AtlasSearchResult searchResult = discoveryService.searchWithParameters(params);
        assertSearchResult(searchResult, expected, searchValue);
    }

    @Test
    public void japaneseLedgerStarSearch() throws AtlasBaseException {
        String searchValue = "の台*";
        int    expected    = 1;

        SearchParameters params = new SearchParameters();
        params.setQuery(searchValue);
        params.setLimit(5);
        params.setOffset(0);

        AtlasSearchResult searchResult = discoveryService.searchWithParameters(params);
        assertSearchResult(searchResult, expected, searchValue);
    }

    @AfterClass
    public void teardown() throws Exception {
        AtlasGraphProvider.cleanup();

        super.cleanup();
    }

    @DataProvider(name = "specialCharSearchContains")
    private Object[][] specialCharSearchContains() {
        return new Object[][] {
                {"name", Operator.CONTAINS, "test_dot", 4},
                {"name", Operator.CONTAINS, "test_dot_name_", 1},
                {"name", Operator.CONTAINS, "test_colon_name", 2},
                {"name", Operator.CONTAINS, "def:default:name", 1},
                {"name", Operator.CONTAINS, "space 12", 1},
                {"name", Operator.CONTAINS, "with space", 2},
                {"name", Operator.CONTAINS, "Char!name", 1},
                {"name", Operator.CONTAINS, "with_star", 2},
                {"name", Operator.CONTAINS, "/external/hive/name/", 2},

                {"name", Operator.CONTAINS, "test_dot_name@db", 1},
                {"name", Operator.CONTAINS, "name@db", 1},
                {"name", Operator.CONTAINS, "def:default:name-", 1},
                {"name", Operator.CONTAINS, "star*in", 1},
                {"name", Operator.CONTAINS, "Char!na", 1},
                {"name", Operator.CONTAINS, "ith spac", 2},
                {"name", Operator.CONTAINS, "778-87", 1},

                {"qualifiedName", Operator.CONTAINS, "test_dot", 4},
                {"qualifiedName", Operator.CONTAINS, "test_dot_qf_", 1},
                {"qualifiedName", Operator.CONTAINS, "test_colon_qf", 2},
                {"qualifiedName", Operator.CONTAINS, "def:default:qf", 1},
                {"qualifiedName", Operator.CONTAINS, "space 12", 1},
                {"qualifiedName", Operator.CONTAINS, "with space", 2},
                {"qualifiedName", Operator.CONTAINS, "Char!qf", 1},
                {"qualifiedName", Operator.CONTAINS, "with_star", 2},
                {"qualifiedName", Operator.CONTAINS, "/external/hive/qf/", 2},

                {"qualifiedName", Operator.CONTAINS, "test_dot_qf@db", 1},
                {"qualifiedName", Operator.CONTAINS, "qf@db", 1},
                {"qualifiedName", Operator.CONTAINS, "def:default:qf-", 1},
                {"qualifiedName", Operator.CONTAINS, "star*in", 1},
                {"qualifiedName", Operator.CONTAINS, "Char!q", 1},
                {"qualifiedName", Operator.CONTAINS, "ith spac", 2},
                {"qualifiedName", Operator.CONTAINS, "778-87", 1},
        };
    }

    @DataProvider(name = "specialCharSearchName")
    private Object[][] specialCharSearchName() {
        return new Object[][] {
                {"name", Operator.STARTS_WITH, "default.test_dot_", 3},

                {"name", Operator.STARTS_WITH, "default.test_dot_name@db.test", 1},
                {"name", Operator.STARTS_WITH, "default.test_dot_name@db.", 1},
                {"name", Operator.STARTS_WITH, "default.test_dot_name", 3},
                {"name", Operator.ENDS_WITH, "test_db", 3},

                {"name", Operator.STARTS_WITH, "default.test_dot_name_12.col1@db", 1},
                {"name", Operator.STARTS_WITH, "default.test_dot_name_12.col1@", 1},
                {"name", Operator.STARTS_WITH, "default.test_dot_name_12.col1", 1},
                {"name", Operator.STARTS_WITH, "default.test_dot_name_12.col", 1},
                {"name", Operator.STARTS_WITH, "default.test_dot_name_12.", 1},
                {"name", Operator.STARTS_WITH, "default.test_dot_name_12", 1},
                {"name", Operator.ENDS_WITH, "col1@db1", 1},

                {"name", Operator.STARTS_WITH, "default_.test_dot", 1},
                {"name", Operator.ENDS_WITH, "test_dot_name", 2},

                {"name", Operator.STARTS_WITH, "default.test_colon_name:test_", 1},

                {"name", Operator.STARTS_WITH, "default.test_colon_name:-test_", 1},
                {"name", Operator.STARTS_WITH, "default.test_colon_name:-", 1},
                {"name", Operator.STARTS_WITH, "default.test_colon", 2},

                {"name", Operator.STARTS_WITH, "crn:def:default:name-76778-87e7-23@", 1},
                {"name", Operator.STARTS_WITH, "crn:def:default:name-76778-87e7-", 1},
                {"name", Operator.STARTS_WITH, "crn:def:default:", 1},

                {"name", Operator.STARTS_WITH, "default.:test_db", 1},
                {"name", Operator.ENDS_WITH, "test_db_name", 1},

                {"name", Operator.STARTS_WITH, "default.customer's", 1},
                {"name", Operator.ENDS_WITH, "mer's_name", 1},

                {"name", Operator.STARTS_WITH, "default.customers'_data", 1},
                {"name", Operator.ENDS_WITH, "customers'_data_name", 1},

                {"name", Operator.STARTS_WITH, "search_with space", 2},
                {"name", Operator.STARTS_WITH, "search_with space ", 1},
                {"name", Operator.STARTS_WITH, "search_with space 123@", 1},
                {"name", Operator.STARTS_WITH, "search_with space 1", 1},

                {"name", Operator.STARTS_WITH, "search_with_special-char#having$and%inthename=", 1},
                {"name", Operator.STARTS_WITH, "search_with_special-char#having$and%in", 1},
                {"name", Operator.STARTS_WITH, "search_with_special-char#having$", 1},
                {"name", Operator.STARTS_WITH, "search_with_special-char#h", 1},
                {"name", Operator.STARTS_WITH, "search_with_special", 2},
                {"name", Operator.STARTS_WITH, "search_with_spe", 2},

                {"name", Operator.STARTS_WITH, "search_with_specialChar!", 1},

                {"name", Operator.STARTS_WITH, "search_with_star*in", 1},

                {"name", Operator.ENDS_WITH, "5.*5_inname", 1},
                {"name", Operator.STARTS_WITH, "search_with_star5.*5_", 1},

                {"name", Operator.STARTS_WITH, "search_quest?n_", 1},

                {"name", Operator.STARTS_WITH, "/warehouse/tablespace/external/hive/name/hortonia", 1},
                {"name", Operator.STARTS_WITH, "/warehouse/tablespace/external/hive/name/", 2},
        };
    }

    @DataProvider(name = "specialCharSearchQFName")
    private Object[][] specialCharSearchQFName() {
        return new Object[][] {
                {"qualifiedName", Operator.STARTS_WITH, "default.test_dot_", 3},

                {"qualifiedName", Operator.STARTS_WITH, "default.test_dot_qf@db.test", 1},
                {"qualifiedName", Operator.STARTS_WITH, "default.test_dot_qf@db.", 1},
                {"qualifiedName", Operator.STARTS_WITH, "default.test_dot_qf", 3},
                {"qualifiedName", Operator.ENDS_WITH, "test_db", 3},

                {"qualifiedName", Operator.STARTS_WITH, "default.test_dot_qf_12.col1@db", 1},
                {"qualifiedName", Operator.STARTS_WITH, "default.test_dot_qf_12.col1@", 1},
                {"qualifiedName", Operator.STARTS_WITH, "default.test_dot_qf_12.col1", 1},
                {"qualifiedName", Operator.STARTS_WITH, "default.test_dot_qf_12.col", 1},
                {"qualifiedName", Operator.STARTS_WITH, "default.test_dot_qf_12.", 1},
                {"qualifiedName", Operator.STARTS_WITH, "default.test_dot_qf_12", 1},
                {"qualifiedName", Operator.ENDS_WITH, "col1@db1", 1},

                {"qualifiedName", Operator.STARTS_WITH, "default_.test_dot", 1},
                {"qualifiedName", Operator.ENDS_WITH, "test_dot_qf", 2},

                {"qualifiedName", Operator.STARTS_WITH, "default.test_colon_qf:test_", 1},

                {"qualifiedName", Operator.STARTS_WITH, "default.test_colon_qf:-test_", 1},
                {"qualifiedName", Operator.STARTS_WITH, "default.test_colon_qf:-", 1},
                {"qualifiedName", Operator.STARTS_WITH, "default.test_colon", 2},

                {"qualifiedName", Operator.STARTS_WITH, "crn:def:default:qf-76778-87e7-23@", 1},
                {"qualifiedName", Operator.STARTS_WITH, "crn:def:default:qf-76778-87e7-", 1},
                {"qualifiedName", Operator.STARTS_WITH, "crn:def:default:", 1},

                {"qualifiedName", Operator.STARTS_WITH, "default.:test_db", 1},
                {"qualifiedName", Operator.ENDS_WITH, "test_db_qf", 1},

                {"qualifiedName", Operator.STARTS_WITH, "default.customer's", 1},
                {"qualifiedName", Operator.ENDS_WITH, "mer's_qf", 1},

                {"qualifiedName", Operator.STARTS_WITH, "default.customers'_data", 1},
                {"qualifiedName", Operator.ENDS_WITH, "customers'_data_qf", 1},

                {"qualifiedName", Operator.STARTS_WITH, "search_with space", 2},
                {"qualifiedName", Operator.STARTS_WITH, "search_with space ", 1},
                {"qualifiedName", Operator.STARTS_WITH, "search_with space 123@", 1},
                {"qualifiedName", Operator.STARTS_WITH, "search_with space 1", 1},

                {"qualifiedName", Operator.STARTS_WITH, "search_with_special-char#having$and%intheqf=", 1},
                {"qualifiedName", Operator.STARTS_WITH, "search_with_special-char#having$and%in", 1},
                {"qualifiedName", Operator.STARTS_WITH, "search_with_special-char#having$", 1},
                {"qualifiedName", Operator.STARTS_WITH, "search_with_special-char#h", 1},
                {"qualifiedName", Operator.STARTS_WITH, "search_with_special", 2},
                {"qualifiedName", Operator.STARTS_WITH, "search_with_spe", 2},

                {"qualifiedName", Operator.STARTS_WITH, "search_with_specialChar!", 1},

                {"qualifiedName", Operator.STARTS_WITH, "search_with_star*in", 1},

                {"qualifiedName", Operator.ENDS_WITH, "5.*5_inqf", 1},
                {"qualifiedName", Operator.STARTS_WITH, "search_with_star5.*5_", 1},

                {"qualifiedName", Operator.STARTS_WITH, "search_quest?n_", 1},

                {"qualifiedName", Operator.STARTS_WITH, "/warehouse/tablespace/external/hive/qf/hortonia", 1},
                {"qualifiedName", Operator.STARTS_WITH, "/warehouse/tablespace/external/hive/qf/", 2},
        };
    }

    @DataProvider(name = "specialCharSearchEQ")
    private Object[][] specialCharSearch() {
        return new Object[][] {
                {"name", Operator.EQ, spChar1, 1},
                {"name", Operator.EQ, spChar2, 1},
                {"name", Operator.EQ, spChar3, 1},
                {"name", Operator.EQ, spChar4, 1},
                {"name", Operator.EQ, spChar5, 1},
                {"name", Operator.EQ, spChar6, 1},
                {"name", Operator.EQ, spChar7, 1},
                {"name", Operator.EQ, spChar8, 1},
                {"name", Operator.EQ, spChar9, 1},
                {"name", Operator.EQ, spChar10, 1},
                {"name", Operator.EQ, spChar11, 1},
                {"name", Operator.EQ, spChar12, 1},
                {"name", Operator.EQ, spChar13, 1},
                {"name", Operator.EQ, spChar14, 1},
                {"name", Operator.EQ, spChar15, 1},
                {"name", Operator.EQ, spChar16, 1},
                {"name", Operator.EQ, spChar17, 1},
                {"name", Operator.EQ, spChar18, 1},
                {"name", Operator.EQ, spChar19, 1},

                {"qualifiedName", Operator.EQ, spChar1.replace("name", "qf"), 1},
                {"qualifiedName", Operator.EQ, spChar2.replace("name", "qf"), 1},
                {"qualifiedName", Operator.EQ, spChar3.replace("name", "qf"), 1},
                {"qualifiedName", Operator.EQ, spChar4.replace("name", "qf"), 1},
                {"qualifiedName", Operator.EQ, spChar5.replace("name", "qf"), 1},
                {"qualifiedName", Operator.EQ, spChar6.replace("name", "qf"), 1},
                {"qualifiedName", Operator.EQ, spChar7.replace("name", "qf"), 1},
                {"qualifiedName", Operator.EQ, spChar8.replace("name", "qf"), 1},
                {"qualifiedName", Operator.EQ, spChar9.replace("name", "qf"), 1},
                {"qualifiedName", Operator.EQ, spChar10.replace("name", "qf"), 1},
                {"qualifiedName", Operator.EQ, spChar11.replace("name", "qf"), 1},
                {"qualifiedName", Operator.EQ, spChar12.replace("name", "qf"), 1},
                {"qualifiedName", Operator.EQ, spChar13.replace("name", "qf"), 1},
                {"qualifiedName", Operator.EQ, spChar14.replace("name", "qf"), 1},
                {"qualifiedName", Operator.EQ, spChar15.replace("name", "qf"), 1},
                {"qualifiedName", Operator.EQ, spChar16.replace("name", "qf"), 1},
                {"qualifiedName", Operator.EQ, spChar17.replace("name", "qf"), 1},
                {"qualifiedName", Operator.EQ, spChar18.replace("name", "qf"), 1},
                {"qualifiedName", Operator.EQ, spChar19.replace("name", "qf"), 1},
        };
    }

    @DataProvider(name = "specialCharQuickSearch")
    private Object[][] specialCharQuickSearch() {
        return new Object[][] {
                {"default.test_dot_name", 3},
                {"default.test_dot_name*", 3},
                {"test_dot_name", 0},
                {"*test_dot_name", 2},
                {"*test_dot_name*", 4},
                {"default.test_dot_name\\*", 0},

                {"default.test_dot_qf", 3},
                {"default.test_dot_qf*", 3},
                {"test_dot_qf", 1},
                {"*test_dot_qf", 3},
                {"*test_dot_qf*", 4},
                {"default.test_dot_qf\\*", 2},

                {"default.test_dot_name_12.col1", 1},
                {"default.test_dot_name_12.col1*", 1},
                {"default.test_dot_name_12.col", 0},
                {"default.test_dot_name_12.col*", 1},
                {"default.test_dot_qf_12.col1", 1},
                {"default.test_dot_qf_12.col1*", 0},

                {"default.test_dot_name_12*", 1},
                {"default.test_dot_qf_12*", 1},

                //space gets tokenized, hence whenever user searches for 'STRING' type attribute, space needs to be escaped
                {"search_name_with nameblank@namecluster", 0},
                //{"search_name_with nameblank@name", 0}, commenting as there are some entities created from other class with same name
                {"search_name_with nameblank*", 0},
                {"search_name_with\\ nameblank@namecluster", 1},
                {"search_name_with\\ nameblank*", 1},

                //if we escape it will consider as single string, hence * will act as character not as wildcard
                {"search_qf_with\\ qfblank*", 0},
                {"search_qf_with\\ qfblank@qfcluster", 1},
                {"search_qf_with qfblank@qfcluster", 1},
                {"search_qf_with qfblank@q", 1},
                {"search_qf_with qfblank*", 1},
        };
    }

    private String gethiveTableSalesFactGuid() throws AtlasBaseException {
        if (salesFactGuid == null) {
            SearchParameters params = new SearchParameters();
            params.setTypeName(HIVE_TABLE_TYPE);
            SearchParameters.FilterCriteria filterCriteria = getSingleFilterCondition("name", Operator.EQ, "sales_fact");
            params.setEntityFilters(filterCriteria);
            AtlasSearchResult result = discoveryService.searchWithParameters(params);

            if (result != null && CollectionUtils.isNotEmpty(result.getEntities())) {
                salesFactGuid = result.getEntities().get(0).getGuid();
            }
        }
        return salesFactGuid;
    }

    private void assertSearchResult(AtlasSearchResult searchResult, int expected, String query) {
        assertNotNull(searchResult);
        if (expected == 0) {
            assertTrue(searchResult.getAttributes() == null || CollectionUtils.isEmpty(searchResult.getAttributes().getValues()));
            assertNull(searchResult.getEntities(), query);
        } else if (searchResult.getEntities() != null) {
            assertEquals(searchResult.getEntities().size(), expected, query);
        } else {
            assertNotNull(searchResult.getAttributes());
            assertNotNull(searchResult.getAttributes().getValues());
            assertEquals(searchResult.getAttributes().getValues().size(), expected, query);
        }
    }

    private void assertSearchResult(AtlasSearchResult searchResult, AtlasSearchResult.AttributeSearchResult expected) {
        assertNotNull(searchResult);
        AtlasSearchResult.AttributeSearchResult result = searchResult.getAttributes();
        assertNotNull(result);
        assertTrue(result.getName().containsAll(expected.getName()));
        int i = 0;
        for (List<Object> value : result.getValues()) {
            assertTrue(value.containsAll(expected.getValues().get(i)));
            i++;
        }
    }

    private void assertAggregationMetrics(AtlasQuickSearchResult searchResult) {
        Map<String, List<AtlasAggregationEntry>> agg = searchResult.getAggregationMetrics();
        assertTrue(CollectionUtils.isNotEmpty(agg.get("__typeName")));

        AtlasAggregationEntry entry = agg.get("__typeName").get(0);
        assertTrue(entry != null && entry.getCount() > 0);
    }

    private void createDimensionalTaggedEntity(String name) throws AtlasBaseException {
        EntityMutationResponse  resp         = createDummyEntity(name, HIVE_TABLE_TYPE);
        AtlasEntityHeader       entityHeader = resp.getCreatedEntities().get(0);
        String                  guid         = entityHeader.getGuid();
        HashMap<String, Object> attr         = new HashMap<>();
        attr.put("attr1", "value1");
        entityStore.addClassification(Arrays.asList(guid), new AtlasClassification(DIMENSIONAL_CLASSIFICATION, attr));
    }

    private void createJapaneseEntityWithDescription() throws AtlasBaseException {
        AtlasEntity entity = new AtlasEntity(HDFS_PATH);
        entity.setAttribute("name", "元帳");
        entity.setAttribute(AtlasClient.REFERENCEABLE_ATTRIBUTE_NAME, "レシート");
        entity.setAttribute("path", "レシート");

        entityStore.createOrUpdate(new AtlasEntityStream(new AtlasEntity.AtlasEntitiesWithExtInfo(entity)), false);

        AtlasEntity entity2 = new AtlasEntity(HDFS_PATH);
        entity2.setAttribute("name", "cjk");
        entity2.setAttribute(AtlasClient.REFERENCEABLE_ATTRIBUTE_NAME, "の台帳");
        entity2.setAttribute("path", "cjk");

        entityStore.createOrUpdate(new AtlasEntityStream(new AtlasEntity.AtlasEntitiesWithExtInfo(entity2)), false);
    }

    private void assertSearchProcessorWithoutMarker(SearchParameters params, int expected) throws AtlasBaseException {
        assertSearchProcessor(params, expected, false);
    }

    private void assertSearchProcessorWithMarker(SearchParameters params, int expected) throws AtlasBaseException {
        assertSearchProcessor(params, expected, true);
    }

    private void assertSearchProcessor(SearchParameters params, int expected, boolean checkMarker) throws AtlasBaseException {
        AtlasSearchResult       searchResult  = discoveryService.searchWithParameters(params);
        List<AtlasEntityHeader> entityHeaders = searchResult.getEntities();

        assertTrue(CollectionUtils.isNotEmpty(entityHeaders));
        assertEquals(entityHeaders.size(), expected);

        if (checkMarker) {
            assertTrue(StringUtils.isNotEmpty(searchResult.getNextMarker()));
        } else {
            assertTrue(StringUtils.isEmpty(searchResult.getNextMarker()));
        }
    }
}
