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

import org.apache.atlas.AtlasErrorCode;
import org.apache.atlas.RequestContext;
import org.apache.atlas.SortOrder;
import org.apache.atlas.TestModules;
import org.apache.atlas.bulkimport.BulkImportResponse;
import org.apache.atlas.discovery.AtlasDiscoveryService;
import org.apache.atlas.exception.AtlasBaseException;
import org.apache.atlas.model.discovery.SearchParameters;
import org.apache.atlas.model.glossary.AtlasGlossary;
import org.apache.atlas.model.glossary.AtlasGlossaryCategory;
import org.apache.atlas.model.glossary.AtlasGlossaryTerm;
import org.apache.atlas.model.glossary.enums.AtlasTermRelationshipStatus;
import org.apache.atlas.model.glossary.relations.AtlasGlossaryHeader;
import org.apache.atlas.model.glossary.relations.AtlasRelatedCategoryHeader;
import org.apache.atlas.model.glossary.relations.AtlasRelatedTermHeader;
import org.apache.atlas.model.glossary.relations.AtlasTermAssignmentHeader;
import org.apache.atlas.model.glossary.relations.AtlasTermCategorizationHeader;
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.AtlasRelatedObjectId;
import org.apache.atlas.model.instance.EntityMutationResponse;
import org.apache.atlas.model.typedef.AtlasClassificationDef;
import org.apache.atlas.model.typedef.AtlasTypesDef;
import org.apache.atlas.repository.ogm.DataAccess;
import org.apache.atlas.repository.ogm.glossary.AtlasGlossaryDTO;
import org.apache.atlas.repository.store.graph.AtlasEntityStore;
import org.apache.atlas.repository.store.graph.v2.AtlasEntityStream;
import org.apache.atlas.repository.store.graph.v2.AtlasGraphUtilsV2;
import org.apache.atlas.store.AtlasTypeDefStore;
import org.apache.atlas.type.AtlasType;
import org.apache.atlas.type.AtlasTypeRegistry;
import org.apache.atlas.util.FileUtils;
import org.apache.atlas.utils.AtlasJson;
import org.apache.atlas.utils.TestLoadModelUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.SkipException;
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.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;

@Guice(modules = TestModules.TestOnlyModule.class)
public class GlossaryServiceTest {
    public static final  String          CSV_FILES   = "/csvFiles/";
    public static final  String          EXCEL_FILES = "/excelFiles/";
    private static final Logger          LOG         = LoggerFactory.getLogger(GlossaryServiceTest.class);
    @Inject
    private              GlossaryService glossaryService;

    @Inject
    private AtlasTypeDefStore typeDefStore;

    @Inject
    private AtlasTypeRegistry typeRegistry;

    @Inject
    private AtlasEntityStore entityStore;

    @Inject
    private AtlasDiscoveryService discoveryService;

    @Mock
    private GlossaryService mockedGlossaryService; // Mocked GlossaryService

    @Mock
    private DataAccess mockedDataAccess; // Mocked DataAccess layer

    @Mock
    private AtlasEntityStore mockedEntityStore; // Mocked Entity Store

    @Mock
    private AtlasGlossaryDTO mockedGlossaryDTO; // Mocked DTO for entity conversion

    private AtlasGlossary         bankGlossary;
    private AtlasGlossary         creditUnionGlossary;
    private AtlasGlossary         debitUnionGlossary;
    private AtlasGlossaryTerm     checkingAccount;
    private AtlasGlossaryTerm     savingsAccount;
    private AtlasGlossaryTerm     fixedRateMortgage;
    private AtlasGlossaryTerm     adjustableRateMortgage;
    private AtlasGlossaryTerm     currentAccount;
    private AtlasGlossaryCategory customerCategory;
    private AtlasGlossaryCategory accountCategory;
    private AtlasGlossaryCategory mortgageCategory;
    private AtlasGlossaryCategory loanCategory;
    private AtlasRelatedObjectId  relatedObjectId;

    @DataProvider
    public static Object[][] getGlossaryTermsProvider() {
        return new Object[][] {
                // offset, limit, expected
                {0, -1, 8},
                {0, 2, 2},
                {2, 6, 6},
        };
    }

    @BeforeClass
    public void setupSampleGlossary() {
        try {
            TestLoadModelUtils.loadAllModels("0000-Area0", typeDefStore, typeRegistry);
        } catch (AtlasBaseException | IOException e) {
            throw new SkipException("SubjectArea model loading failed");
        }

        try {
            AtlasClassificationDef classificationDef = new AtlasClassificationDef("TestClassification", "Test only classification");
            AtlasTypesDef          typesDef          = new AtlasTypesDef();

            typesDef.setClassificationDefs(Collections.singletonList(classificationDef));

            typeDefStore.createTypesDef(typesDef);
        } catch (AtlasBaseException e) {
            throw new SkipException("Test classification creation failed");
        }

        // Glossary
        bankGlossary = new AtlasGlossary();

        bankGlossary.setQualifiedName("testBankingGlossary");
        bankGlossary.setName("Banking glossary");
        bankGlossary.setShortDescription("Short description");
        bankGlossary.setLongDescription("Long description");
        bankGlossary.setUsage("N/A");
        bankGlossary.setLanguage("en-US");

        creditUnionGlossary = new AtlasGlossary();

        creditUnionGlossary.setQualifiedName("testCreditUnionGlossary");
        creditUnionGlossary.setName("Credit union glossary");
        creditUnionGlossary.setShortDescription("Short description");
        creditUnionGlossary.setLongDescription("Long description");
        creditUnionGlossary.setUsage("N/A");
        creditUnionGlossary.setLanguage("en-US");

        debitUnionGlossary = new AtlasGlossary();

        debitUnionGlossary.setName("<Debit union glossary");
        debitUnionGlossary.setShortDescription("Short description");
        debitUnionGlossary.setLongDescription("Long description");
        debitUnionGlossary.setUsage("N/A");
        debitUnionGlossary.setLanguage("en-US");

        // Category
        accountCategory = new AtlasGlossaryCategory();

        accountCategory.setName("Account categorization");
        accountCategory.setShortDescription("Short description");
        accountCategory.setLongDescription("Long description");

        customerCategory = new AtlasGlossaryCategory();

        customerCategory.setQualifiedName("customer@testBankingGlossary");
        customerCategory.setName("Customer category");
        customerCategory.setShortDescription("Short description");
        customerCategory.setLongDescription("Long description");

        mortgageCategory = new AtlasGlossaryCategory();

        mortgageCategory.setName("Mortgage categorization");
        mortgageCategory.setShortDescription("Short description");
        mortgageCategory.setLongDescription("Long description");

        loanCategory = new AtlasGlossaryCategory();

        loanCategory.setName("Loan categorization>");
        loanCategory.setShortDescription("Short description");
        loanCategory.setLongDescription("Long description");

        // Terms
        checkingAccount = new AtlasGlossaryTerm();

        checkingAccount.setName("A checking account");
        checkingAccount.setShortDescription("Short description");
        checkingAccount.setLongDescription("Long description");
        checkingAccount.setAbbreviation("CHK");
        checkingAccount.setExamples(Arrays.asList("Personal", "Joint"));
        checkingAccount.setUsage("N/A");

        savingsAccount = new AtlasGlossaryTerm();

        savingsAccount.setQualifiedName("sav_acc@testBankingGlossary");
        savingsAccount.setName("A savings account");
        savingsAccount.setShortDescription("Short description");
        savingsAccount.setLongDescription("Long description");
        savingsAccount.setAbbreviation("SAV");
        savingsAccount.setExamples(Arrays.asList("Personal", "Joint"));
        savingsAccount.setUsage("N/A");

        fixedRateMortgage = new AtlasGlossaryTerm();

        fixedRateMortgage.setName("Conventional mortgage");
        fixedRateMortgage.setShortDescription("Short description");
        fixedRateMortgage.setLongDescription("Long description");
        fixedRateMortgage.setAbbreviation("FMTG");
        fixedRateMortgage.setExamples(Arrays.asList("15-yr", "30-yr"));
        fixedRateMortgage.setUsage("N/A");

        adjustableRateMortgage = new AtlasGlossaryTerm();

        adjustableRateMortgage.setQualifiedName("arm_mtg@testBankingGlossary");
        adjustableRateMortgage.setName("ARM loans");
        adjustableRateMortgage.setShortDescription("Short description");
        adjustableRateMortgage.setLongDescription("Long description");
        adjustableRateMortgage.setAbbreviation("ARMTG");
        adjustableRateMortgage.setExamples(Arrays.asList("5/1", "7/1", "10/1"));
        adjustableRateMortgage.setUsage("N/A");

        currentAccount = new AtlasGlossaryTerm();

        currentAccount.setName("current@account");
        currentAccount.setShortDescription("Short description");
        currentAccount.setLongDescription("Long description");
        currentAccount.setAbbreviation("CURR");
        currentAccount.setExamples(Arrays.asList("Personal", "Joint"));
        currentAccount.setUsage("N/A");

        // Create a real instance of GlossaryService with mocked dependencies
        mockedDataAccess      = mock(DataAccess.class);
        mockedGlossaryDTO     = mock(AtlasGlossaryDTO.class);
        mockedGlossaryService = new GlossaryService(mockedDataAccess, null, null, null, mockedGlossaryDTO);
    }

    @Test(groups = "Glossary.CREATE")
    public void testCreateGlossary() {
        try {
            AtlasGlossary created = glossaryService.createGlossary(bankGlossary);

            bankGlossary.setGuid(created.getGuid());

            created = glossaryService.createGlossary(creditUnionGlossary);

            creditUnionGlossary.setGuid(created.getGuid());
        } catch (AtlasBaseException e) {
            fail("Glossary creation should've succeeded", e);
        }

        // Duplicate create calls should fail with 409 Conflict
        try {
            glossaryService.createGlossary(bankGlossary);
            fail("Glossary duplicate creation should've failed");
        } catch (AtlasBaseException e) {
            assertEquals(e.getAtlasErrorCode(), AtlasErrorCode.GLOSSARY_ALREADY_EXISTS);
        }
        try {
            glossaryService.createGlossary(creditUnionGlossary);
            fail("Glossary duplicate creation should've failed");
        } catch (AtlasBaseException e) {
            assertEquals(e.getAtlasErrorCode(), AtlasErrorCode.GLOSSARY_ALREADY_EXISTS);
        }

        //Validate glossary creation
        try {
            glossaryService.createGlossary(debitUnionGlossary);
            fail("Invalid glossary creation should've failed");
        } catch (AtlasBaseException e) {
            assertEquals(e.getAtlasErrorCode(), AtlasErrorCode.INVALID_DISPLAY_NAME);
        }

        // Retrieve the glossary and see ensure no terms or categories are linked

        try {
            List<AtlasRelatedCategoryHeader> glossaryCategories = glossaryService.getGlossaryCategoriesHeaders(bankGlossary.getGuid(), 0, 10, SortOrder.ASCENDING);

            assertNotNull(glossaryCategories);
            assertEquals(glossaryCategories.size(), 0);

            glossaryCategories = glossaryService.getGlossaryCategoriesHeaders(creditUnionGlossary.getGuid(), 0, 10, SortOrder.ASCENDING);

            assertNotNull(glossaryCategories);
            assertEquals(glossaryCategories.size(), 0);
        } catch (AtlasBaseException e) {
            fail("Get glossary categories calls should've succeeded", e);
        }

        try {
            List<AtlasRelatedTermHeader> glossaryCategories = glossaryService.getGlossaryTermsHeaders(bankGlossary.getGuid(), 0, 10, SortOrder.ASCENDING);

            assertNotNull(glossaryCategories);
            assertEquals(glossaryCategories.size(), 0);

            glossaryCategories = glossaryService.getGlossaryTermsHeaders(creditUnionGlossary.getGuid(), 0, 10, SortOrder.ASCENDING);

            assertNotNull(glossaryCategories);
            assertEquals(glossaryCategories.size(), 0);
        } catch (AtlasBaseException e) {
            fail("Get glossary categories calls should've succeeded", e);
        }

        // Glossary anchor
        AtlasGlossaryHeader glossaryId = new AtlasGlossaryHeader();
        glossaryId.setGlossaryGuid(bankGlossary.getGuid());

        // Create terms
        checkingAccount.setAnchor(glossaryId);
        savingsAccount.setAnchor(glossaryId);
        fixedRateMortgage.setAnchor(glossaryId);

        adjustableRateMortgage.setAnchor(glossaryId);
        currentAccount.setAnchor(glossaryId);

        // Create glossary categories
        accountCategory.setAnchor(glossaryId);
        customerCategory.setAnchor(glossaryId);
        mortgageCategory.setAnchor(glossaryId);
        loanCategory.setAnchor(glossaryId);
    }

    @Test(groups = "Glossary.CREATE", dependsOnMethods = "testCategoryCreation")
    public void testTermCreationWithoutAnyRelations() {
        try {
            checkingAccount = glossaryService.createTerm(checkingAccount);

            assertNotNull(checkingAccount);
            assertNotNull(checkingAccount.getGuid());
        } catch (AtlasBaseException e) {
            fail("Term creation should've succeeded", e);
        }

        //validating term creation
        try {
            glossaryService.createTerm(currentAccount);

            fail("Invalid term creation should've failed");
        } catch (AtlasBaseException e) {
            assertEquals(e.getAtlasErrorCode(), AtlasErrorCode.INVALID_DISPLAY_NAME);
        }
    }

    @Test(groups = "Glossary.CREATE", dependsOnMethods = "testTermCreationWithoutAnyRelations")
    public void testTermCreateWithRelation() {
        try {
            AtlasRelatedTermHeader relatedTermHeader = new AtlasRelatedTermHeader();

            relatedTermHeader.setTermGuid(checkingAccount.getGuid());
            relatedTermHeader.setDescription("test description");
            relatedTermHeader.setExpression("test expression");
            relatedTermHeader.setSource("UT");
            relatedTermHeader.setSteward("UT");
            relatedTermHeader.setStatus(AtlasTermRelationshipStatus.ACTIVE);

            savingsAccount.setSeeAlso(Collections.singleton(relatedTermHeader));

            savingsAccount = glossaryService.createTerm(savingsAccount);

            assertNotNull(savingsAccount);
            assertNotNull(savingsAccount.getGuid());
        } catch (AtlasBaseException e) {
            fail("Term creation with relation should've succeeded", e);
        }
    }

    @Test(groups = "Glossary.UPDATE", dependsOnGroups = "Glossary.CREATE")
    public void testTermAfterEntityIsDeleted() {
        SearchParameters params = new SearchParameters();

        params.setTypeName("Asset");

        AtlasTermAssignmentHeader loan = new AtlasTermAssignmentHeader();

        loan.setTermGuid(fixedRateMortgage.getGuid());
        loan.setRelationGuid(fixedRateMortgage.getAnchor().getRelationGuid());
        loan.setQualifiedName(fixedRateMortgage.getQualifiedName());

        AtlasEntity assetEntity = new AtlasEntity("Asset");

        assetEntity.setAttribute("qualifiedName", "testAsset");
        assetEntity.setAttribute("name", "testAsset");
        assetEntity.addMeaning(loan);

        try {
            EntityMutationResponse response           = entityStore.createOrUpdate(new AtlasEntityStream(assetEntity), false);
            AtlasEntityHeader      firstEntityCreated = response.getFirstEntityCreated();

            relatedObjectId = new AtlasRelatedObjectId();

            relatedObjectId.setGuid(firstEntityCreated.getGuid());
            relatedObjectId.setTypeName(firstEntityCreated.getTypeName());

            assertNotNull(relatedObjectId);
        } catch (AtlasBaseException e) {
            fail("Entity creation should've succeeded", e);
        }

        try {
            glossaryService.assignTermToEntities(fixedRateMortgage.getGuid(), Collections.singletonList(relatedObjectId));
        } catch (AtlasBaseException e) {
            fail("Term assignment to asset should've succeeded", e);
        }

        try {
            entityStore.deleteById(relatedObjectId.getGuid());

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

            assertNotNull(AtlasType.toJson(entityHeaders.get(0).getMeaningNames()));
        } catch (AtlasBaseException e) {
            fail("Entity delete should've succeeded");
        }
    }

    @Test(groups = "Glossary.CREATE", dependsOnMethods = "testCategoryCreation")
    public void testTermCreationWithCategory() {
        try {
            AtlasTermCategorizationHeader termCategorizationHeader = new AtlasTermCategorizationHeader();

            termCategorizationHeader.setCategoryGuid(mortgageCategory.getGuid());
            termCategorizationHeader.setDescription("Test description");
            termCategorizationHeader.setStatus(AtlasTermRelationshipStatus.DRAFT);

            fixedRateMortgage.setCategories(Collections.singleton(termCategorizationHeader));
            adjustableRateMortgage.setCategories(Collections.singleton(termCategorizationHeader));

            List<AtlasGlossaryTerm> terms = glossaryService.createTerms(Arrays.asList(fixedRateMortgage, adjustableRateMortgage));

            fixedRateMortgage.setGuid(terms.get(0).getGuid());
            adjustableRateMortgage.setGuid(terms.get(1).getGuid());
        } catch (AtlasBaseException e) {
            fail("Term creation should've succeeded", e);
        }
    }

    @Test(groups = "Glossary.CREATE", dependsOnMethods = "testCreateGlossary")
    public void testCategoryCreation() {
        try {
            customerCategory = glossaryService.createCategory(customerCategory);

            AtlasRelatedCategoryHeader parentHeader = new AtlasRelatedCategoryHeader();

            parentHeader.setCategoryGuid(customerCategory.getGuid());

            // Test parent relation
            accountCategory.setParentCategory(parentHeader);
            List<AtlasGlossaryCategory> categories = glossaryService.createCategories(Arrays.asList(accountCategory, mortgageCategory));

            accountCategory.setGuid(categories.get(0).getGuid());

            assertNotNull(accountCategory.getParentCategory());
            assertEquals(accountCategory.getParentCategory().getCategoryGuid(), customerCategory.getGuid());
            assertTrue(accountCategory.getQualifiedName().endsWith(customerCategory.getQualifiedName()));

            mortgageCategory.setGuid(categories.get(1).getGuid());

            assertNull(mortgageCategory.getParentCategory());
        } catch (AtlasBaseException e) {
            fail("Category creation should've succeeded", e);
        }

        // Validate category creation
        try {
            glossaryService.createCategory(loanCategory);

            fail("Invalid category creation should've failed");
        } catch (AtlasBaseException e) {
            assertEquals(e.getAtlasErrorCode(), AtlasErrorCode.INVALID_DISPLAY_NAME);
        }
    }

    @DataProvider
    public Object[][] getAllGlossaryDataProvider() {
        return new Object[][] {
                // limit, offset, sortOrder, expected
                {1, 0, SortOrder.ASCENDING, 1},
                {5, 0, SortOrder.ASCENDING, 2},
                {10, 0, SortOrder.ASCENDING, 2},
                {1, 1, SortOrder.ASCENDING, 1},
                {5, 1, SortOrder.ASCENDING, 1},
                {10, 1, SortOrder.ASCENDING, 1},
                {1, 2, SortOrder.ASCENDING, 0},
                {5, 2, SortOrder.ASCENDING, 0},
                {10, 2, SortOrder.ASCENDING, 0},
        };
    }

    @Test(dataProvider = "getAllGlossaryDataProvider", groups = "Glossary.GET", dependsOnGroups = "Glossary.CREATE")
    public void testGetAllGlossaries(int limit, int offset, SortOrder sortOrder, int expected) {
        try {
            List<AtlasGlossary> glossaries = glossaryService.getGlossaries(limit, offset, sortOrder);

            assertEquals(glossaries.size(), expected);
        } catch (AtlasBaseException e) {
            fail("Get glossaries should've succeeded", e);
        }
    }

    @Test(groups = "Glossary.UPDATE", dependsOnGroups = "Glossary.CREATE")
    public void testUpdateGlossary() {
        try {
            bankGlossary = glossaryService.getGlossary(bankGlossary.getGuid());

            bankGlossary.setShortDescription("Updated short description");
            bankGlossary.setLongDescription("Updated long description");

            AtlasGlossary updatedGlossary = glossaryService.updateGlossary(bankGlossary);

            assertNotNull(updatedGlossary);
            assertEquals(updatedGlossary.getGuid(), bankGlossary.getGuid());
//            assertEquals(updatedGlossary.getCategories(), bankGlossary.getCategories());
//            assertEquals(updatedGlossary.getTerms(), bankGlossary.getTerms());
//            assertEquals(updatedGlossary, bankGlossary);

            // There's some weirdness around the equality check of HashSet, hence the conversion to ArrayList
            ArrayList<AtlasRelatedCategoryHeader> a = new ArrayList<>(updatedGlossary.getCategories());
            ArrayList<AtlasRelatedCategoryHeader> b = new ArrayList<>(bankGlossary.getCategories());

            assertEquals(a, b);
        } catch (AtlasBaseException e) {
            fail("Glossary fetch/update should've succeeded", e);
        }

        //Validate glossary update
        try {
            debitUnionGlossary.setName("Test Glossary Update");

            debitUnionGlossary = glossaryService.createGlossary(debitUnionGlossary);

            assertNotNull(debitUnionGlossary);

            debitUnionGlossary.setName("<test glossary create>");

            glossaryService.updateGlossary(debitUnionGlossary);

            fail("Invalid glossary update should've failed");
        } catch (AtlasBaseException e) {
            assertEquals(e.getAtlasErrorCode(), AtlasErrorCode.INVALID_DISPLAY_NAME);
        }
    }

    @Test(dependsOnGroups = "Glossary.MIGRATE")
    public void testInvalidFetches() {
        try {
            glossaryService.getGlossary(mortgageCategory.getGuid());
        } catch (AtlasBaseException e) {
            assertEquals(e.getAtlasErrorCode(), AtlasErrorCode.UNEXPECTED_TYPE);
        }

        try {
            glossaryService.getTerm(bankGlossary.getGuid());
        } catch (AtlasBaseException e) {
            assertEquals(e.getAtlasErrorCode(), AtlasErrorCode.UNEXPECTED_TYPE);
        }

        try {
            glossaryService.getCategory(savingsAccount.getGuid());
        } catch (AtlasBaseException e) {
            assertEquals(e.getAtlasErrorCode(), AtlasErrorCode.UNEXPECTED_TYPE);
        }
    }

    @Test(dependsOnMethods = "testInvalidFetches") // Should be the last test
    public void testDeleteGlossary() {
        try {
            glossaryService.deleteGlossary(bankGlossary.getGuid());

            // Fetch deleted glossary
            try {
                glossaryService.getGlossary(bankGlossary.getGuid());
            } catch (AtlasBaseException e) {
                assertEquals(e.getAtlasErrorCode(), AtlasErrorCode.INSTANCE_GUID_NOT_FOUND);
            }

            // Fetch delete terms
            try {
                glossaryService.getTerm(fixedRateMortgage.getGuid());
            } catch (AtlasBaseException e) {
                assertEquals(e.getAtlasErrorCode(), AtlasErrorCode.INSTANCE_GUID_NOT_FOUND);
            }

            try {
                glossaryService.getTerm(adjustableRateMortgage.getGuid());
            } catch (AtlasBaseException e) {
                assertEquals(e.getAtlasErrorCode(), AtlasErrorCode.INSTANCE_GUID_NOT_FOUND);
            }

            try {
                glossaryService.getTerm(savingsAccount.getGuid());
            } catch (AtlasBaseException e) {
                assertEquals(e.getAtlasErrorCode(), AtlasErrorCode.INSTANCE_GUID_NOT_FOUND);
            }

            try {
                glossaryService.getTerm(checkingAccount.getGuid());
            } catch (AtlasBaseException e) {
                assertEquals(e.getAtlasErrorCode(), AtlasErrorCode.INSTANCE_GUID_NOT_FOUND);
            }

            // Fetch deleted categories
            try {
                glossaryService.getCategory(customerCategory.getGuid());
            } catch (AtlasBaseException e) {
                assertEquals(e.getAtlasErrorCode(), AtlasErrorCode.INSTANCE_GUID_NOT_FOUND);
            }

            try {
                glossaryService.getCategory(accountCategory.getGuid());
            } catch (AtlasBaseException e) {
                assertEquals(e.getAtlasErrorCode(), AtlasErrorCode.INSTANCE_GUID_NOT_FOUND);
            }

            try {
                glossaryService.getCategory(mortgageCategory.getGuid());
            } catch (AtlasBaseException e) {
                assertEquals(e.getAtlasErrorCode(), AtlasErrorCode.INSTANCE_GUID_NOT_FOUND);
            }
        } catch (AtlasBaseException e) {
            fail("Glossary delete should've succeeded", e);
        }
    }

    @Test(groups = "Glossary.UPDATE", dependsOnGroups = "Glossary.CREATE")
    public void testUpdateGlossaryTerm() {
        List<AtlasGlossaryTerm> glossaryTerms  = new ArrayList<>();
        AtlasClassification     classification = new AtlasClassification("TestClassification");

        for (AtlasGlossaryTerm term : Arrays.asList(checkingAccount, savingsAccount, fixedRateMortgage, adjustableRateMortgage)) {
            try {
                glossaryTerms.add(glossaryService.getTerm(term.getGuid()));
            } catch (AtlasBaseException e) {
                fail("Fetch of GlossaryTerm should've succeeded", e);
            }
        }

        for (AtlasGlossaryTerm t : glossaryTerms) {
            try {
                t.setShortDescription("Updated short description");
                t.setLongDescription("Updated long description");

                entityStore.addClassifications(t.getGuid(), Collections.singletonList(classification));

                AtlasGlossaryTerm updatedTerm = glossaryService.updateTerm(t);

                assertNotNull(updatedTerm);
                assertEquals(updatedTerm.getGuid(), t.getGuid());
                assertNotNull(updatedTerm.getClassifications());
                assertEquals(updatedTerm.getClassifications().size(), 1);
            } catch (AtlasBaseException e) {
                fail("Glossary term fetch/update should've succeeded", e);
            }
        }

        //Validate term update
        try {
            currentAccount.setName("Test term update");

            currentAccount = glossaryService.createTerm(currentAccount);

            currentAccount.setName("<Test term update>");

            glossaryService.updateTerm(currentAccount);
            fail("Invalid term update should've failed");
        } catch (AtlasBaseException e) {
            assertEquals(e.getAtlasErrorCode(), AtlasErrorCode.INVALID_DISPLAY_NAME);
        }
    }

    @Test(groups = "Glossary.UPDATE", dependsOnGroups = "Glossary.CREATE")
    public void testUpdateGlossaryCategory() {
        List<AtlasGlossaryCategory> glossaryCategories = new ArrayList<>();

        for (AtlasGlossaryCategory glossaryCategory : Arrays.asList(customerCategory, accountCategory, mortgageCategory)) {
            try {
                glossaryCategories.add(glossaryService.getCategory(glossaryCategory.getGuid()));
            } catch (AtlasBaseException e) {
                fail("Category fetch should've succeeded", e);
            }
        }

        for (AtlasGlossaryCategory c : glossaryCategories) {
            try {
                c.setShortDescription("Updated short description");
                c.setLongDescription("Updated long description");

                AtlasGlossaryCategory updatedCategory = glossaryService.updateCategory(c);

                assertNotNull(updatedCategory);
                assertEquals(updatedCategory.getGuid(), c.getGuid());
            } catch (AtlasBaseException e) {
                fail("Glossary category fetching should've succeeded", e);
            }
        }

        // Unlink children
        try {
            customerCategory = glossaryService.getCategory(customerCategory.getGuid());

            customerCategory.setChildrenCategories(null);

            customerCategory = glossaryService.updateCategory(customerCategory);

            assertNotNull(customerCategory);
            assertNull(customerCategory.getChildrenCategories());

            accountCategory = glossaryService.getCategory(accountCategory.getGuid());

            assertNull(accountCategory.getParentCategory());
            assertTrue(accountCategory.getQualifiedName().endsWith(bankGlossary.getQualifiedName()));

            mortgageCategory = glossaryService.getCategory(mortgageCategory.getGuid());

            assertNull(mortgageCategory.getParentCategory());
            assertTrue(mortgageCategory.getQualifiedName().endsWith(bankGlossary.getQualifiedName()));
        } catch (AtlasBaseException e) {
            fail("Customer category fetch should've succeeded");
        }
        //Validate category update
        try {
            loanCategory.setName("Test category update");

            loanCategory = glossaryService.createCategory(loanCategory);

            loanCategory.setName("<Test category update>");

            glossaryService.updateCategory(loanCategory);

            fail("Invalid category update should've failed");
        } catch (AtlasBaseException e) {
            assertEquals(e.getAtlasErrorCode(), AtlasErrorCode.INVALID_DISPLAY_NAME);
        }
    }

    @Test(groups = "Glossary.MIGRATE", dependsOnGroups = "Glossary.GET.postUpdate")
    public void testTermMigration() {
        assertNotNull(creditUnionGlossary);

        AtlasGlossaryHeader newGlossaryHeader = new AtlasGlossaryHeader();

        newGlossaryHeader.setGlossaryGuid(creditUnionGlossary.getGuid());

        try {
            checkingAccount = glossaryService.getTerm(checkingAccount.getGuid());
            savingsAccount  = glossaryService.getTerm(savingsAccount.getGuid());

            checkingAccount.setAnchor(newGlossaryHeader);
            checkingAccount.setSeeAlso(null);
            savingsAccount.setAnchor(newGlossaryHeader);
            savingsAccount.setSeeAlso(null);
        } catch (AtlasBaseException e) {
            fail("Term fetch for migration should've succeeded", e);
        }

        try {
            checkingAccount = glossaryService.updateTerm(checkingAccount);

            assertNotNull(checkingAccount);
            assertTrue(CollectionUtils.isEmpty(checkingAccount.getSeeAlso()));

            savingsAccount = glossaryService.updateTerm(savingsAccount);

            assertNotNull(savingsAccount);
            assertTrue(CollectionUtils.isEmpty(savingsAccount.getSeeAlso()));
        } catch (AtlasBaseException e) {
            fail("Term anchor change should've succeeded", e);
        }

        try {
            List<AtlasRelatedTermHeader> terms = glossaryService.getGlossaryTermsHeaders(creditUnionGlossary.getGuid(), 0, 5, SortOrder.ASCENDING);

            assertNotNull(terms);
            assertEquals(terms.size(), 2);
        } catch (AtlasBaseException e) {
            fail("Term fetch for glossary should've succeeded", e);
        }
    }

    @Test(groups = "Glossary.MIGRATE", dependsOnGroups = "Glossary.GET.postUpdate")
    public void testCategoryMigration() {
        assertNotNull(creditUnionGlossary);

        AtlasGlossaryHeader newGlossaryHeader = new AtlasGlossaryHeader();
        newGlossaryHeader.setGlossaryGuid(creditUnionGlossary.getGuid());

        try {
            customerCategory = glossaryService.getCategory(customerCategory.getGuid());
            mortgageCategory = glossaryService.getCategory(mortgageCategory.getGuid());
            accountCategory  = glossaryService.getCategory(accountCategory.getGuid());
        } catch (AtlasBaseException e) {
            fail("Category fetch for migration should've succeeded");
        }

        customerCategory.setAnchor(newGlossaryHeader);
        mortgageCategory.setAnchor(newGlossaryHeader);
        accountCategory.setAnchor(newGlossaryHeader);

        try {
            customerCategory = glossaryService.updateCategory(customerCategory);
            mortgageCategory = glossaryService.updateCategory(mortgageCategory);
            accountCategory  = glossaryService.updateCategory(accountCategory);

            assertTrue(customerCategory.getQualifiedName().endsWith(creditUnionGlossary.getQualifiedName()));
            assertEquals(customerCategory.getAnchor().getGlossaryGuid(), newGlossaryHeader.getGlossaryGuid());
            assertTrue(accountCategory.getQualifiedName().endsWith(creditUnionGlossary.getQualifiedName()));
            assertEquals(accountCategory.getAnchor().getGlossaryGuid(), newGlossaryHeader.getGlossaryGuid());
            assertTrue(mortgageCategory.getQualifiedName().endsWith(creditUnionGlossary.getQualifiedName()));
            assertEquals(mortgageCategory.getAnchor().getGlossaryGuid(), newGlossaryHeader.getGlossaryGuid());
        } catch (AtlasBaseException e) {
            fail("Category anchor change should've succeeded");
        }

        try {
            List<AtlasRelatedCategoryHeader> categories = glossaryService.getGlossaryCategoriesHeaders(creditUnionGlossary.getGuid(), 0, 5, SortOrder.ASCENDING);

            assertNotNull(categories);
            assertEquals(categories.size(), 3);
        } catch (AtlasBaseException e) {
            fail("Category migration should've succeeded", e);
        }

        // Move the entire hierarchy back to the original glossary
        AtlasRelatedCategoryHeader child1 = new AtlasRelatedCategoryHeader();

        child1.setCategoryGuid(accountCategory.getGuid());

        AtlasRelatedCategoryHeader child2 = new AtlasRelatedCategoryHeader();

        child2.setCategoryGuid(mortgageCategory.getGuid());

        customerCategory.addChild(child1);
        customerCategory.addChild(child2);

        try {
            customerCategory = glossaryService.updateCategory(customerCategory);

            assertTrue(CollectionUtils.isNotEmpty(customerCategory.getChildrenCategories()));
        } catch (AtlasBaseException e) {
            fail("Children addition to Category should've succeeded");
        }

        customerCategory.setAnchor(newGlossaryHeader);

        newGlossaryHeader.setGlossaryGuid(bankGlossary.getGuid());

        try {
            customerCategory = glossaryService.getCategory(customerCategory.getGuid());

            assertTrue(CollectionUtils.isNotEmpty(customerCategory.getChildrenCategories()));
        } catch (AtlasBaseException e) {
            fail("Category fetch should've succeeded");
        }

        try {
            accountCategory = glossaryService.getCategory(accountCategory.getGuid());

            assertEquals(accountCategory.getAnchor().getGlossaryGuid(), customerCategory.getAnchor().getGlossaryGuid());
        } catch (AtlasBaseException e) {
            fail("Category fetch should've succeeded");
        }

        try {
            mortgageCategory = glossaryService.getCategory(mortgageCategory.getGuid());

            assertEquals(mortgageCategory.getAnchor().getGlossaryGuid(), customerCategory.getAnchor().getGlossaryGuid());
        } catch (AtlasBaseException e) {
            fail("Category fetch should've succeeded");
        }
    }

    @Test(groups = "Glossary.UPDATE", dependsOnGroups = "Glossary.CREATE")
    public void testAddTermsToCategory() {
        assertNotNull(accountCategory);

        try {
            accountCategory = glossaryService.getCategory(accountCategory.getGuid());

            assertTrue(CollectionUtils.isEmpty(accountCategory.getTerms()));
        } catch (AtlasBaseException e) {
            fail("Fetch of accountCategory should've succeeded", e);
        }

        for (AtlasGlossaryTerm term : Arrays.asList(checkingAccount, savingsAccount)) {
            try {
                AtlasGlossaryTerm      termEntry     = glossaryService.getTerm(term.getGuid());
                AtlasRelatedTermHeader relatedTermId = new AtlasRelatedTermHeader();

                relatedTermId.setTermGuid(termEntry.getGuid());
                relatedTermId.setStatus(AtlasTermRelationshipStatus.ACTIVE);
                relatedTermId.setSteward("UT");
                relatedTermId.setSource("UT");
                relatedTermId.setExpression("N/A");
                relatedTermId.setDescription("Categorization under account category");

                accountCategory.addTerm(relatedTermId);
            } catch (AtlasBaseException e) {
                fail("Term fetching should've succeeded", e);
            }
        }

        try {
            AtlasGlossaryCategory updated = glossaryService.updateCategory(accountCategory);

            assertNotNull(updated.getTerms());
            assertEquals(updated.getTerms().size(), 2);

            accountCategory = updated;
        } catch (AtlasBaseException e) {
            fail("Glossary category update should've succeeded", e);
        }

        assertNotNull(accountCategory);
        try {
            accountCategory = glossaryService.getCategory(accountCategory.getGuid());
        } catch (AtlasBaseException e) {
            fail("Fetch of accountCategory should've succeeded", e);
        }

        for (AtlasGlossaryTerm term : Arrays.asList(fixedRateMortgage, adjustableRateMortgage)) {
            try {
                AtlasGlossaryTerm      termEntry     = glossaryService.getTerm(term.getGuid());
                AtlasRelatedTermHeader relatedTermId = new AtlasRelatedTermHeader();

                relatedTermId.setTermGuid(termEntry.getGuid());
                relatedTermId.setStatus(AtlasTermRelationshipStatus.ACTIVE);
                relatedTermId.setSteward("UT");
                relatedTermId.setSource("UT");
                relatedTermId.setExpression("N/A");
                relatedTermId.setDescription("Categorization under mortgage category");

                mortgageCategory.addTerm(relatedTermId);
            } catch (AtlasBaseException e) {
                fail("Term fetching should've succeeded", e);
            }
        }

        try {
            AtlasGlossaryCategory updated = glossaryService.updateCategory(mortgageCategory);

            assertNotNull(updated.getTerms());
            assertEquals(updated.getTerms().size(), 2);

            mortgageCategory = updated;
        } catch (AtlasBaseException e) {
            fail("Glossary category update should've succeeded", e);
        }
    }

    @Test(groups = "Glossary.UPDATE", dependsOnGroups = "Glossary.CREATE")
    public void testAddGlossaryCategoryChildren() {
        assertNotNull(customerCategory);
        try {
            customerCategory = glossaryService.getCategory(customerCategory.getGuid());

            assertNull(customerCategory.getParentCategory());
        } catch (AtlasBaseException e) {
            fail("Fetch of accountCategory should've succeeded", e);
        }

        AtlasRelatedCategoryHeader id = new AtlasRelatedCategoryHeader();

        id.setCategoryGuid(mortgageCategory.getGuid());
        id.setDescription("Sub-category of customer");

        customerCategory.addChild(id);

        try {
            AtlasGlossaryCategory updateGlossaryCategory = glossaryService.updateCategory(customerCategory);

            assertNull(updateGlossaryCategory.getParentCategory());
            assertNotNull(updateGlossaryCategory.getChildrenCategories());

            if (LOG.isDebugEnabled()) {
                LOG.debug(AtlasJson.toJson(updateGlossaryCategory));
            }
        } catch (AtlasBaseException e) {
            fail("Sub category addition should've succeeded", e);
        }

        for (AtlasGlossaryCategory childCategory : Arrays.asList(accountCategory, mortgageCategory)) {
            try {
                AtlasGlossaryCategory child = glossaryService.getCategory(childCategory.getGuid());

                assertNotNull(child);
                assertNotNull(child.getParentCategory());
            } catch (AtlasBaseException e) {
                fail("Category fetch should've been a success", e);
            }
        }
    }

    @Test(groups = "Glossary.UPDATE", dependsOnGroups = "Glossary.CREATE")
    public void testTermAssignmentAndDissociation() {
        AtlasEntity assetEntity = new AtlasEntity("Asset");

        assetEntity.setAttribute("qualifiedName", "testAsset");
        assetEntity.setAttribute("name", "testAsset");

        long originalUpdateTime         = 0L;
        long updatedTimeAfterAssignment = 0L;
        try {
            EntityMutationResponse response           = entityStore.createOrUpdate(new AtlasEntityStream(assetEntity), false);
            AtlasEntityHeader      firstEntityCreated = response.getFirstEntityCreated();

            relatedObjectId = new AtlasRelatedObjectId();

            relatedObjectId.setGuid(firstEntityCreated.getGuid());
            relatedObjectId.setTypeName(firstEntityCreated.getTypeName());

            assertNotNull(relatedObjectId);
            AtlasEntity.AtlasEntityWithExtInfo entityInfo    = entityStore.getById(relatedObjectId.getGuid());
            AtlasEntity                        createdEntity = entityInfo.getEntity();
            assertNotNull(createdEntity);
            originalUpdateTime = createdEntity.getUpdateTime().getTime();
        } catch (AtlasBaseException e) {
            fail("Entity creation should've succeeded", e);
        }

        long mockRequestTimeAssign = System.currentTimeMillis() + 5000;  // simulate later timestamp
        try {
            try (MockedStatic<RequestContext> mockedRequestContext = Mockito.mockStatic(RequestContext.class)) {
                RequestContext mockContext = mock(RequestContext.class);
                when(mockContext.getRequestTime()).thenReturn(mockRequestTimeAssign);
                mockedRequestContext.when(RequestContext::get).thenReturn(mockContext);

                glossaryService.assignTermToEntities(fixedRateMortgage.getGuid(), Collections.singletonList(relatedObjectId));

                //verify updateTime after assignment
                AtlasEntity.AtlasEntityWithExtInfo updatedInfo                  = entityStore.getById(relatedObjectId.getGuid());
                AtlasEntity                        updatedEntityAfterAssignment = updatedInfo.getEntity();
                updatedTimeAfterAssignment = updatedEntityAfterAssignment.getUpdateTime().getTime();

                assertEquals(updatedTimeAfterAssignment, mockRequestTimeAssign);
                assertTrue(updatedTimeAfterAssignment > originalUpdateTime, "updateTime should have increased after term assignment");
            }
        } catch (AtlasBaseException e) {
            fail("Term assignment to asset should've succeeded", e);
        }

        try {
            List<AtlasRelatedObjectId> assignedEntities = glossaryService.getAssignedEntities(fixedRateMortgage.getGuid(), 0, 1, SortOrder.ASCENDING);

            assertNotNull(assignedEntities);
            assertEquals(assignedEntities.size(), 1);

            String relationshipGuid = assignedEntities.get(0).getRelationshipGuid();

            assertNotNull(relationshipGuid);

            relatedObjectId.setRelationshipGuid(relationshipGuid);
        } catch (AtlasBaseException e) {
            fail("Term fetch should've succeeded", e);
        }

        // Dissociate term from entities
        try {
            long mockRequestTimeRemove = mockRequestTimeAssign + 5000;
            try (MockedStatic<RequestContext> mockedRequestContext = Mockito.mockStatic(RequestContext.class)) {
                RequestContext mockContext = mock(RequestContext.class);
                when(mockContext.getRequestTime()).thenReturn(mockRequestTimeRemove);
                mockedRequestContext.when(RequestContext::get).thenReturn(mockContext);

                glossaryService.removeTermFromEntities(fixedRateMortgage.getGuid(), Collections.singletonList(relatedObjectId));

                //verify updateTime after dissociation
                AtlasEntity.AtlasEntityWithExtInfo updatedInfo                    = entityStore.getById(relatedObjectId.getGuid());
                AtlasEntity                        updatedEntityAfterDissociation = updatedInfo.getEntity();
                long                               updatedTimeAfterDissociation   = updatedEntityAfterDissociation.getUpdateTime().getTime();

                assertEquals(updatedTimeAfterDissociation, mockRequestTimeRemove);
                assertTrue(updatedTimeAfterDissociation > updatedTimeAfterAssignment, "updateTime should have increased after term dissociation");
            }
            AtlasGlossaryTerm term = glossaryService.getTerm(fixedRateMortgage.getGuid());

            assertNotNull(term);
            assertNull(term.getAssignedEntities());
        } catch (AtlasBaseException e) {
            fail("Term update should've succeeded", e);
        }

        try {
            entityStore.deleteById(relatedObjectId.getGuid());
        } catch (AtlasBaseException e) {
            fail("Entity delete should've succeeded");
        }
    }

    @Test(groups = "Glossary.UPDATE", dependsOnGroups = "Glossary.CREATE")
    public void testTermRelation() {
        AtlasRelatedTermHeader relatedTerm = new AtlasRelatedTermHeader();

        relatedTerm.setTermGuid(savingsAccount.getGuid());
        relatedTerm.setStatus(AtlasTermRelationshipStatus.DRAFT);
        relatedTerm.setSteward("UT");
        relatedTerm.setSource("UT");
        relatedTerm.setExpression("N/A");
        relatedTerm.setDescription("Related term");

        assertNotNull(checkingAccount);

        try {
            checkingAccount = glossaryService.getTerm(checkingAccount.getGuid());
        } catch (AtlasBaseException e) {
            fail("Glossary term fetch should've been a success", e);
        }

        checkingAccount.setSeeAlso(new HashSet<>(Collections.singletonList(relatedTerm)));

        try {
            checkingAccount = glossaryService.updateTerm(checkingAccount);

            assertNotNull(checkingAccount.getSeeAlso());
            assertEquals(checkingAccount.getSeeAlso().size(), 1);
        } catch (AtlasBaseException e) {
            fail("RelatedTerm association should've succeeded", e);
        }

        relatedTerm.setTermGuid(fixedRateMortgage.getGuid());

        assertNotNull(adjustableRateMortgage);

        try {
            adjustableRateMortgage = glossaryService.getTerm(adjustableRateMortgage.getGuid());
        } catch (AtlasBaseException e) {
            fail("Glossary term fetch should've been a success", e);
        }

        adjustableRateMortgage.setSeeAlso(new HashSet<>(Collections.singletonList(relatedTerm)));

        try {
            adjustableRateMortgage = glossaryService.updateTerm(adjustableRateMortgage);

            assertNotNull(adjustableRateMortgage.getSeeAlso());
            assertEquals(adjustableRateMortgage.getSeeAlso().size(), 1);
        } catch (AtlasBaseException e) {
            fail("RelatedTerm association should've succeeded", e);
        }
    }

    @Test(dataProvider = "getGlossaryTermsProvider", groups = "Glossary.GET.postUpdate", dependsOnGroups = "Glossary.UPDATE")
    public void testGetGlossaryTerms(int offset, int limit, int expected) {
        String    guid      = bankGlossary.getGuid();
        SortOrder sortOrder = SortOrder.ASCENDING;

        try {
            List<AtlasRelatedTermHeader> glossaryTerms = glossaryService.getGlossaryTermsHeaders(guid, offset, limit, sortOrder);

            assertNotNull(glossaryTerms);
            assertEquals(glossaryTerms.size(), expected);
        } catch (AtlasBaseException e) {
            fail("Glossary term fetching should've succeeded", e);
        }
    }

    @DataProvider
    public Object[][] getGlossaryCategoriesProvider() {
        return new Object[][] {
                // offset, limit, expected
                {0, -1, 4},
                {0, 2, 2},
                {2, 5, 2},
        };
    }

    @Test(dataProvider = "getGlossaryCategoriesProvider", groups = "Glossary.GET.postUpdate", dependsOnGroups = "Glossary.UPDATE")
    public void testGetGlossaryCategories(int offset, int limit, int expected) {
        String    guid      = bankGlossary.getGuid();
        SortOrder sortOrder = SortOrder.ASCENDING;

        try {
            List<AtlasRelatedCategoryHeader> glossaryCategories = glossaryService.getGlossaryCategoriesHeaders(guid, offset, limit, sortOrder);

            assertNotNull(glossaryCategories);
            assertEquals(glossaryCategories.size(), expected);
        } catch (AtlasBaseException e) {
            fail("Glossary term fetching should've succeeded");
        }
    }

    @DataProvider
    public Object[][] getCategoryTermsProvider() {
        return new Object[][] {
                // offset, limit, expected
                {0, -1, 2},
                {0, 2, 2},
                {1, 5, 1},
                {2, 5, 0},
        };
    }

    @Test(dataProvider = "getCategoryTermsProvider", dependsOnGroups = "Glossary.CREATE")
    public void testGetCategoryTerms(int offset, int limit, int expected) {
        for (AtlasGlossaryCategory c : Arrays.asList(accountCategory, mortgageCategory)) {
            try {
                List<AtlasRelatedTermHeader> categoryTerms = glossaryService.getCategoryTerms(c.getGuid(), offset, limit, SortOrder.ASCENDING);

                assertNotNull(categoryTerms);
                assertEquals(categoryTerms.size(), expected);
            } catch (AtlasBaseException e) {
                fail("Category term retrieval should've been a success", e);
            }
        }
    }

    @Test
    public void testGetTemplate() {
        try {
            String glossaryTermHeaderListAsString = GlossaryTermUtils.getGlossaryTermHeaders();

            assertNotNull(glossaryTermHeaderListAsString);
            assertEquals(glossaryTermHeaderListAsString, "GlossaryName, TermName, ShortDescription, LongDescription, Examples, Abbreviation, Usage, AdditionalAttributes, TranslationTerms, ValidValuesFor, Synonyms, ReplacedBy, ValidValues, ReplacementTerms, SeeAlso, TranslatedTerms, IsA, Antonyms, Classifies, PreferredToTerms, PreferredTerms");
        } catch (Exception e) {
            fail("The Template for Glossary Term should've been a success", e);
        }
    }

    @Test(dependsOnGroups = "Glossary.CREATE")
    public void testImportGlossaryData() {
        try {
            InputStream        inputStream        = getFile(CSV_FILES, "template_1.csv");
            BulkImportResponse bulkImportResponse = glossaryService.importGlossaryData(inputStream, "template_1.csv");

            assertNotNull(bulkImportResponse);
            assertEquals(bulkImportResponse.getSuccessImportInfoList().size(), 1);

            InputStream        inputStream1        = getFile(EXCEL_FILES, "template_1.xlsx");
            BulkImportResponse bulkImportResponse1 = glossaryService.importGlossaryData(inputStream1, "template_1.xlsx");

            assertNotNull(bulkImportResponse1);
            assertEquals(bulkImportResponse1.getSuccessImportInfoList().size(), 1);

            // With circular dependent relations
            InputStream        inputStream2        = getFile(CSV_FILES, "template_with_circular_relationship.csv");
            BulkImportResponse bulkImportResponse2 = glossaryService.importGlossaryData(inputStream2, "template_with_circular_relationship.csv");

            assertNotNull(bulkImportResponse2);
            assertEquals(bulkImportResponse2.getSuccessImportInfoList().size(), 3);
            assertEquals(bulkImportResponse2.getFailedImportInfoList().size(), 0);
        } catch (AtlasBaseException e) {
            fail("The GlossaryTerm should have been created " + e);
        }
    }

    @Test
    public void testEmptyFileException() {
        InputStream inputStream = getFile(CSV_FILES, "empty.csv");

        try {
            glossaryService.importGlossaryData(inputStream, "empty.csv");
            fail("Error occurred : Failed to recognize the empty file.");
        } catch (AtlasBaseException e) {
            assertEquals(e.getMessage(), "No data found in the uploaded file");
        }
    }

    @Test
    public void testInvalidFileException() {
        InputStream inputStream = getFile(EXCEL_FILES, "invalid_xls.xls");

        try {
            BulkImportResponse bulkImportResponse = glossaryService.importGlossaryData(inputStream, "invalid_xls.xls");
            fail("Error occurred : Failed to recognize the invalid xls file.");
        } catch (AtlasBaseException e) {
            assertEquals(e.getMessage(), "Invalid XLS file");
        }
    }

    @Test
    public void testFileExtension() {
        InputStream  inputStream = getFile(CSV_FILES, "incorrectEXT.py");
        final String userDir     = System.getProperty("user.dir");
        String       filePath    = getTestFilePath(userDir, CSV_FILES, "incorrectEXT.py");
        File         f           = new File(filePath);

        try {
            FileUtils.readFileData("incorrectEXT.py", inputStream);
            fail("Error occurred : Incorrect file extension.");
        } catch (AtlasBaseException e) {
            assertEquals(e.getMessage(), "The provided file type: " + f.getName() + " is not supported. Expected file formats are .csv and .xls.");
        }
    }

    @Test
    public void testIncorrectFileException() {
        InputStream inputStream = getFile(CSV_FILES, "incorrectFile.csv");

        try {
            BulkImportResponse bulkImportResponse = glossaryService.importGlossaryData(inputStream, "incorrectFile.csv");

            assertEquals(bulkImportResponse.getSuccessImportInfoList().size(), 1);

            //Due to invalid Relation we get Failed message even the import succeeded for the term
            assertEquals(bulkImportResponse.getFailedImportInfoList().size(), 1);
        } catch (AtlasBaseException e) {
            fail("The incorrect file exception should have handled " + e);
        }
    }

    @DataProvider(name = "getAllGlossaryForPaginationDataProvider")
    public Object[][] getAllGlossaryForPaginationDataProvider() {
        List<String> listAllGuids = Arrays.asList("guid-1", "guid-2", "guid-3", "guid-4", "guid-5",
                "guid-6", "guid-7", "guid-8", "guid-9", "guid-10");

        return new Object[][] {
                // limit, offset, sortOrder, expected glossaries guids, skipped glossaries guids, expected page-count
                {-1, 0, SortOrder.ASCENDING, 10, listAllGuids, null, 1},
                {15, 0, SortOrder.ASCENDING, 10, listAllGuids, null, 1},
                {10, 0, SortOrder.ASCENDING, 10, listAllGuids, null, 1},
                {10, 2, SortOrder.ASCENDING, 8, listAllGuids, null, 1},
                {10, 6, SortOrder.ASCENDING, 4, listAllGuids, null, 1},
                {5, 0, SortOrder.ASCENDING, 5, listAllGuids, null, 1},
                {5, 5, SortOrder.ASCENDING, 5, listAllGuids, null, 1},
                {5, 2, SortOrder.ASCENDING, 5, listAllGuids, null, 1},
                {10, 0, SortOrder.ASCENDING, 9, listAllGuids, Collections.singletonList("guid-3"), 1},
                {5, 2, SortOrder.ASCENDING, 5, listAllGuids, Arrays.asList("guid-3", "guid-7"), 2}
        };
    }

    @Test(dataProvider = "getAllGlossaryForPaginationDataProvider")
    void testGetGlossaries_WithPaginationHandlingSkippedGlossaries(int limit, int offset, SortOrder sortOrder, int expectedSize,
            List<String> allGlossaryGuids, List<String> guidsToSkip, int expectedPageCount) throws Exception {
        mockedEntityStore = mock(AtlasEntityStore.class);
        when(mockedDataAccess.getAtlasEntityStore()).thenReturn(mockedEntityStore);

        List<String> finalGuidsToSkip = (guidsToSkip == null) ? Collections.emptyList() : guidsToSkip;

        try (MockedStatic<AtlasGraphUtilsV2> mockedGraphUtilsClass = Mockito.mockStatic(AtlasGraphUtilsV2.class)) {
            // Mocking the retrieval of glossary GUIDs from the system
            mockedGraphUtilsClass.when(() -> AtlasGraphUtilsV2.findEntityGUIDsByType(anyString(), any()))
                    .thenReturn(allGlossaryGuids);

            //Mocking getByIds() so it removes skipped glossaries before returning results.
            when(mockedEntityStore.getByIds(anyList(), anyBoolean(), anyBoolean()))
                    .thenAnswer(invocation -> {
                        List<String>      requestedGuids   = invocation.getArgument(0); // Get first argument (List<String>)
                        List<AtlasEntity> filteredEntities = new ArrayList<>();

                        // Filter out guids to be skipped
                        for (String guid : requestedGuids) {
                            if (!finalGuidsToSkip.contains(guid)) {
                                AtlasEntity entity = new AtlasEntity();
                                entity.setGuid(guid);
                                filteredEntities.add(entity);

                                AtlasGlossary glossaryToReturn = new AtlasGlossary();
                                glossaryToReturn.setGuid(guid);
                                when(mockedGlossaryDTO.from(entity)).thenReturn(glossaryToReturn);
                            }
                        }
                        return new AtlasEntity.AtlasEntitiesWithExtInfo(filteredEntities);
                    });

            List<AtlasGlossary> fetchedGlossaries = mockedGlossaryService.getGlossaries(limit, offset, sortOrder);

            assertNotNull(fetchedGlossaries);

            int actualFetchedResults = fetchedGlossaries.size();
            assertEquals(actualFetchedResults, expectedSize, "Expected to fetch " + expectedSize + " glossaries but got " + actualFetchedResults);

            for (AtlasGlossary fetchedGlossary : fetchedGlossaries) {
                assertNotNull(fetchedGlossary);
            }

            // Capture method calls to count pages
            ArgumentCaptor<List<String>> guidsCaptor = ArgumentCaptor.forClass(List.class);
            verify(mockedEntityStore, atLeast(1)).getByIds(guidsCaptor.capture(), anyBoolean(), anyBoolean());

            // Number of pages that were needed is the number of times `getByIds()` was called
            int actualPageCount = guidsCaptor.getAllValues().size();

            //Verify that the number of pages fetched matches expectation
            assertEquals(actualPageCount, expectedPageCount,
                    "Expected " + expectedPageCount + " pages but got " + actualPageCount);
        } catch (Exception e) {
            fail("Test failed due to exception: " + e.getMessage());
        }
    }

    private static InputStream getFile(String subDir, String fileName) {
        final String userDir  = System.getProperty("user.dir");
        String       filePath = getTestFilePath(userDir, subDir, fileName);
        File         f        = new File(filePath);
        InputStream  fs       = null;

        try {
            fs = new FileInputStream(f);
        } catch (FileNotFoundException e) {
            LOG.error("File could not be found at: {}", filePath, e);
        }

        return fs;
    }

    private static String getTestFilePath(String startPath, String subDir, String fileName) {
        if (StringUtils.isNotEmpty(subDir)) {
            return startPath + "/src/test/resources/" + subDir + "/" + fileName;
        } else {
            return startPath + "/src/test/resources/" + fileName;
        }
    }
}
