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

import com.cloud.agent.api.to.DataObjectType;
import com.cloud.storage.DataStoreRole;
import com.cloud.storage.Storage.StoragePoolType;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.VolumeDao;
import com.cloud.tags.dao.ResourceTagDao;
import com.cloud.vm.VMInstanceVO;
import com.cloud.vm.VirtualMachine.State;
import com.cloud.vm.dao.VMInstanceDao;
import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult;
import org.apache.cloudstack.engine.subsystem.api.storage.DataObject;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
import org.apache.cloudstack.framework.async.AsyncCompletionCallback;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
import org.apache.cloudstack.storage.datastore.util.StorPoolUtil;
import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
import org.apache.cloudstack.storage.to.VolumeObjectTO;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.powermock.core.classloader.annotations.PrepareForTest;

import java.util.UUID;

import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.when;
import static org.powermock.api.mockito.PowerMockito.mock;


@RunWith(MockitoJUnitRunner.class)
@PrepareForTest(StorPoolUtil.class)
public class StorPoolPrimaryDataStoreDriverTest {

    @Mock
    private VMInstanceDao vmInstanceDao;

    @Mock
    private ResourceTagDao _resourceTagDao;

    @Mock
    private AsyncCompletionCallback<CopyCommandResult> callback;
    @Mock
    private PrimaryDataStoreDao storagePool;
    @Mock
    private StoragePoolDetailsDao detailsDao;
    @Mock
    private VolumeDao volumeDao;

    DataStore srcStore;
    DataStore destStore;

    DataObject srcObj;
    DataObject destObj;

    VolumeObjectTO srcTO;
    VolumeObjectTO dstTO;

    PrimaryDataStoreTO dstPrimaryTo;
    MockedStatic<StorPoolUtil> utilities;
    StorPoolUtil.SpConnectionDesc conn;

    @Before
    public void setUp(){
        utilities = Mockito.mockStatic(StorPoolUtil.class);
        conn = new StorPoolUtil.SpConnectionDesc("1.1.1.1:81", "123", "tmp");

        srcStore = mock(DataStore.class);
        destStore = mock(DataStore.class);

        srcObj = mock(VolumeInfo.class);
        destObj = mock(VolumeInfo.class);

        srcTO = mock(VolumeObjectTO.class);
        dstTO = mock(VolumeObjectTO.class);

        dstPrimaryTo = mock(PrimaryDataStoreTO.class);
    }

    @After
    public void tearDown(){
        utilities.close();
    }
    @InjectMocks
    private StorPoolPrimaryDataStoreDriver storPoolPrimaryDataStoreDriver;

    @Test
    public void testMigrateVolumePassed(){


        VMInstanceVO vm = mock(VMInstanceVO.class);
        setReturnsWhenSourceAndDestinationAreVolumes(srcStore, destStore, srcObj, destObj, srcTO, dstTO, dstPrimaryTo, vm);
        when(vm.getState()).thenReturn(State.Running);

        when(StorPoolUtil.getSpConnection(destObj.getDataStore().getUuid(), destObj.getDataStore().getId(), detailsDao, storagePool)).thenReturn(conn);
        StorPoolUtil.SpApiResponse resp = new StorPoolUtil.SpApiResponse();
        when(StorPoolUtil.volumeUpdateTemplate("~t.t.t", conn)).thenReturn(resp);

        when(volumeDao.findById(srcObj.getId())).thenReturn(mock(VolumeVO.class));
        storPoolPrimaryDataStoreDriver.copyAsync(srcObj, destObj, callback);
        utilities.verify(() -> StorPoolUtil.volumeUpdateTemplate("~t.t.t", conn), times(1));
        Assert.assertNull(resp.getError());
    }
    @Test
    public void testMigrateVolumeNotPassed() {

        VMInstanceVO vm = mock(VMInstanceVO.class);

        setReturnsWhenSourceAndDestinationAreVolumes(srcStore, destStore, srcObj, destObj, srcTO, dstTO, dstPrimaryTo, vm);
        when(vm.getState()).thenReturn(State.Running);

        when(StorPoolUtil.getSpConnection(destObj.getDataStore().getUuid(), destObj.getDataStore().getId(), detailsDao, storagePool)).thenReturn(conn);
        StorPoolUtil.SpApiResponse resp = new StorPoolUtil.SpApiResponse();
        setResponseError(resp);
        when(StorPoolUtil.volumeUpdateTemplate("~t.t.t", conn)).thenReturn(resp);
        storPoolPrimaryDataStoreDriver.copyAsync(srcObj, destObj, callback);
        Assert.assertNotNull(resp.getError());
    }

    @Test
    public void testCopyVolumeAttachedToVmPassed() {

        VMInstanceVO vm = mock(VMInstanceVO.class);

        setReturnsWhenSourceAndDestinationAreVolumes(srcStore, destStore, srcObj, destObj, srcTO, dstTO, dstPrimaryTo, vm);
        when(vm.getState()).thenReturn(State.Stopped);
        String vmUuid = UUID.randomUUID().toString();
        when(vm.getUuid()).thenReturn(vmUuid);

        when(StorPoolUtil.getSpConnection(destObj.getDataStore().getUuid(), destObj.getDataStore().getId(), detailsDao, storagePool)).thenReturn(conn);

        StorPoolUtil.SpApiResponse response = new StorPoolUtil.SpApiResponse();
        String volumeUuid = UUID.randomUUID().toString();
        when(srcObj.getUuid()).thenReturn(volumeUuid);
        when(StorPoolUtil.volumeCopy(volumeUuid, "~t.t.t", "volume", null, vmUuid, "", conn)).thenReturn(response);
        storPoolPrimaryDataStoreDriver.copyAsync(srcObj, destObj, callback);
        Assert.assertNull(response.getError());
    }

    @Test
    public void testCopyVolumeAttachedToVmNotPassed() {

        VMInstanceVO vm = mock(VMInstanceVO.class);

        setReturnsWhenSourceAndDestinationAreVolumes(srcStore, destStore, srcObj, destObj, srcTO, dstTO, dstPrimaryTo, vm);
        when(vm.getState()).thenReturn(State.Stopped);
        String vmUuid = UUID.randomUUID().toString();
        when(vm.getUuid()).thenReturn(vmUuid);

        when(StorPoolUtil.getSpConnection(destObj.getDataStore().getUuid(), destObj.getDataStore().getId(), detailsDao, storagePool)).thenReturn(conn);
        StorPoolUtil.SpApiResponse response = new StorPoolUtil.SpApiResponse();
        setResponseError(response);
        String volumeUuid = UUID.randomUUID().toString();
        when(srcObj.getUuid()).thenReturn(volumeUuid);
        when(StorPoolUtil.volumeCopy(volumeUuid, "~t.t.t", "volume", null, vmUuid, "", conn)).thenReturn(response);
        storPoolPrimaryDataStoreDriver.copyAsync(srcObj, destObj, callback);
        Assert.assertNotNull(response.getError());
    }
    @Test
    public void testCopyVolumeNotAttachedToVmNotPassed() {
        setReturnsWhenSourceAndDestinationAreVolumes(srcStore, destStore, srcObj, destObj, srcTO, dstTO, dstPrimaryTo, null);

        when(StorPoolUtil.getSpConnection(destObj.getDataStore().getUuid(), destObj.getDataStore().getId(), detailsDao, storagePool)).thenReturn(conn);
        StorPoolUtil.SpApiResponse response = new StorPoolUtil.SpApiResponse();
        setResponseError(response);
        String volumeUuid = UUID.randomUUID().toString();
        when(srcObj.getUuid()).thenReturn(volumeUuid);
        when(StorPoolUtil.volumeCopy(volumeUuid, "~t.t.t", "volume", null, null, null, conn)).thenReturn(response);
        storPoolPrimaryDataStoreDriver.copyAsync(srcObj, destObj, callback);
        utilities.verify(() -> StorPoolUtil.volumeCopy(volumeUuid, "~t.t.t", "volume", null, null, null, conn), times(1));

        Assert.assertNotNull(response.getError());
    }

    @Test
    public void testCopyVolumeNotAttachedToVmPassed() {

        setReturnsWhenSourceAndDestinationAreVolumes(srcStore, destStore, srcObj, destObj, srcTO, dstTO, dstPrimaryTo, null);

        when(StorPoolUtil.getSpConnection(destObj.getDataStore().getUuid(), destObj.getDataStore().getId(), detailsDao, storagePool)).thenReturn(conn);
        StorPoolUtil.SpApiResponse response = new StorPoolUtil.SpApiResponse();
        String volumeUuid = UUID.randomUUID().toString();
        when(srcObj.getUuid()).thenReturn(volumeUuid);
        when(StorPoolUtil.volumeCopy(volumeUuid, "~t.t.t", "volume", null, null, null, conn)).thenReturn(response);
        storPoolPrimaryDataStoreDriver.copyAsync(srcObj, destObj, callback);
        utilities.verify(() -> StorPoolUtil.volumeCopy(volumeUuid, "~t.t.t", "volume", null, null, null, conn), times(1));
        Assert.assertNull(response.getError());
    }

    private void setReturnsWhenSourceAndDestinationAreVolumes(DataStore srcStore, DataStore destStore, DataObject srcObj, DataObject destObj, VolumeObjectTO srcTO, VolumeObjectTO dstTO, PrimaryDataStoreTO dstPrimaryTo, VMInstanceVO vm) {
        when(srcStore.getRole()).thenReturn(DataStoreRole.Primary);
        when(destStore.getRole()).thenReturn(DataStoreRole.Primary);
        when(srcObj.getDataStore()).thenReturn(srcStore);
        when(destObj.getDataStore()).thenReturn(destStore);
        when(srcObj.getType()).thenReturn(DataObjectType.VOLUME);
        when(destObj.getType()).thenReturn(DataObjectType.VOLUME);
        when(destObj.getTO()).thenReturn(dstTO);
        when(srcObj.getTO()).thenReturn(srcTO);

        when(srcObj.getDataStore().getDriver()).thenReturn(storPoolPrimaryDataStoreDriver);
        when(destObj.getTO().getDataStore()).thenReturn(dstPrimaryTo);
        when(destObj.getDataStore().getUuid()).thenReturn("SP_API_HTTP=1.1.1.1:81;SP_AUTH_TOKEN=token;SP_TEMPLATE=template_name");
        when(destObj.getDataStore().getId()).thenReturn(1L);

        when(srcTO.getPath()).thenReturn("/dev/storpool-byid/t.t.t");
        when(dstPrimaryTo.getPoolType()).thenReturn(StoragePoolType.StorPool);
        when(vmInstanceDao.findById(anyLong())).thenReturn(vm);
    }

    private static void setResponseError(StorPoolUtil.SpApiResponse resp) {
        StorPoolUtil.SpApiError respErr = new StorPoolUtil.SpApiError();
        respErr.setName("error");
        respErr.setDescr("Failed");
        resp.setError(respErr);
    }
}
