// 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.cloudstack.storage.datastore.db;

import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.isNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import java.io.IOException;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDaoImpl.ValueType;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner;

import com.cloud.storage.ScopeType;
import com.cloud.storage.dao.StoragePoolHostDao;
import com.cloud.storage.dao.StoragePoolTagsDao;
import com.cloud.utils.db.GenericSearchBuilder;
import com.cloud.utils.db.SearchBuilder;

import junit.framework.TestCase;

@RunWith(MockitoJUnitRunner.class)
public class PrimaryDataStoreDaoImplTest extends TestCase {

    @Mock
    StoragePoolDetailsDao _detailsDao;
    @Mock
    StoragePoolHostDao _hostDao;
    @Mock
    StoragePoolTagsDao _tagsDao;

    @Spy
    @InjectMocks
    private static PrimaryDataStoreDaoImpl primaryDataStoreDao = new PrimaryDataStoreDaoImpl();

    @Mock
    StoragePoolVO storagePoolVO;

    private GenericSearchBuilder<StoragePoolVO, Long> genericSearchBuilder;

    private static final String STORAGE_TAG_1 = "NFS-A";
    private static final String STORAGE_TAG_2 = "NFS-B";
    private static final String[] STORAGE_TAGS_ARRAY = {STORAGE_TAG_1, STORAGE_TAG_2};

    private static final String DETAIL_KEY = "storage.overprovisioning.factor";
    private static final String DETAIL_VALUE = "2.0";
    private static final Map<String, String> STORAGE_POOL_DETAILS = new HashMap<>();

    private static final String EXPECTED_RESULT_SQL_STORAGE_TAGS = "(storage_pool_tags.tag='" + STORAGE_TAG_1 + "') OR (storage_pool_tags.tag='" + STORAGE_TAG_2 + "')";
    private static final String EXPECTED_RESULT_SQL_DETAILS = "((storage_pool_details.name='" + DETAIL_KEY + "') AND (storage_pool_details.value='" + DETAIL_VALUE +"'))";

    private static final String SQL_PREFIX = "XXXXXXXXXXXXXXXX";
    private static final String SQL_SUFFIX = "ZZZZZZZZZZZZZZZZ";
    private static final String SQL_VALUES = "YYYYYYYYYYYYYYYY";

    private static final Long DATACENTER_ID = 1l;
    private static final Long POD_ID = 1l;
    private static final Long CLUSTER_ID = null;
    private static final ScopeType SCOPE = ScopeType.ZONE;


    @Before
    public void setup() throws IOException, ClassNotFoundException, SQLException {
        STORAGE_POOL_DETAILS.put(DETAIL_KEY, DETAIL_VALUE);
    }

    @Test
    public void testGetSqlValuesFromStorageTagsNotNullStorageTags() {
        assertEquals(EXPECTED_RESULT_SQL_STORAGE_TAGS, primaryDataStoreDao.getSqlValuesFromStorageTags(STORAGE_TAGS_ARRAY));
    }

    @Test(expected=NullPointerException.class)
    public void testGetSqlValuesFromStorageTagsNullStorageTags() {
        primaryDataStoreDao.getSqlValuesFromStorageTags(null);
    }

    @Test(expected=IndexOutOfBoundsException.class)
    public void testGetSqlValuesFromStorageTagsEmptyStorageTags() {
        String[] emptyStorageTags = {};
        primaryDataStoreDao.getSqlValuesFromStorageTags(emptyStorageTags);
    }

    @Test
    public void testGetSqlValuesFromDetailsNotNullDetails() {
        assertEquals(EXPECTED_RESULT_SQL_DETAILS, primaryDataStoreDao.getSqlValuesFromDetails(STORAGE_POOL_DETAILS));
    }

    @Test(expected=NullPointerException.class)
    public void testGetSqlValuesFromDetailsNullDetails() {
        primaryDataStoreDao.getSqlValuesFromDetails(null);
    }

    @Test(expected=IndexOutOfBoundsException.class)
    public void testGetSqlValuesFromDetailsEmptyDetailss() {
        Map<String,String> emptyDetails = new HashMap<String, String>();
        primaryDataStoreDao.getSqlValuesFromDetails(emptyDetails);
    }

    @Test
    public void testGetSqlPreparedStatementNullClusterId() {
        String sqlPreparedStatement = primaryDataStoreDao.getSqlPreparedStatement(SQL_PREFIX, SQL_SUFFIX, SQL_VALUES, null);
        assertEquals(SQL_PREFIX + SQL_VALUES + SQL_SUFFIX, sqlPreparedStatement);
    }

    @Test
    public void testGetSqlPreparedStatementNotNullClusterId() {
        String clusterSql = "storage_pool.cluster_id = ? OR storage_pool.cluster_id IS NULL) AND (";
        String sqlPreparedStatement = primaryDataStoreDao.getSqlPreparedStatement(SQL_PREFIX, SQL_SUFFIX, SQL_VALUES, 1l);
        assertEquals(SQL_PREFIX + clusterSql + SQL_VALUES + SQL_SUFFIX, sqlPreparedStatement);
    }

    @Test
    public void testFindPoolsByDetailsOrTagsInternalStorageTagsType() {
        doReturn(Arrays.asList(storagePoolVO)).when(primaryDataStoreDao).
                searchStoragePoolsPreparedStatement(nullable(String.class), nullable(Long.class), nullable(Long.class), nullable(Long.class),
                        nullable(ScopeType.class), nullable(Integer.class));
        List<StoragePoolVO> storagePools = primaryDataStoreDao.findPoolsByDetailsOrTagsInternal(DATACENTER_ID, POD_ID, CLUSTER_ID, SCOPE, SQL_VALUES, ValueType.TAGS, STORAGE_TAGS_ARRAY.length);
        assertEquals(Arrays.asList(storagePoolVO), storagePools);
        verify(primaryDataStoreDao).getSqlPreparedStatement(
                primaryDataStoreDao.TagsSqlPrefix, primaryDataStoreDao.TagsSqlSuffix, SQL_VALUES, CLUSTER_ID);
        String expectedSql = primaryDataStoreDao.TagsSqlPrefix + SQL_VALUES + primaryDataStoreDao.TagsSqlSuffix;
        verify(primaryDataStoreDao).searchStoragePoolsPreparedStatement(expectedSql, DATACENTER_ID, POD_ID, CLUSTER_ID, SCOPE, STORAGE_TAGS_ARRAY.length);
    }

    @Test
    public void testFindPoolsByDetailsOrTagsInternalDetailsType() {
        doReturn(Arrays.asList(storagePoolVO)).when(primaryDataStoreDao).
                searchStoragePoolsPreparedStatement(nullable(String.class), nullable(Long.class), nullable(Long.class), nullable(Long.class),
                        nullable(ScopeType.class), nullable(Integer.class));
        List<StoragePoolVO> storagePools = primaryDataStoreDao.findPoolsByDetailsOrTagsInternal(DATACENTER_ID, POD_ID, CLUSTER_ID, SCOPE, SQL_VALUES, ValueType.DETAILS, STORAGE_POOL_DETAILS.size());
        assertEquals(Arrays.asList(storagePoolVO), storagePools);
        verify(primaryDataStoreDao).getSqlPreparedStatement(
                primaryDataStoreDao.DetailsSqlPrefix, primaryDataStoreDao.DetailsSqlSuffix, SQL_VALUES, CLUSTER_ID);
        String expectedSql = primaryDataStoreDao.DetailsSqlPrefix + SQL_VALUES + primaryDataStoreDao.DetailsSqlSuffix;
        verify(primaryDataStoreDao).searchStoragePoolsPreparedStatement(expectedSql, DATACENTER_ID, POD_ID, CLUSTER_ID, SCOPE, STORAGE_POOL_DETAILS.size());
    }

    @Test
    public void testListAllIds() {
        GenericSearchBuilder<StoragePoolVO, Long> genericSearchBuilder = mock(SearchBuilder.class);
        StoragePoolVO entityVO = mock(StoragePoolVO.class);
        when(genericSearchBuilder.entity()).thenReturn(entityVO);
        doReturn(genericSearchBuilder).when(primaryDataStoreDao).createSearchBuilder(Long.class);
        List<Long> mockIds = Arrays.asList(1L, 2L, 3L);
        doReturn(mockIds).when(primaryDataStoreDao).customSearch(any(), isNull());
        List<Long> result = primaryDataStoreDao.listAllIds();
        verify(primaryDataStoreDao).customSearch(genericSearchBuilder.create(), null);
        assertEquals(3, result.size());
        assertEquals(Long.valueOf(1L), result.get(0));
        assertEquals(Long.valueOf(2L), result.get(1));
        assertEquals(Long.valueOf(3L), result.get(2));
    }

    @Test
    public void testListAllIdsEmptyResult() {
        GenericSearchBuilder<StoragePoolVO, Long> genericSearchBuilder = mock(SearchBuilder.class);
        StoragePoolVO entityVO = mock(StoragePoolVO.class);
        when(genericSearchBuilder.entity()).thenReturn(entityVO);
        doReturn(genericSearchBuilder).when(primaryDataStoreDao).createSearchBuilder(Long.class);
        doReturn(Collections.emptyList()).when(primaryDataStoreDao).customSearch(any(), isNull());
        List<Long> result = primaryDataStoreDao.listAllIds();
        verify(primaryDataStoreDao).customSearch(genericSearchBuilder.create(), null);
        assertTrue(result.isEmpty());
    }
}
