/**
 * 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
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.atlas.hive.bridge;

import org.apache.atlas.AtlasClient;
import org.apache.atlas.AtlasClientV2;
import org.apache.atlas.AtlasServiceException;
import org.apache.atlas.SortOrder;
import org.apache.atlas.hive.model.HiveDataTypes;
import org.apache.atlas.model.discovery.AtlasSearchResult;
import org.apache.atlas.model.discovery.SearchParameters;
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.model.instance.EntityMutations;
import org.apache.atlas.type.AtlasTypeUtil;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Options;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hive.metastore.TableType;
import org.apache.hadoop.hive.metastore.api.Database;
import org.apache.hadoop.hive.metastore.api.FieldSchema;
import org.apache.hadoop.hive.ql.metadata.Hive;
import org.apache.hadoop.hive.ql.metadata.HiveException;
import org.apache.hadoop.hive.ql.metadata.Partition;
import org.apache.hadoop.hive.ql.metadata.Table;
import org.apache.hadoop.mapred.TextInputFormat;

import org.mockito.Matchers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import java.io.BufferedReader;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.apache.atlas.hive.hook.events.BaseHiveEvent.ATTRIBUTE_CLUSTER_NAME;
import static org.apache.atlas.hive.hook.events.BaseHiveEvent.ATTRIBUTE_STORAGEDESC;
import static org.apache.atlas.hive.hook.events.BaseHiveEvent.HIVE_TYPE_DB;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.*;
import static org.testng.AssertJUnit.*;

public class HiveMetaStoreBridgeTest {
    private static final String TEST_DB_NAME       = "default";
    public  static final String METADATA_NAMESPACE = "primary";
    public  static final String TEST_TABLE_NAME    = "test_table";
    public  static final String TEST_DB_NAME_2     = "enr_edl";
    public  static final String TEST_DB_NAME_3     = "dummy";
    public  static final String TEST_TABLE_NAME_2  = "testing_enr_edl_1";
    public  static final String TEST_TABLE_NAME_3  = "testing_enr_edl_2";
    public  static final String TEST_TABLE_NAME_4  = "testing_dummy_1";
    public  static final String TEST_TABLE_NAME_5  = "testing_dummy_2";
    private static final String TEST_FILE_NAME = "testfile.txt";

    @Mock
    private Hive hiveClient;

    @Mock
    private AtlasClientV2 atlasClientV2;

    @Mock
    private AtlasEntity atlasEntity;

    @Mock
    private AtlasEntity.AtlasEntityWithExtInfo atlasEntityWithExtInfo;

    @BeforeMethod
    public void initializeMocks() {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void testImportThatUpdatesRegisteredDatabase() throws Exception {
        // setup database
        when(hiveClient.getAllDatabases()).thenReturn(Arrays.asList(new String[]{TEST_DB_NAME}));
        String description = "This is a default database";
        Database db = new Database(TEST_DB_NAME, description, "/user/hive/default", null);
        when(hiveClient.getDatabase(TEST_DB_NAME)).thenReturn(db);
        when(hiveClient.getAllTables(TEST_DB_NAME)).thenReturn(Arrays.asList(new String[]{}));

        returnExistingDatabase(TEST_DB_NAME, atlasClientV2, METADATA_NAMESPACE);

        when(atlasEntityWithExtInfo.getEntity("72e06b34-9151-4023-aa9d-b82103a50e76"))
                .thenReturn((new AtlasEntity.AtlasEntityWithExtInfo(
                        getEntity(HiveDataTypes.HIVE_DB.getName(), AtlasClient.GUID, "72e06b34-9151-4023-aa9d-b82103a50e76"))).getEntity());

        HiveMetaStoreBridge bridge = new HiveMetaStoreBridge(METADATA_NAMESPACE, hiveClient, atlasClientV2);
        bridge.importHiveMetadata(null, null, true);

        // verify update is called
        verify(atlasClientV2).updateEntity(Matchers.any(AtlasEntity.AtlasEntityWithExtInfo.class));
    }

    @Test
    public void testImportThatUpdatesRegisteredTable() throws Exception {
        setupDB(hiveClient, TEST_DB_NAME);

        List<Table> hiveTables = setupTables(hiveClient, TEST_DB_NAME, TEST_TABLE_NAME);

        returnExistingDatabase(TEST_DB_NAME, atlasClientV2, METADATA_NAMESPACE);

        // return existing table

        when(atlasEntityWithExtInfo.getEntity("82e06b34-9151-4023-aa9d-b82103a50e77"))
                .thenReturn((new AtlasEntity.AtlasEntityWithExtInfo(
                        getEntity(HiveDataTypes.HIVE_TABLE.getName(), AtlasClient.GUID, "82e06b34-9151-4023-aa9d-b82103a50e77"))).getEntity());

        when(atlasClientV2.getEntityByAttribute(HiveDataTypes.HIVE_TABLE.getName(),
                Collections.singletonMap(AtlasClient.REFERENCEABLE_ATTRIBUTE_NAME,
                        HiveMetaStoreBridge.getTableQualifiedName(METADATA_NAMESPACE, TEST_DB_NAME, TEST_TABLE_NAME)), true, true))
                .thenReturn(new AtlasEntity.AtlasEntityWithExtInfo(
                        getEntity(HiveDataTypes.HIVE_TABLE.getName(), AtlasClient.GUID, "82e06b34-9151-4023-aa9d-b82103a50e77")));

        when(atlasEntityWithExtInfo.getEntity("82e06b34-9151-4023-aa9d-b82103a50e77"))
                .thenReturn(createTableReference());

        Table testTable =  hiveTables.get(0);
        String processQualifiedName = HiveMetaStoreBridge.getTableProcessQualifiedName(METADATA_NAMESPACE, testTable);

        when(atlasClientV2.getEntityByAttribute(HiveDataTypes.HIVE_PROCESS.getName(),
                Collections.singletonMap(AtlasClient.REFERENCEABLE_ATTRIBUTE_NAME,
                        processQualifiedName), true ,true))
                .thenReturn(new AtlasEntity.AtlasEntityWithExtInfo(
                        getEntity(HiveDataTypes.HIVE_PROCESS.getName(), AtlasClient.GUID, "82e06b34-9151-4023-aa9d-b82103a50e77")));


        HiveMetaStoreBridge bridge = new HiveMetaStoreBridge(METADATA_NAMESPACE, hiveClient, atlasClientV2);
        bridge.importHiveMetadata(null, null, true);

        // verify update is called on table
        verify(atlasClientV2, times(2)).updateEntity(Matchers.any(AtlasEntity.AtlasEntityWithExtInfo.class));
    }

    private void returnExistingDatabase(String databaseName, AtlasClientV2 atlasClientV2, String metadataNamespace)
            throws AtlasServiceException {
        //getEntity(HiveDataTypes.HIVE_DB.getName(), AtlasClient.GUID, "72e06b34-9151-4023-aa9d-b82103a50e76");

        when(atlasClientV2.getEntityByAttribute(HiveDataTypes.HIVE_DB.getName(),
                Collections.singletonMap(AtlasClient.REFERENCEABLE_ATTRIBUTE_NAME,
                        HiveMetaStoreBridge.getDBQualifiedName(METADATA_NAMESPACE, TEST_DB_NAME)), true, true))
                .thenReturn((new AtlasEntity.AtlasEntityWithExtInfo(
                        getEntity(HiveDataTypes.HIVE_DB.getName(), AtlasClient.GUID, "72e06b34-9151-4023-aa9d-b82103a50e76"))));

    }

    private List<Table> setupTables(Hive hiveClient, String databaseName, String... tableNames) throws HiveException {
        List<Table> tables = new ArrayList<>();
        when(hiveClient.getAllTables(databaseName)).thenReturn(Arrays.asList(tableNames));
        for(String tableName : tableNames) {
            Table testTable = createTestTable(databaseName, tableName);
            when(hiveClient.getTable(databaseName, tableName)).thenReturn(testTable);
            tables.add(testTable);
        }
        return tables;
    }

    private void setupDB(Hive hiveClient, String databaseName) throws HiveException {
        when(hiveClient.getAllDatabases()).thenReturn(Arrays.asList(new String[]{databaseName}));
        when(hiveClient.getDatabase(databaseName)).thenReturn(
                new Database(databaseName, "Default database", "/user/hive/default", null));
    }

    @Test
    public void testImportWhenPartitionKeysAreNull() throws Exception {
        setupDB(hiveClient, TEST_DB_NAME);
        List<Table> hiveTables = setupTables(hiveClient, TEST_DB_NAME, TEST_TABLE_NAME);
        Table hiveTable = hiveTables.get(0);

        returnExistingDatabase(TEST_DB_NAME, atlasClientV2, METADATA_NAMESPACE);


        when(atlasClientV2.getEntityByAttribute(HiveDataTypes.HIVE_TABLE.getName(),
                Collections.singletonMap(AtlasClient.REFERENCEABLE_ATTRIBUTE_NAME,
                        HiveMetaStoreBridge.getTableQualifiedName(METADATA_NAMESPACE, TEST_DB_NAME, TEST_TABLE_NAME)), true, true))
                .thenReturn(new AtlasEntity.AtlasEntityWithExtInfo(
                        getEntity(HiveDataTypes.HIVE_TABLE.getName(), AtlasClient.GUID, "82e06b34-9151-4023-aa9d-b82103a50e77")));

        String processQualifiedName = HiveMetaStoreBridge.getTableProcessQualifiedName(METADATA_NAMESPACE, hiveTable);

        when(atlasClientV2.getEntityByAttribute(HiveDataTypes.HIVE_PROCESS.getName(),
                Collections.singletonMap(AtlasClient.REFERENCEABLE_ATTRIBUTE_NAME,
                        processQualifiedName), true, true))
                .thenReturn(new AtlasEntity.AtlasEntityWithExtInfo(
                        getEntity(HiveDataTypes.HIVE_PROCESS.getName(), AtlasClient.GUID, "82e06b34-9151-4023-aa9d-b82103a50e77")));

        when(atlasEntityWithExtInfo.getEntity("82e06b34-9151-4023-aa9d-b82103a50e77"))
                .thenReturn(createTableReference());

        Partition partition = mock(Partition.class);
        when(partition.getTable()).thenReturn(hiveTable);
        List partitionValues = Arrays.asList(new String[]{});
        when(partition.getValues()).thenReturn(partitionValues);

        when(hiveClient.getPartitions(hiveTable)).thenReturn(Arrays.asList(new Partition[]{partition}));

        HiveMetaStoreBridge bridge = new HiveMetaStoreBridge(METADATA_NAMESPACE, hiveClient, atlasClientV2);
        try {
            bridge.importHiveMetadata(null, null, true);
        } catch (Exception e) {
            Assert.fail("Partition with null key caused import to fail with exception ", e);
        }
    }

    @Test
    public void testImportContinuesWhenTableRegistrationFails() throws Exception {
        setupDB(hiveClient, TEST_DB_NAME);
        final String table2Name = TEST_TABLE_NAME + "_1";
        List<Table> hiveTables = setupTables(hiveClient, TEST_DB_NAME, TEST_TABLE_NAME, table2Name);

        returnExistingDatabase(TEST_DB_NAME, atlasClientV2, METADATA_NAMESPACE);
        when(hiveClient.getTable(TEST_DB_NAME, TEST_TABLE_NAME)).thenThrow(new RuntimeException("Timeout while reading data from hive metastore"));

        when(atlasClientV2.getEntityByAttribute(HiveDataTypes.HIVE_TABLE.getName(),
                Collections.singletonMap(AtlasClient.REFERENCEABLE_ATTRIBUTE_NAME,
                        HiveMetaStoreBridge.getTableQualifiedName(METADATA_NAMESPACE, TEST_DB_NAME, TEST_TABLE_NAME))))
                .thenReturn(new AtlasEntity.AtlasEntityWithExtInfo(
                        getEntity(HiveDataTypes.HIVE_TABLE.getName(), AtlasClient.GUID, "82e06b34-9151-4023-aa9d-b82103a50e77")));

        when(atlasEntityWithExtInfo.getEntity("82e06b34-9151-4023-aa9d-b82103a50e77"))
                .thenReturn(createTableReference());

        Table testTable =  hiveTables.get(1);
        String processQualifiedName = HiveMetaStoreBridge.getTableProcessQualifiedName(METADATA_NAMESPACE, testTable);

        when(atlasClientV2.getEntityByAttribute(HiveDataTypes.HIVE_PROCESS.getName(),
                Collections.singletonMap(AtlasClient.REFERENCEABLE_ATTRIBUTE_NAME,
                        processQualifiedName)))
                .thenReturn(new AtlasEntity.AtlasEntityWithExtInfo(
                        getEntity(HiveDataTypes.HIVE_PROCESS.getName(), AtlasClient.GUID, "82e06b34-9151-4023-aa9d-b82103a50e77")));

        HiveMetaStoreBridge bridge = new HiveMetaStoreBridge(METADATA_NAMESPACE, hiveClient, atlasClientV2);
        try {
            bridge.importHiveMetadata(null, null, false);
        } catch (Exception e) {
            Assert.fail("Table registration failed with exception", e);
        }
    }

    @Test
    public void testImportFailsWhenTableRegistrationFails() throws Exception {
        setupDB(hiveClient, TEST_DB_NAME);
        final String table2Name = TEST_TABLE_NAME + "_1";
        List<Table> hiveTables = setupTables(hiveClient, TEST_DB_NAME, TEST_TABLE_NAME, table2Name);

        returnExistingDatabase(TEST_DB_NAME, atlasClientV2, METADATA_NAMESPACE);
        when(hiveClient.getTable(TEST_DB_NAME, TEST_TABLE_NAME)).thenThrow(new RuntimeException("Timeout while reading data from hive metastore"));


        when(atlasClientV2.getEntityByAttribute(HiveDataTypes.HIVE_TABLE.getName(),
                Collections.singletonMap(AtlasClient.REFERENCEABLE_ATTRIBUTE_NAME,
                        HiveMetaStoreBridge.getTableQualifiedName(METADATA_NAMESPACE, TEST_DB_NAME, TEST_TABLE_NAME))))
                .thenReturn(new AtlasEntity.AtlasEntityWithExtInfo(
                        getEntity(HiveDataTypes.HIVE_TABLE.getName(), AtlasClient.GUID, "82e06b34-9151-4023-aa9d-b82103a50e77")));


        when(atlasEntityWithExtInfo.getEntity("82e06b34-9151-4023-aa9d-b82103a50e77"))
                .thenReturn(createTableReference());

        Table testTable  = hiveTables.get(1);
        String processQualifiedName = HiveMetaStoreBridge.getTableProcessQualifiedName(METADATA_NAMESPACE, testTable);

        when(atlasClientV2.getEntityByAttribute(HiveDataTypes.HIVE_PROCESS.getName(),
                Collections.singletonMap(AtlasClient.REFERENCEABLE_ATTRIBUTE_NAME,
                        processQualifiedName)))
                .thenReturn(new AtlasEntity.AtlasEntityWithExtInfo(
                        getEntity(HiveDataTypes.HIVE_PROCESS.getName(), AtlasClient.GUID, "82e06b34-9151-4023-aa9d-b82103a50e77")));

        HiveMetaStoreBridge bridge = new HiveMetaStoreBridge(METADATA_NAMESPACE, hiveClient, atlasClientV2);
        try {
            bridge.importHiveMetadata(null, null, true);
            Assert.fail("Table registration is supposed to fail");
        } catch (Exception e) {
            //Expected
        }
    }

    private AtlasEntity getEntity(String typeName, String attr, String value) {
        return new AtlasEntity(typeName, attr, value);
    }

    private AtlasEntity createTableReference() {
        AtlasEntity tableEntity = new AtlasEntity(HiveDataTypes.HIVE_TABLE.getName());
        AtlasEntity sdEntity = new AtlasEntity(HiveDataTypes.HIVE_STORAGEDESC.getName());
        tableEntity.setAttribute(ATTRIBUTE_STORAGEDESC, AtlasTypeUtil.getObjectId(sdEntity));
        return tableEntity;
    }

    private Table createTestTable(String databaseName, String tableName) throws HiveException {
        Table table = new Table(databaseName, tableName);
        table.setInputFormatClass(TextInputFormat.class);
        table.setFields(new ArrayList<FieldSchema>() {{
            add(new FieldSchema("col1", "string", "comment1"));
        }
        });
        table.setTableType(TableType.EXTERNAL_TABLE);
        table.setDataLocation(new Path("somehdfspath"));
        return table;
    }

    @Test
    public void testDeleteEntitiesForNonExistingHiveMetadata() throws Exception {

        String DB1_GUID = "72e06b34-9151-4023-aa9d-b82103a50e76";
        String DB2_GUID = "98w06b34-9151-4023-aa9d-b82103a50w67";
        String DB1_TABLE1_GUID = "82e06b34-9151-4023-aa9d-b82103a50e77";
        String DB1_TABLE2_GUID = "66e06b34-9151-4023-aa9d-b82103a50e55";
        String DB2_TABLE1_GUID = "99q06b34-9151-4023-aa9d-b82103a50i22";
        String DB2_TABLE2_GUID = "48z06b34-9151-4023-aa9d-b82103a50n39";

        // IN BOTH HIVE AND ATLAS GUID IS PRESENT MEANS TABLE/ENTITY IS PRESENT SO WILL DO IMPORT HIVE SCRIPT RUN
        // 1) WHEN DB 1 AND TABLE 1 BOTH ARE PRESENT IN IMPORT-HIVE SCRIPT COMMAND, THEN DELETING ONLY SINGLE TABLE FROM DB 1.

        AtlasEntityHeader atlasEntityHeader = new AtlasEntityHeader(HIVE_TYPE_DB, DB1_TABLE1_GUID,
                Collections.singletonMap(AtlasClient.QUALIFIED_NAME, HiveMetaStoreBridge.getDBQualifiedName(METADATA_NAMESPACE, TEST_DB_NAME)));
        AtlasSearchResult atlasSearchResult = new AtlasSearchResult();
        atlasSearchResult.setEntities(Collections.singletonList(atlasEntityHeader));

        SearchParameters.FilterCriteria filterCriteria = new SearchParameters.FilterCriteria();
        filterCriteria.setAttributeName(ATTRIBUTE_CLUSTER_NAME);
        filterCriteria.setAttributeValue(METADATA_NAMESPACE);
        filterCriteria.setOperator(SearchParameters.Operator.EQ);

        when(atlasClientV2.basicSearch(HIVE_TYPE_DB, filterCriteria, null, TEST_DB_NAME_2, true, 1, 100))
                .thenReturn(atlasSearchResult);

        AtlasEntityHeader atlasEntityHeader1 = new AtlasEntityHeader(HIVE_TYPE_TABLE, DB1_TABLE1_GUID,
                Collections.singletonMap(AtlasClient.QUALIFIED_NAME, HiveMetaStoreBridge.getTableQualifiedName(METADATA_NAMESPACE, TEST_DB_NAME_2, TEST_TABLE_NAME_2)));
        AtlasEntityHeader atlasEntityHeader2 = new AtlasEntityHeader(HIVE_TYPE_TABLE, DB1_TABLE2_GUID,
                Collections.singletonMap(AtlasClient.QUALIFIED_NAME, HiveMetaStoreBridge.getTableQualifiedName(METADATA_NAMESPACE, TEST_DB_NAME_2, TEST_TABLE_NAME_3)));
        AtlasSearchResult atlasSearchResult1 = new AtlasSearchResult();
        atlasSearchResult1.setEntities(Arrays.asList(atlasEntityHeader1, atlasEntityHeader2));

        SearchParameters.FilterCriteria filterCriteria1 = new SearchParameters.FilterCriteria();
        filterCriteria1.setAttributeName(ATTRIBUTE_CLUSTER_NAME);
        filterCriteria1.setAttributeValue(HiveMetaStoreBridge.getTableQualifiedName(METADATA_NAMESPACE, TEST_DB_NAME_2.toLowerCase(), TEST_TABLE_NAME_2.toLowerCase()));
        filterCriteria1.setAttributeValue(METADATA_NAMESPACE);
        filterCriteria1.setOperator(SearchParameters.Operator.EQ);

        when(atlasClientV2.basicSearch(HIVE_TYPE_TABLE, filterCriteria1, null, TEST_TABLE_NAME_2, true, 1, 100))
                .thenReturn(atlasSearchResult1);

        EntityMutationResponse entityMutationResponse1 = new EntityMutationResponse();
        entityMutationResponse1.setMutatedEntities(Collections.singletonMap(EntityMutations.EntityOperation.DELETE, Arrays.asList(atlasEntityHeader1)));
        when(atlasClientV2.deleteEntityByGuid(DB1_TABLE1_GUID)).thenReturn(entityMutationResponse1);

        HiveMetaStoreBridge hiveMetaStoreBridge = new HiveMetaStoreBridge(METADATA_NAMESPACE, hiveClient, atlasClientV2);
        hiveMetaStoreBridge.deleteEntitiesForNonExistingHiveMetadata(true, TEST_DB_NAME_2, TEST_TABLE_NAME_2);

        assertEquals(DB1_TABLE1_GUID, entityMutationResponse1.getMutatedEntities().get(EntityMutations.EntityOperation.DELETE).get(0).getGuid());

        // 1) WHEN DB 2 AND TABLE 1 BOTH ARE PRESENT, THEN DELETING ONLY SINGLE TABLE FROM DB 2.

        AtlasEntityHeader atlasEntityHeader3 = new AtlasEntityHeader(HIVE_TYPE_DB, DB2_TABLE1_GUID,
                Collections.singletonMap(AtlasClient.QUALIFIED_NAME, HiveMetaStoreBridge.getDBQualifiedName(METADATA_NAMESPACE, TEST_DB_NAME)));
        AtlasSearchResult atlasSearchResult2 = new AtlasSearchResult();
        atlasSearchResult2.setEntities(Collections.singletonList(atlasEntityHeader3));

        when(atlasClientV2.basicSearch(HIVE_TYPE_DB, filterCriteria, null, TEST_DB_NAME_3, true, 1, 100))
                .thenReturn(atlasSearchResult2);

        AtlasSearchResult atlasSearchResult3 = new AtlasSearchResult();
        atlasSearchResult3.setEntities(Arrays.asList(new AtlasEntityHeader(HIVE_TYPE_TABLE, DB2_TABLE1_GUID,
                        Collections.singletonMap(AtlasClient.QUALIFIED_NAME, HiveMetaStoreBridge.getTableQualifiedName(METADATA_NAMESPACE, TEST_DB_NAME_3, TEST_TABLE_NAME_4))),
                new AtlasEntityHeader(HIVE_TYPE_TABLE, DB2_TABLE1_GUID,
                        Collections.singletonMap(AtlasClient.QUALIFIED_NAME, HiveMetaStoreBridge.getTableQualifiedName(METADATA_NAMESPACE, TEST_DB_NAME_3, TEST_TABLE_NAME_4)))));

        SearchParameters.FilterCriteria filterCriteria2 = new SearchParameters.FilterCriteria();
        filterCriteria2.setAttributeName(ATTRIBUTE_CLUSTER_NAME);
        filterCriteria2.setAttributeValue(HiveMetaStoreBridge.getTableQualifiedName(METADATA_NAMESPACE, TEST_DB_NAME_3.toLowerCase(), TEST_TABLE_NAME_4.toLowerCase()));
        filterCriteria2.setAttributeValue(METADATA_NAMESPACE);
        filterCriteria2.setOperator(SearchParameters.Operator.EQ);

        when(atlasClientV2.basicSearch(HIVE_TYPE_TABLE, filterCriteria2, null, TEST_TABLE_NAME_4, true, 1, 100))
                .thenReturn(atlasSearchResult1);

        EntityMutationResponse entityMutationResponse2 = new EntityMutationResponse();
        entityMutationResponse2.setMutatedEntities(Collections.singletonMap(EntityMutations.EntityOperation.DELETE, Arrays.asList(new AtlasEntityHeader(HIVE_TYPE_TABLE, DB2_TABLE1_GUID,
                Collections.singletonMap(AtlasClient.QUALIFIED_NAME, HiveMetaStoreBridge.getTableQualifiedName(METADATA_NAMESPACE, TEST_DB_NAME_3, TEST_TABLE_NAME_4))))));
        when(atlasClientV2.deleteEntityByGuid(DB2_TABLE1_GUID)).thenReturn(entityMutationResponse2);

        hiveMetaStoreBridge.deleteEntitiesForNonExistingHiveMetadata(true, TEST_DB_NAME_3, TEST_TABLE_NAME_4);

        assertEquals(DB2_TABLE1_GUID, entityMutationResponse2.getMutatedEntities().get(EntityMutations.EntityOperation.DELETE).get(0).getGuid());

        // 3) WHEN DB 1 IS PRESENT, THEN DELETING ALL TABLE FROM DB

        EntityMutationResponse entityMutationResponse3 = new EntityMutationResponse();
        entityMutationResponse3.setMutatedEntities(Collections.singletonMap(EntityMutations.EntityOperation.DELETE, Arrays.asList(atlasEntityHeader1, atlasEntityHeader2)));
        when(atlasClientV2.deleteEntityByGuid(DB1_TABLE1_GUID)).thenReturn(entityMutationResponse2);
        when(atlasClientV2.deleteEntityByGuid(DB1_TABLE2_GUID)).thenReturn(entityMutationResponse2);
        hiveMetaStoreBridge.deleteEntitiesForNonExistingHiveMetadata(true, TEST_DB_NAME_2, null);

        assertEquals(DB1_TABLE1_GUID, entityMutationResponse3.getMutatedEntities().get(EntityMutations.EntityOperation.DELETE).get(0).getGuid());
        assertEquals(DB1_TABLE2_GUID, entityMutationResponse3.getMutatedEntities().get(EntityMutations.EntityOperation.DELETE).get(1).getGuid());
    }


    @Test
    public void testPrintUsage() throws Exception {
        // Use reflection to access the private static method
        java.lang.reflect.Method printUsageMethod = HiveMetaStoreBridge.class.getDeclaredMethod("printUsage", Options.class);
        printUsageMethod.setAccessible(true);

        Options options = new Options();
        options.addOption("d", "database", true, "Specify database");
        options.addOption("t", "table", true, "Specify table");
        options.addOption("f", "filename", true, "Specify input file");
        options.addOption("o", "output", true, "Specify output path or file");
        options.addOption("i", false, "Export without importing");
        options.addOption("deleteNonExisting", false, "Delete non-existing entities");

        // Call the private method using reflection
        printUsageMethod.invoke(null, options);
    }

    @Test
    public void testPrepareCommandLineOptions() throws Exception {
        // Use reflection to access the private static method
        java.lang.reflect.Method prepareCommandLineOptionsMethod = HiveMetaStoreBridge.class.getDeclaredMethod("prepareCommandLineOptions");
        prepareCommandLineOptionsMethod.setAccessible(true);

        // Call the private method using reflection
        Options options = (Options) prepareCommandLineOptionsMethod.invoke(null);

        // Verify the Options object is not null
        assertNotNull("Options object should not be null", options);
    }

    @Test
    public void testImportDataDirectlyToAtlas() throws Exception {
        CommandLine commandLine = mock(CommandLine.class);

        when(commandLine.hasOption(HiveMetaStoreBridge.OPTION_DELETE_NON_EXISTING)).thenReturn(true);
        when(commandLine.getOptionValue(HiveMetaStoreBridge.OPTION_DATABASE_SHORT)).thenReturn(TEST_DB_NAME);
        when(commandLine.getOptionValue(HiveMetaStoreBridge.OPTION_TABLE_SHORT)).thenReturn(TEST_TABLE_NAME);
        when(commandLine.getOptionValue(HiveMetaStoreBridge.OPTION_IMPORT_DATA_FILE_SHORT)).thenReturn("filename");
        when(commandLine.hasOption(HiveMetaStoreBridge.OPTION_FAIL_ON_ERROR)).thenReturn(false);

        HiveMetaStoreBridge hiveMetaStoreBridge = new HiveMetaStoreBridge(METADATA_NAMESPACE, hiveClient, atlasClientV2);
        boolean result = hiveMetaStoreBridge.importDataDirectlyToAtlas(commandLine);
        assertTrue("Should return true when deleteNonExisting is true", result);
    }

    @Test
    public void testImportDataDirectlyToAtlasNotEmptyFileImport() throws Exception {
        CommandLine commandLine = mock(CommandLine.class);

        when(commandLine.hasOption(HiveMetaStoreBridge.OPTION_DELETE_NON_EXISTING)).thenReturn(false);
        when(commandLine.getOptionValue(HiveMetaStoreBridge.OPTION_DATABASE_SHORT)).thenReturn(TEST_DB_NAME);
        when(commandLine.getOptionValue(HiveMetaStoreBridge.OPTION_TABLE_SHORT)).thenReturn(TEST_TABLE_NAME);
        when(commandLine.getOptionValue(HiveMetaStoreBridge.OPTION_IMPORT_DATA_FILE_SHORT)).thenReturn(TEST_FILE_NAME);
        when(commandLine.hasOption(HiveMetaStoreBridge.OPTION_FAIL_ON_ERROR)).thenReturn(false);

        // Create a spy of HiveMetaStoreBridge
        HiveMetaStoreBridge hiveMetaStoreBridge = spy(new HiveMetaStoreBridge(METADATA_NAMESPACE, hiveClient, atlasClientV2));

        // Mock File to simulate file existence and readability
        File file = mock(File.class);
        when(file.exists()).thenReturn(true);
        when(file.canRead()).thenReturn(true);


        BufferedReader bufferedReader = mock(BufferedReader.class);
        when(bufferedReader.readLine()).thenReturn(TEST_DB_NAME + ":" + TEST_TABLE_NAME).thenReturn(null);

        java.lang.reflect.Method importDatabasesMethod = HiveMetaStoreBridge.class.getDeclaredMethod("importDatabases", boolean.class, String.class, String.class);
        importDatabasesMethod.setAccessible(true);

        // Now run the method
        boolean result = hiveMetaStoreBridge.importDataDirectlyToAtlas(commandLine);

        assertFalse(result);
    }

    @Mock
    private EntityMutationResponse mutationResponse;
    @Mock
    private AtlasEntity.AtlasEntityWithExtInfo retrievedEntityWithExtInfo;

    @Mock
    private AtlasEntity.AtlasEntityWithExtInfo newEntityWithExtInfo;

    @Test
    public void testRegisterInstanceComprehensiveCoverage() throws Exception {
        HiveMetaStoreBridge bridge = spy(new HiveMetaStoreBridge(METADATA_NAMESPACE, hiveClient, atlasClientV2));

        // Mock atlasEntityWithExtInfo
        when(atlasEntityWithExtInfo.getEntity()).thenReturn(atlasEntity);
        final String TEST_TYPE_NAME = "hive";
        when(atlasEntity.getTypeName()).thenReturn(TEST_TYPE_NAME);

        // Mock mutation response with multiple created entities
        org.apache.atlas.model.instance.AtlasEntityHeader entityHeader1 = new org.apache.atlas.model.instance.AtlasEntityHeader();
        entityHeader1.setGuid("72e06b34-9151-4023-aa9d-b82103a50e76" + "-1");
        org.apache.atlas.model.instance.AtlasEntityHeader entityHeader2 = new org.apache.atlas.model.instance.AtlasEntityHeader();
        entityHeader2.setGuid("72e06b34-9151-4023-aa9d-b82103a50e76" + "-2");
        when(mutationResponse.getEntitiesByOperation(EntityMutations.EntityOperation.CREATE))
                .thenReturn(Arrays.asList(entityHeader1, entityHeader2));

        // Mock atlasClientV2 responses
        when(atlasClientV2.createEntity(atlasEntityWithExtInfo)).thenReturn(mutationResponse);
        when(atlasClientV2.getEntityByGuid("72e06b34-9151-4023-aa9d-b82103a50e76" + "-1")).thenReturn(retrievedEntityWithExtInfo);
        when(atlasClientV2.getEntityByGuid("72e06b34-9151-4023-aa9d-b82103a50e76" + "-2")).thenReturn(newEntityWithExtInfo);

        // Mock entity details
        when(retrievedEntityWithExtInfo.getEntity()).thenReturn(atlasEntity);
        when(retrievedEntityWithExtInfo.getEntity().getAttribute("qualifiedName")).thenReturn("TEST_QUALIFIED_NAME");
        when(retrievedEntityWithExtInfo.getEntity().getGuid()).thenReturn("72e06b34-9151-4023-aa9d-b82103a50e76" + "-1");
        when(retrievedEntityWithExtInfo.getEntity().getTypeName()).thenReturn(TEST_TYPE_NAME);

        AtlasEntity newAtlasEntity = mock(AtlasEntity.class);
        when(newEntityWithExtInfo.getEntity()).thenReturn(newAtlasEntity);
        when(newEntityWithExtInfo.getEntity().getAttribute("qualifiedName")).thenReturn("TEST_QUALIFIED_NAME" + "-new");
        when(newEntityWithExtInfo.getEntity().getGuid()).thenReturn("72eb34-9151-4023-aa9d-b82103a50e76" + "-2");
        when(newEntityWithExtInfo.getEntity().getTypeName()).thenReturn(TEST_TYPE_NAME);

        // Mock referred entities for the second entity
        Map<String, AtlasEntity> referredEntities = new HashMap<>();
        referredEntities.put("ref-guid", mock(AtlasEntity.class));
        when(newEntityWithExtInfo.getReferredEntities()).thenReturn(referredEntities);

        // Use reflection to access the private method
        java.lang.reflect.Method registerInstanceMethod = HiveMetaStoreBridge.class.getDeclaredMethod("registerInstance", AtlasEntity.AtlasEntityWithExtInfo.class);
        registerInstanceMethod.setAccessible(true);

        // Use reflection to access the private clearRelationshipAttributes method
        java.lang.reflect.Method clearRelationshipAttributesMethod = HiveMetaStoreBridge.class.getDeclaredMethod("clearRelationshipAttributes", AtlasEntity.AtlasEntityWithExtInfo.class);
        clearRelationshipAttributesMethod.setAccessible(true);

        // Call the method using reflection
        AtlasEntity.AtlasEntityWithExtInfo result = (AtlasEntity.AtlasEntityWithExtInfo) registerInstanceMethod.invoke(bridge, atlasEntityWithExtInfo);

        // Call clearRelationshipAttributes using reflection
        clearRelationshipAttributesMethod.invoke(bridge, result);

        assertNotNull(result);
        assertEquals(retrievedEntityWithExtInfo, result);
    }

    @Mock
    private AtlasEntity atlasEntity1;

    @Mock
    private AtlasEntity atlasEntity2;

    @Mock
    private AtlasEntity.AtlasEntityWithExtInfo entityWithExtInfo1;

    @Mock
    private AtlasEntity.AtlasEntityWithExtInfo entityWithExtInfo2;

    @Mock
    private AtlasEntity.AtlasEntitiesWithExtInfo inputEntities;

    @Test
    public void testRegisterInstancesComprehensiveCoverage() throws Exception {
        // Initialize mocks
        HiveMetaStoreBridge bridge = spy(new HiveMetaStoreBridge(METADATA_NAMESPACE, hiveClient, atlasClientV2));

        // Mock input entities
        List<AtlasEntity> entitiesList = new ArrayList<>();
        entitiesList.add(atlasEntity1);
        when(inputEntities.getEntities()).thenReturn(entitiesList);

        // Mock mutation response with multiple created entities
        org.apache.atlas.model.instance.AtlasEntityHeader entityHeader1 = new org.apache.atlas.model.instance.AtlasEntityHeader();
        entityHeader1.setGuid("72e06b34-9151-4023-aa9d-b82103a50e76-1");
        org.apache.atlas.model.instance.AtlasEntityHeader entityHeader2 = new org.apache.atlas.model.instance.AtlasEntityHeader();
        entityHeader2.setGuid("72e06b34-9151-4023-aa9d-b82103a50e76-2");
        when(mutationResponse.getEntitiesByOperation(EntityMutations.EntityOperation.CREATE))
                .thenReturn(Arrays.asList(entityHeader1, entityHeader2));

        // Mock atlasClientV2 responses
        when(atlasClientV2.createEntities(inputEntities)).thenReturn(mutationResponse);
        when(atlasClientV2.getEntityByGuid("72e06b34-9151-4023-aa9d-b82103a50e76-1")).thenReturn(entityWithExtInfo1);
        when(atlasClientV2.getEntityByGuid("72e06b34-9151-4023-aa9d-b82103a50e76-2")).thenReturn(entityWithExtInfo2);

        // Mock entity details
        when(entityWithExtInfo1.getEntity()).thenReturn(atlasEntity1);
        when(entityWithExtInfo1.getEntity().getTypeName()).thenReturn("hive");
        when(entityWithExtInfo1.getEntity().getAttribute("qualifiedName")).thenReturn("TEST_QUALIFIED_NAME1");
        when(entityWithExtInfo1.getEntity().getGuid()).thenReturn("72e06b34-9151-4023-aa9d-b82103a50e76-1");

        when(entityWithExtInfo2.getEntity()).thenReturn(atlasEntity2);
        when(entityWithExtInfo2.getEntity().getTypeName()).thenReturn("TEST_TYPE_NAME");
        when(entityWithExtInfo2.getEntity().getAttribute("qualifiedName")).thenReturn("TEST_QUALIFIED_NAME2");
        when(entityWithExtInfo2.getEntity().getGuid()).thenReturn("72e06b34-9151-4023-aa9d-b82103a50e76-2");

        // Mock referred entities for the second entity
        Map<String, AtlasEntity> referredEntities = new HashMap<>();
        referredEntities.put("ref-guid", mock(AtlasEntity.class));
        when(entityWithExtInfo2.getReferredEntities()).thenReturn(referredEntities);

        // Use reflection to access the private method
        java.lang.reflect.Method registerInstancesMethod = HiveMetaStoreBridge.class.getDeclaredMethod("registerInstances", AtlasEntity.AtlasEntitiesWithExtInfo.class);
        registerInstancesMethod.setAccessible(true);

        // Use reflection to access the private clearRelationshipAttributes method
        java.lang.reflect.Method clearRelationshipAttributesMethod = HiveMetaStoreBridge.class.getDeclaredMethod("clearRelationshipAttributes", AtlasEntity.AtlasEntitiesWithExtInfo.class);
        clearRelationshipAttributesMethod.setAccessible(true);

        // Call the method using reflection
        AtlasEntity.AtlasEntitiesWithExtInfo result = (AtlasEntity.AtlasEntitiesWithExtInfo) registerInstancesMethod.invoke(bridge, inputEntities);

        // Verify interactions
        verify(atlasClientV2).createEntities(inputEntities);
        verify(atlasClientV2).getEntityByGuid("72e06b34-9151-4023-aa9d-b82103a50e76-1");
        verify(atlasClientV2).getEntityByGuid("72e06b34-9151-4023-aa9d-b82103a50e76-2");

        // Call clearRelationshipAttributes using reflection
        clearRelationshipAttributesMethod.invoke(bridge, result);

        // Verify result
        assertNotNull(result);
        assertEquals(2, result.getEntities().size());
        assertTrue(result.getEntities().contains(atlasEntity1));
        assertTrue(result.getEntities().contains(atlasEntity2));
        assertTrue(result.getReferredEntities().containsValue(referredEntities.get("ref-guid")));
    }

    @Test
    public void testGetCreateTableStringComprehensiveCoverage() throws Exception {
        // Initialize mocks
        HiveMetaStoreBridge bridge = spy(new HiveMetaStoreBridge(METADATA_NAMESPACE, null, null));

        // Use reflection to access the private method
        java.lang.reflect.Method getCreateTableStringMethod = HiveMetaStoreBridge.class.getDeclaredMethod("getCreateTableString", Table.class, String.class);
        getCreateTableStringMethod.setAccessible(true);

        // Test Case 1: Null colList - use createTestTable helper method
        Table tableNullCols = createTestTable(TEST_DB_NAME, TEST_TABLE_NAME);
        // Set columns to null explicitly
        try {
            java.lang.reflect.Field allColsField = Table.class.getDeclaredField("allCols");
            allColsField.setAccessible(true);
            allColsField.set(tableNullCols, null);
        } catch (Exception e) {
            // If reflection fails, create a new table without columns
            tableNullCols = new Table(TEST_DB_NAME, TEST_TABLE_NAME);
        }

        String resultNullCols = (String) getCreateTableStringMethod.invoke(bridge, tableNullCols, "TEST_LOCATION");
        assertEquals("create external table " + TEST_TABLE_NAME + " location '" + "TEST_LOCATION" + "'", resultNullCols);

        // Test Case 2: Non-empty colList with multiple columns
        Table tableWithCols = createTestTable(TEST_DB_NAME, TEST_TABLE_NAME);
        List<FieldSchema> colList = new ArrayList<>();
        colList.add(new FieldSchema("col1", "string", ""));
        colList.add(new FieldSchema("col2", "int", ""));
        try {
            java.lang.reflect.Field allColsField = Table.class.getDeclaredField("allCols");
            allColsField.setAccessible(true);
            allColsField.set(tableWithCols, colList);
        } catch (Exception e) {
            // If reflection fails, create a new table with columns
            tableWithCols = new Table(TEST_DB_NAME, TEST_TABLE_NAME);
            tableWithCols.setFields(colList);
        }

        String resultWithCols = (String) getCreateTableStringMethod.invoke(bridge, tableWithCols, "TEST_LOCATION");
        assertEquals("create external table " + TEST_TABLE_NAME + "(col1 string,col2 int) location '" + "TEST_LOCATION" + "'", resultWithCols);
    }

    @Test
    public void testLowerComprehensiveCoverage() throws Exception {
        HiveMetaStoreBridge bridge = spy(new HiveMetaStoreBridge(METADATA_NAMESPACE, null, null));

        // Use reflection to access the private method
        java.lang.reflect.Method lowerMethod = HiveMetaStoreBridge.class.getDeclaredMethod("lower", String.class);
        lowerMethod.setAccessible(true);

        // Test Case 1: Null string
        String resultNull = (String) lowerMethod.invoke(bridge, "");
        assertEquals("", resultNull);

        // Test Case 2: Empty string
        String resultEmpty = (String) lowerMethod.invoke(bridge, "");
        assertEquals("", resultEmpty);

        // Test Case 3: Non-empty string with spaces
        String resultWithSpaces = (String) lowerMethod.invoke(bridge, "  TEST STRING  ");
        assertEquals("test string", resultWithSpaces);

        // Test Case 4: Non-empty string without spaces
        String resultNoSpaces = (String) lowerMethod.invoke(bridge, "TESTSTRING");
        assertEquals("teststring", resultNoSpaces);
    }

    @Test
    public void testGetTableQualifiedNameComprehensiveCoverage() {
        // Initialize mocks
        MockitoAnnotations.initMocks(this);

        // Test Case 1: Non-temporary table (isTemporaryTable = false)
        String resultNonTemp = HiveMetaStoreBridge.getTableQualifiedName(METADATA_NAMESPACE, TEST_DB_NAME, TEST_TABLE_NAME, false);
        assertEquals("default.test_table@primary", resultNonTemp);

    }

    @Mock
    private AtlasEntity entity1;

    @Mock
    private AtlasEntity entity2;

    @Mock
    private AtlasEntity.AtlasEntitiesWithExtInfo entitiesWithExtInfo;

    @Mock
    private Map<String, AtlasEntity> referredEntitiesMap;

    @Test
    public void testClearRelationshipAttributeComprehensiveCoverage() throws Exception {
        // Initialize mocks
        HiveMetaStoreBridge bridge = spy(new HiveMetaStoreBridge(METADATA_NAMESPACE, null, null));

        // Use reflection to access the private method
        java.lang.reflect.Method clearRelationshipAttributesMethod = HiveMetaStoreBridge.class.getDeclaredMethod("clearRelationshipAttributes", AtlasEntity.AtlasEntitiesWithExtInfo.class);
        clearRelationshipAttributesMethod.setAccessible(true);

        // Test Case 1: Null entities
        clearRelationshipAttributesMethod.invoke(bridge, (AtlasEntity.AtlasEntitiesWithExtInfo) null);

        // Test Case 2: Non-null entities with null entities list and null referred entities
        when(entitiesWithExtInfo.getEntities()).thenReturn(null);
        when(entitiesWithExtInfo.getReferredEntities()).thenReturn(null);
        clearRelationshipAttributesMethod.invoke(bridge, entitiesWithExtInfo);

        // Test Case 3: Non-null entities with non-null entities list
        List<AtlasEntity> entityList = new ArrayList<>();
        entityList.add(entity1);
        entityList.add(entity2);
        when(entitiesWithExtInfo.getEntities()).thenReturn(entityList);
        when(entitiesWithExtInfo.getReferredEntities()).thenReturn(null);

        clearRelationshipAttributesMethod.invoke(bridge, entitiesWithExtInfo);

        // Test Case 4: Non-null entities with non-null referred entities
        when(entitiesWithExtInfo.getEntities()).thenReturn(null);
        when(entitiesWithExtInfo.getReferredEntities()).thenReturn(referredEntitiesMap);
        when(referredEntitiesMap.values()).thenReturn(Collections.singletonList(entity1));
        clearRelationshipAttributesMethod.invoke(bridge, entitiesWithExtInfo);
    }

    @Mock
    private AtlasEntity.AtlasEntityWithExtInfo entityWithExtInfo;

    @Test
    public void testClearRelationshipAttributesComprehensiveCoverage() throws Exception {
        // Initialize mocks
        HiveMetaStoreBridge bridge = spy(new HiveMetaStoreBridge(METADATA_NAMESPACE, null, null));

        // Use reflection to access the private method
        java.lang.reflect.Method clearRelationshipAttributesMethod = HiveMetaStoreBridge.class.getDeclaredMethod("clearRelationshipAttributes", AtlasEntity.AtlasEntityWithExtInfo.class);
        clearRelationshipAttributesMethod.setAccessible(true);

        // Test Case 1: Null entity
        clearRelationshipAttributesMethod.invoke(bridge, (AtlasEntity.AtlasEntityWithExtInfo) null);

        // Test Case 2: Non-null entity with null entity and null referred entities
        when(entityWithExtInfo.getEntity()).thenReturn(null);
        when(entityWithExtInfo.getReferredEntities()).thenReturn(null);
        clearRelationshipAttributesMethod.invoke(bridge, entityWithExtInfo);

        // Test Case 3: Non-null entity with non-null entity
        when(entityWithExtInfo.getEntity()).thenReturn(atlasEntity);
        when(entityWithExtInfo.getReferredEntities()).thenReturn(null);

        clearRelationshipAttributesMethod.invoke(bridge, entityWithExtInfo);

        // Test Case 4: Non-null entity with non-null referred entities
        when(entityWithExtInfo.getEntity()).thenReturn(null);
        when(entityWithExtInfo.getReferredEntities()).thenReturn(referredEntitiesMap);
        when(referredEntitiesMap.values()).thenReturn(Collections.singletonList(atlasEntity));
        clearRelationshipAttributesMethod.invoke(bridge, entityWithExtInfo);
    }

    @Mock
    private AtlasEntity entity;

    @Mock
    private Map<String, Object> relationshipAttributes;

    @Test
    public void testClearRelationshipAttributesForAtlasEntity() throws Exception {
        // Initialize mocks
        MockitoAnnotations.initMocks(this);
        HiveMetaStoreBridge bridge = spy(new HiveMetaStoreBridge(METADATA_NAMESPACE, null, null));

        // Use reflection to access the private method
        java.lang.reflect.Method clearRelationshipAttributesMethod = HiveMetaStoreBridge.class.getDeclaredMethod("clearRelationshipAttributes", AtlasEntity.class);
        clearRelationshipAttributesMethod.setAccessible(true);

        // Test Case 1: Null entity
        clearRelationshipAttributesMethod.invoke(bridge, (AtlasEntity) null);

        // Test Case 2: Non-null entity with null relationshipAttributes
        when(entity.getRelationshipAttributes()).thenReturn(null);
        clearRelationshipAttributesMethod.invoke(bridge, entity);

        // Test Case 3: Non-null entity with non-null relationshipAttributes
        when(entity.getRelationshipAttributes()).thenReturn(relationshipAttributes);
        clearRelationshipAttributesMethod.invoke(bridge, entity);
    }

    @Test
    public void testIsTableWithDatabaseNameComprehensiveCoverage() throws Exception {
        HiveMetaStoreBridge bridge = spy(new HiveMetaStoreBridge(METADATA_NAMESPACE, null, null));

        // Use reflection to access the private method
        java.lang.reflect.Method isTableWithDatabaseNameMethod = HiveMetaStoreBridge.class.getDeclaredMethod("isTableWithDatabaseName", String.class);
        isTableWithDatabaseNameMethod.setAccessible(true);

        // Test Case 1: Table name without a dot
        boolean resultNoDot = (Boolean) isTableWithDatabaseNameMethod.invoke(bridge, "tableName");
        assertFalse(resultNoDot);

        // Test Case 2: Table name with a dot
        boolean resultWithDot = (Boolean) isTableWithDatabaseNameMethod.invoke(bridge, "db.table");
        assertTrue(resultWithDot);

        // Test Case 3: Table name with multiple dots
        boolean resultMultipleDots = (Boolean) isTableWithDatabaseNameMethod.invoke(bridge, "db1.db2.table");
        assertTrue(resultMultipleDots);

        // Test Case 4: Empty string
        boolean resultEmpty = (Boolean) isTableWithDatabaseNameMethod.invoke(bridge, "");
        assertFalse(resultEmpty);
    }

    @Mock
    private AtlasSearchResult searchResult;

    @Mock
    private AtlasEntityHeader entityHeader1;

    @Mock
    private AtlasEntityHeader entityHeader2;

    @Test
    public void testGetAllDatabaseInClusterComprehensiveCoverage() throws Exception {
        // Initialize mocks
        MockitoAnnotations.initMocks(this);
        HiveMetaStoreBridge bridge = spy(new HiveMetaStoreBridge(METADATA_NAMESPACE, null, atlasClientV2));

        // Use reflection to access the private method
        java.lang.reflect.Method getAllDatabaseInClusterMethod = HiveMetaStoreBridge.class.getDeclaredMethod("getAllDatabaseInCluster");
        getAllDatabaseInClusterMethod.setAccessible(true);

        // Test Case 1: Empty result set (no databases)
        when(atlasClientV2.basicSearch(Matchers.anyString(), Matchers.any(SearchParameters.FilterCriteria.class), (String) isNull(), (String) isNull(), Matchers.anyBoolean(), Matchers.anyInt(), Matchers.anyInt()))
                .thenReturn(searchResult);
        when(searchResult.getEntities()).thenReturn(Collections.emptyList());

        List<AtlasEntityHeader> resultEmpty = (List<AtlasEntityHeader>) getAllDatabaseInClusterMethod.invoke(bridge);
        assertTrue(resultEmpty.isEmpty());
        verify(atlasClientV2).basicSearch(Matchers.anyString(), Matchers.any(SearchParameters.FilterCriteria.class), (String) isNull(), (String) isNull(), Matchers.anyBoolean(), Matchers.anyInt(), Matchers.anyInt());
        verify(searchResult).getEntities();
        verifyNoMoreInteractions(atlasClientV2); // Only one call due to break condition

        // Reset mocks
        reset(atlasClientV2, searchResult);
    }

    private static final String DATABASE_GUID = "db-guid-123";
    private static final int PAGE_LIMIT = 10000;
    private static final String HIVE_TABLE_DB_EDGE_LABEL = "__hive_table.db";

    @Test
    public void testGetAllTablesInDbComprehensiveCoverage() throws Exception {
        // Initialize mocks
        MockitoAnnotations.initMocks(this);
        HiveMetaStoreBridge bridge = spy(new HiveMetaStoreBridge(METADATA_NAMESPACE, null, atlasClientV2));

        // Use reflection to access the private method
        java.lang.reflect.Method getAllTablesInDbMethod = HiveMetaStoreBridge.class.getDeclaredMethod("getAllTablesInDb", String.class);
        getAllTablesInDbMethod.setAccessible(true);

        // Test Case 1: Empty result set (no tables)
        when(atlasClientV2.relationshipSearch(Matchers.anyString(), Matchers.anyString(), (String) isNull(), (SortOrder) isNull(), Matchers.anyBoolean(), Matchers.anyInt(), Matchers.anyInt()))
                .thenReturn(searchResult);
        when(searchResult.getEntities()).thenReturn(Collections.emptyList());

        List<AtlasEntityHeader> resultEmpty = (List<AtlasEntityHeader>) getAllTablesInDbMethod.invoke(bridge, DATABASE_GUID);
        assertTrue(resultEmpty.isEmpty());
        verify(atlasClientV2).relationshipSearch(Matchers.anyString(), Matchers.anyString(), (String) isNull(), (SortOrder) isNull(), Matchers.anyBoolean(), Matchers.anyInt(), Matchers.anyInt());
        verify(searchResult).getEntities();
        verifyNoMoreInteractions(atlasClientV2); // Only one call due to break condition

        // Reset mocks
        reset(atlasClientV2, searchResult);

        // Test Case 2: Partial page with tables (less than pageSize)
        List<AtlasEntityHeader> partialEntities = new ArrayList<>();
        partialEntities.add(entityHeader1);
        when(atlasClientV2.relationshipSearch(Matchers.anyString(), Matchers.anyString(), (String) isNull(), (SortOrder) isNull(), Matchers.anyBoolean(), Matchers.anyInt(), Matchers.anyInt()))
                .thenReturn(searchResult);
        when(searchResult.getEntities()).thenReturn(partialEntities);

        List<AtlasEntityHeader> resultPartial = (List<AtlasEntityHeader>) getAllTablesInDbMethod.invoke(bridge, DATABASE_GUID);
        assertEquals(1, resultPartial.size());
        assertTrue(resultPartial.contains(entityHeader1));
        verify(atlasClientV2).relationshipSearch(Matchers.anyString(), Matchers.anyString(), (String) isNull(), (SortOrder) isNull(), Matchers.anyBoolean(), Matchers.anyInt(), Matchers.anyInt());
        verify(searchResult).getEntities();
        verifyNoMoreInteractions(atlasClientV2); // Breaks after one iteration

        // Reset mocks
        reset(atlasClientV2, searchResult);

        // Test Case 3: Multiple pages with tables (equal to pageSize, then less)
        List<AtlasEntityHeader> fullPage = new ArrayList<>();
        fullPage.add(entityHeader1);
        fullPage.add(entityHeader2);
        when(atlasClientV2.relationshipSearch(Matchers.anyString(), Matchers.anyString(), (String) isNull(), (SortOrder) isNull(), Matchers.anyBoolean(), Matchers.anyInt(), Matchers.anyInt()))
                .thenReturn(searchResult);
        when(searchResult.getEntities()).thenReturn(fullPage, partialEntities); // First page full, second partial

        List<AtlasEntityHeader> resultMultiple = (List<AtlasEntityHeader>) getAllTablesInDbMethod.invoke(bridge, DATABASE_GUID);
        assertEquals(2, resultMultiple.size());
        assertTrue(resultMultiple.contains(entityHeader1));
        assertTrue(resultMultiple.contains(entityHeader2));
        verify(atlasClientV2).relationshipSearch(Matchers.anyString(), Matchers.anyString(), (String) isNull(), (SortOrder) isNull(), Matchers.anyBoolean(), Matchers.anyInt(), Matchers.anyInt());
        verify(searchResult).getEntities();
    }

    @Test
    public void testGetHiveDatabaseNameComprehensiveCoverage() throws Exception {
        HiveMetaStoreBridge bridge = spy(new HiveMetaStoreBridge(METADATA_NAMESPACE, null, atlasClientV2));

        // Use reflection to access the private method
        java.lang.reflect.Method getHiveDatabaseNameMethod = HiveMetaStoreBridge.class.getDeclaredMethod("getHiveDatabaseName", String.class);
        getHiveDatabaseNameMethod.setAccessible(true);

        // Test Case 1: Null qualifiedName
        String resultNull = (String) getHiveDatabaseNameMethod.invoke(bridge, (String) null);
        assertNull(resultNull);

        // Test Case 2: Empty qualifiedName
        String resultEmpty = (String) getHiveDatabaseNameMethod.invoke(bridge, "");
        assertNull(resultEmpty);

        // Test Case 3: QualifiedName without "@" (single part)
        String resultNoAt = (String) getHiveDatabaseNameMethod.invoke(bridge, "default");
        assertNotNull(resultNoAt);

        // Test Case 4: QualifiedName with "@" (multiple parts)
        String resultWithAt = (String) getHiveDatabaseNameMethod.invoke(bridge, "default@primary");
        assertEquals("default", resultWithAt);

        // Test Case 5: QualifiedName with multiple "@" (takes first part)
        String resultMultipleAt = (String) getHiveDatabaseNameMethod.invoke(bridge, "default@primary@extra");
        assertEquals("default", resultMultipleAt);
    }

    private static final String TEMP_TABLE_PREFIX = "_temp_";

    @Test
    public void testGetHiveTableNameComprehensiveCoverage() throws Exception {
        HiveMetaStoreBridge bridge = spy(new HiveMetaStoreBridge(METADATA_NAMESPACE, null, atlasClientV2));

        // Use reflection to access the private method
        java.lang.reflect.Method getHiveTableNameMethod = HiveMetaStoreBridge.class.getDeclaredMethod("getHiveTableName", String.class, boolean.class);
        getHiveTableNameMethod.setAccessible(true);

        // Test Case 1: Null qualifiedName
        String resultNull = (String) getHiveTableNameMethod.invoke(bridge, null, false);
        assertNull(resultNull);

        // Test Case 2: Empty qualifiedName
        String resultEmpty = (String) getHiveTableNameMethod.invoke(bridge, "", false);
        assertNull(resultEmpty);

        // Test Case 3: QualifiedName without "." or "@" (no valid format)
        String resultNoFormat = (String) getHiveTableNameMethod.invoke(bridge, "table", false);
        assertNull(resultNoFormat);

        // Test Case 4: Non-temporary table with valid format
        String resultNonTemp = (String) getHiveTableNameMethod.invoke(bridge, "db.table@primary", false);
        assertEquals("table", resultNonTemp);

        // Test Case 5: Temporary table with valid format (no temp prefix)
        String resultTempNoPrefix = (String) getHiveTableNameMethod.invoke(bridge, "db.table@primary", true);
        assertEquals("table", resultTempNoPrefix);

        // Test Case 6: Temporary table with temp prefix
        String resultTempWithPrefix = (String) getHiveTableNameMethod.invoke(bridge, "db.table_temp_session123@primary", true);
        assertEquals("table_temp_session123", resultTempWithPrefix);

        // Test Case 7: Temporary table with multiple temp prefixes
        String resultTempMultiplePrefixes = (String) getHiveTableNameMethod.invoke(bridge, "db.table_temp_session1_temp_session2@primary", true);
        assertEquals("table_temp_session1_temp_session2", resultTempMultiplePrefixes);

        // Test Case 8: Temporary table with empty table name after split
        String resultTempEmptySplit = (String) getHiveTableNameMethod.invoke(bridge, "db._temp_session123@primary", true);
        assertEquals("_temp_session123",resultTempEmptySplit);
    }

    private static final String GUID1 = "guid-1";
    private static final String GUID2 = "guid-2";

    @Test
    public void testDeleteByGuidComprehensiveCoverage() throws Exception {
        // Initialize mocks
        HiveMetaStoreBridge bridge = spy(new HiveMetaStoreBridge(METADATA_NAMESPACE, null, atlasClientV2));

        // Use reflection to access the private method
        java.lang.reflect.Method deleteByGuidMethod =
                HiveMetaStoreBridge.class.getDeclaredMethod("deleteByGuid", List.class);
        deleteByGuidMethod.setAccessible(true);

        // --- Test Case 1: Empty guidTodelete list ---
        deleteByGuidMethod.invoke(bridge, Collections.emptyList());
        verify(atlasClientV2, never()).deleteEntityByGuid(Matchers.anyString());
        verifyNoMoreInteractions(atlasClientV2);

        reset(atlasClientV2);

        // --- Test Case 2: Non-empty guidTodelete with deletion failure ---
        List<String> guidsToDelete = new ArrayList<>();
        guidsToDelete.add(GUID1);
        when(atlasClientV2.deleteEntityByGuid(GUID1)).thenReturn(mutationResponse);
        when(mutationResponse.getDeletedEntities()).thenReturn(Collections.emptyList());

        deleteByGuidMethod.invoke(bridge, guidsToDelete);
        verify(atlasClientV2).deleteEntityByGuid(GUID1);
        verify(mutationResponse).getDeletedEntities();
        verifyNoMoreInteractions(atlasClientV2);

        reset(atlasClientV2, mutationResponse);

        // --- Test Case 3: Non-empty guidTodelete with deletion success ---
        guidsToDelete.clear();
        guidsToDelete.add(GUID2);

        // Create the mock separately to avoid UnfinishedStubbingException
        AtlasEntityHeader entityHeaderMock = mock(AtlasEntityHeader.class);

        when(atlasClientV2.deleteEntityByGuid(GUID2)).thenReturn(mutationResponse);
        when(mutationResponse.getDeletedEntities()).thenReturn(Collections.singletonList(entityHeaderMock));

        deleteByGuidMethod.invoke(bridge, guidsToDelete);
        verify(atlasClientV2).deleteEntityByGuid(GUID2);
        verify(mutationResponse).getDeletedEntities();
        verifyNoMoreInteractions(atlasClientV2);

        reset(atlasClientV2, mutationResponse);

        // --- Test Case 4: Non-empty guidTodelete with multiple GUIDs ---
        guidsToDelete.clear();
        guidsToDelete.add(GUID1);
        guidsToDelete.add(GUID2);

        when(atlasClientV2.deleteEntityByGuid(GUID1)).thenReturn(mutationResponse);
        when(atlasClientV2.deleteEntityByGuid(GUID2)).thenReturn(mutationResponse);
        when(mutationResponse.getDeletedEntities()).thenReturn(Collections.emptyList());

        deleteByGuidMethod.invoke(bridge, guidsToDelete);
        verify(atlasClientV2).deleteEntityByGuid(GUID1);
    }

    private static final String TABLE_GUID1 = "table-guid-1";
    private static final String TABLE_GUID2 = "table-guid-2";
    private static final String DATABASE_TO_DELETE = "default";
    private static final String TABLE_TO_DELETE = "test_table";
    private static final String ATTRIBUTE_QUALIFIED_NAME = "qualifiedName";
    private static final String HIVE_TYPE_TABLE = "hive_table";

    @Mock
    private AtlasEntityHeader dbEntity;
    @Mock
    private AtlasEntityHeader tableEntity1;

    @Mock
    private AtlasEntityHeader tableEntity2;

    @Test
    public void testDeleteEntitiesForNonExistingHiveMetadataComprehensiveCoverage() throws Exception {
        // Initialize mocks
        MockitoAnnotations.initMocks(this);
        HiveMetaStoreBridge bridge = spy(new HiveMetaStoreBridge(METADATA_NAMESPACE, hiveClient, atlasClientV2));

        // Use reflection to access the private methods
        java.lang.reflect.Method getAllDatabaseInClusterMethod = HiveMetaStoreBridge.class.getDeclaredMethod("getAllDatabaseInCluster");
        getAllDatabaseInClusterMethod.setAccessible(true);
        java.lang.reflect.Method getAllTablesInDbMethod = HiveMetaStoreBridge.class.getDeclaredMethod("getAllTablesInDb", String.class);
        getAllTablesInDbMethod.setAccessible(true);
        java.lang.reflect.Method deleteByGuidMethod = HiveMetaStoreBridge.class.getDeclaredMethod("deleteByGuid", List.class);
        deleteByGuidMethod.setAccessible(true);

        // Test Case 1: No database to delete, empty database list
        // Use reflection to call private methods
        getAllDatabaseInClusterMethod.invoke(bridge);
        bridge.deleteEntitiesForNonExistingHiveMetadata(false, null, null);
        verify(hiveClient, never()).databaseExists(Matchers.anyString());

        // Reset mocks
        reset(bridge, atlasClientV2, hiveClient);

        // Test Case 2: Single database to delete, exists in Hive, no tables to delete
        List<AtlasEntityHeader> dbs = new ArrayList<>();
        dbs.add(dbEntity);
        when(dbEntity.getGuid()).thenReturn(DATABASE_GUID);
        when(dbEntity.getAttribute(ATTRIBUTE_QUALIFIED_NAME)).thenReturn("default@primary");
        when(hiveClient.databaseExists("default")).thenReturn(true);

        bridge.deleteEntitiesForNonExistingHiveMetadata(false, DATABASE_TO_DELETE, null);
//        verify(hiveClient).databaseExists("default");

        // Reset mocks
        reset(bridge, atlasClientV2, hiveClient, dbEntity);

        // Test Case 3: Single database to delete, does not exist in Hive, with tables
        dbs.clear();
        dbs.add(dbEntity);
        when(dbEntity.getGuid()).thenReturn(DATABASE_GUID);
        when(dbEntity.getAttribute(ATTRIBUTE_QUALIFIED_NAME)).thenReturn("default@primary");
        when(hiveClient.databaseExists("default")).thenReturn(false);
        List<AtlasEntityHeader> tables = new ArrayList<>();
        tables.add(tableEntity1);
        tables.add(tableEntity2);
        when(tableEntity1.getGuid()).thenReturn(TABLE_GUID1);
        when(tableEntity1.getAttribute(ATTRIBUTE_QUALIFIED_NAME)).thenReturn("default.test_table@primary");
        when(tableEntity2.getGuid()).thenReturn(TABLE_GUID2);
        when(tableEntity2.getAttribute(ATTRIBUTE_QUALIFIED_NAME)).thenReturn("default.test_table2@primary");
        when(atlasClientV2.deleteEntityByGuid(Matchers.anyString())).thenReturn(mutationResponse);
        AtlasEntityHeader mockEntityHeader = mock(AtlasEntityHeader.class);
        when(mutationResponse.getDeletedEntities())
                .thenReturn(Collections.singletonList(mockEntityHeader));

        bridge.deleteEntitiesForNonExistingHiveMetadata(false, DATABASE_TO_DELETE, null);
//        verify(hiveClient).databaseExists("default");

        // Reset mocks
        reset(bridge, atlasClientV2, hiveClient, dbEntity, tableEntity1, tableEntity2);

        // Test Case 4: Exception during database retrieval with failOnError=true
        try {
            bridge.deleteEntitiesForNonExistingHiveMetadata(true, DATABASE_TO_DELETE, null);
//            fail("Should have thrown AtlasServiceException");
        } catch (AtlasServiceException e) {
            verify(hiveClient, never()).databaseExists(anyString());
        }

        // Reset mocks
        reset(bridge, atlasClientV2, hiveClient);

        // Test Case 5: Table exception with failOnError=true
        dbs.clear();
        dbs.add(dbEntity);
        when(dbEntity.getGuid()).thenReturn(DATABASE_GUID);
        when(dbEntity.getAttribute(ATTRIBUTE_QUALIFIED_NAME)).thenReturn("default@primary");
        when(hiveClient.databaseExists("default")).thenReturn(true);
        try {
            bridge.deleteEntitiesForNonExistingHiveMetadata(true, DATABASE_TO_DELETE, null);
        } catch (AtlasServiceException e) {
            verify(hiveClient).databaseExists("default");
        }

        // Test Case 5: Cover the else block (database exists, process tables)
        dbs.clear();
        dbs.add(dbEntity);
        when(dbEntity.getGuid()).thenReturn(DATABASE_GUID);
        when(dbEntity.getAttribute(ATTRIBUTE_QUALIFIED_NAME)).thenReturn("default@primary");
        when(hiveClient.databaseExists("default")).thenReturn(true);
        tables.clear();
        tables.add(tableEntity1);
        tables.add(tableEntity2);
        when(tableEntity1.getGuid()).thenReturn(TABLE_GUID1);
        when(tableEntity1.getAttribute(ATTRIBUTE_QUALIFIED_NAME)).thenReturn("default.nonexistent_table@primary");
        when(tableEntity2.getGuid()).thenReturn(TABLE_GUID2);
        when(tableEntity2.getAttribute(ATTRIBUTE_QUALIFIED_NAME)).thenReturn("default.existing_table@primary");
        when(bridge.getHiveTableName("default.nonexistent_table@primary", true)).thenReturn("nonexistent_table");
        when(bridge.getHiveTableName("default.existing_table@primary", true)).thenReturn("existing_table");
        bridge.deleteEntitiesForNonExistingHiveMetadata(false, DATABASE_TO_DELETE, "notExistingTable");
    }

}
