package org.ovirt.engine.core.bll;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Matchers;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.ovirt.engine.core.common.action.RemoveSnapshotParameters;
import org.ovirt.engine.core.common.businessentities.DiskImage;
import org.ovirt.engine.core.common.businessentities.StorageDomainStatus;
import org.ovirt.engine.core.common.businessentities.StorageDomainType;
import org.ovirt.engine.core.common.businessentities.VmTemplate;
import org.ovirt.engine.core.common.businessentities.storage_domains;
import org.ovirt.engine.core.compat.Guid;
import org.ovirt.engine.core.compat.NGuid;
import org.ovirt.engine.core.dao.DiskImageDAO;
import org.ovirt.engine.core.dao.StorageDomainDAO;
import org.ovirt.engine.core.dao.VmTemplateDAO;

/** A test case for the {@link RemoveSnapshotCommand} class. */
@RunWith(MockitoJUnitRunner.class)
public class RemoveSnapshotCommandTest {

    /** The command to test */
    private RemoveSnapshotCommand<RemoveSnapshotParameters> cmd;

    @Mock
    private VmTemplateDAO vmTemplateDAO;

    @Mock
    StorageDomainDAO sdDAO;

    @Mock
    private DiskImageDAO diskImageDAO;

    private static final Guid STORAGE_DOMAIN_ID = Guid.NewGuid();
    private static final Guid STORAGE_DOMAIN_ID2 = Guid.NewGuid();

    private static final int USED_SPACE_GB = 4;
    private static final int IMAGE_ACTUAL_SIZE_GB = 4;

    @Before
    public void setUp() {
        Guid vmGuid = Guid.NewGuid();
        Guid snapGuid = Guid.NewGuid();

        RemoveSnapshotParameters params = new RemoveSnapshotParameters(snapGuid, vmGuid);
        cmd = spy(new RemoveSnapshotCommand<RemoveSnapshotParameters>(params));
        mockDAOs(cmd);
        doReturn(vmTemplateDAO).when(cmd).getVmTemplateDAO();
        doReturn(diskImageDAO).when(cmd).getDiskImageDao();
    }

    private void mockDAOs(RemoveSnapshotCommand<?> cmd) {
        doReturn(sdDAO).when(cmd).getStorageDomainDAO();
    }

    @Test
    public void testValidateImageNotInTemplateTrue() {
        when(vmTemplateDAO.get(mockSourceImage())).thenReturn(null);
        assertTrue("validation should succeed", cmd.validateImageNotInTemplate());
    }

    @Test
    public void testValidateImageNotInTemplateFalse() {
        when(vmTemplateDAO.get(mockSourceImage())).thenReturn(new VmTemplate());
        assertFalse("validation should succeed", cmd.validateImageNotInTemplate());
    }

    @Test
    public void testValidateImageNotActiveTrue() {
        when(diskImageDAO.get(mockSourceImage())).thenReturn(null);
        assertTrue("validation should succeed", cmd.validateImageNotActive());
    }

    @Test
    public void testValidateImageNotActiveFalse() {
        when(diskImageDAO.get(mockSourceImage())).thenReturn(new DiskImage());
        assertFalse("validation should succeed", cmd.validateImageNotActive());
    }

    @Test
    public void testEnoughSpaceToMergeSnapshotsWithOneDisk() {
        when(diskImageDAO.get(mockSourceImage())).thenReturn(new DiskImage());
        mockStorageDomainDAOGetForStoragePool(10, STORAGE_DOMAIN_ID);
        assertTrue("Validation should succeed. Free space minus threshold should be bigger then disk size",
                cmd.isEnoughSpaceToMergeSnapshots());
    }

    @Test
    public void testNotEnoughSpaceToMergeSnapshotsWithOneDisk() {
        when(diskImageDAO.get(mockSourceImage())).thenReturn(new DiskImage());
        mockStorageDomainDAOGetForStoragePool(3, STORAGE_DOMAIN_ID);
        assertFalse("Validation should fail. Free space minus threshold should be smaller then disk size",
                cmd.isEnoughSpaceToMergeSnapshots());
    }

    @Test
    public void testEnoughSpaceToMergeSnapshotsWithMultipleDisk() {
        List<DiskImage> imagesDisks = mockMultipleSourceImagesForDomain(4, STORAGE_DOMAIN_ID, IMAGE_ACTUAL_SIZE_GB);
        doReturn(imagesDisks).when(cmd).getSourceImages();
        mockStorageDomainDAOGetForStoragePool(22, STORAGE_DOMAIN_ID);
        assertTrue("Validation should succeed. Free space minus threshold should be bigger then summarize all disks size",
                cmd.isEnoughSpaceToMergeSnapshots());
    }

    @Test
    public void testNotEnoughSpaceToMergeSnapshotsWithMultipleDisk() {
        List<DiskImage> imagesDisks = mockMultipleSourceImagesForDomain(4, STORAGE_DOMAIN_ID, IMAGE_ACTUAL_SIZE_GB);
        doReturn(imagesDisks).when(cmd).getSourceImages();
        mockStorageDomainDAOGetForStoragePool(15, STORAGE_DOMAIN_ID);
        assertFalse("Validation should fail. Free space minus threshold should be smaller then summarize all disks size",
                cmd.isEnoughSpaceToMergeSnapshots());
    }

    @Test
    public void testEnoughSpaceToMergeSnapshotsWithMultipleDiskAndDomains() {
        List<DiskImage> imagesDisks = mockMultipleSourceImagesForDomain(4, STORAGE_DOMAIN_ID, IMAGE_ACTUAL_SIZE_GB);
        imagesDisks.addAll(mockMultipleSourceImagesForDomain(4, STORAGE_DOMAIN_ID2, IMAGE_ACTUAL_SIZE_GB));
        doReturn(imagesDisks).when(cmd).getSourceImages();
        mockStorageDomainDAOGetForStoragePool(22, STORAGE_DOMAIN_ID);
        mockStorageDomainDAOGetForStoragePool(22, STORAGE_DOMAIN_ID2);
        assertTrue("Validation should succeed. Free space minus threshold should be bigger then summarize all disks size for each domain",
                cmd.isEnoughSpaceToMergeSnapshots());
    }

    @Test
    public void testNotEnoughForMultipleDiskAndDomainsFirstDomainFails() {
        List<DiskImage> imagesDisks = mockMultipleSourceImagesForDomain(4, STORAGE_DOMAIN_ID, IMAGE_ACTUAL_SIZE_GB);
        imagesDisks.addAll(mockMultipleSourceImagesForDomain(4, STORAGE_DOMAIN_ID2, IMAGE_ACTUAL_SIZE_GB));
        doReturn(imagesDisks).when(cmd).getSourceImages();
        mockStorageDomainDAOGetForStoragePool(15, STORAGE_DOMAIN_ID);
        mockStorageDomainDAOGetForStoragePool(22, STORAGE_DOMAIN_ID2);
        assertFalse("Validation should fail. First domain should not have enough free space for request.",
                cmd.isEnoughSpaceToMergeSnapshots());
    }

    @Test
    public void testNotEnoughForMultipleDiskAndDomainsSecondDomainFails() {
        List<DiskImage> imagesDisks = mockMultipleSourceImagesForDomain(4, STORAGE_DOMAIN_ID, IMAGE_ACTUAL_SIZE_GB);
        imagesDisks.addAll(mockMultipleSourceImagesForDomain(4, STORAGE_DOMAIN_ID2, IMAGE_ACTUAL_SIZE_GB));
        doReturn(imagesDisks).when(cmd).getSourceImages();
        mockStorageDomainDAOGetForStoragePool(22, STORAGE_DOMAIN_ID);
        mockStorageDomainDAOGetForStoragePool(10, STORAGE_DOMAIN_ID2);
        assertFalse("Validation should fail. Second domain should not have enough free space for request.",
                cmd.isEnoughSpaceToMergeSnapshots());
    }

    @Test
    public void testNotEnoughForMultipleDiskAndDomainsAllDomainsFail() {
        List<DiskImage> imagesDisks = mockMultipleSourceImagesForDomain(4, STORAGE_DOMAIN_ID, IMAGE_ACTUAL_SIZE_GB);
        imagesDisks.addAll(mockMultipleSourceImagesForDomain(4, STORAGE_DOMAIN_ID2, IMAGE_ACTUAL_SIZE_GB));
        doReturn(imagesDisks).when(cmd).getSourceImages();
        mockStorageDomainDAOGetForStoragePool(10, STORAGE_DOMAIN_ID);
        mockStorageDomainDAOGetForStoragePool(10, STORAGE_DOMAIN_ID2);
        assertFalse("Validation should fail. Second domain should not have enough free space for request.",
                cmd.isEnoughSpaceToMergeSnapshots());
    }

    private void mockStorageDomainDAOGetForStoragePool(int domainSpaceGB, Guid storageDomainId) {
        when(sdDAO.getForStoragePool(Matchers.<Guid> eq(storageDomainId), Matchers.<NGuid> any(NGuid.class))).thenReturn(createStorageDomain(domainSpaceGB,
                storageDomainId));
    }

    /** Mocks a call to {@link RemoveSnapshotCommand#getSourceImages()} and returns list of images */
    private static List<DiskImage> mockMultipleSourceImagesForDomain(int numberOfDisks, Guid storageDomainId, int actualDiskSize) {
        List<DiskImage> listDisks = new ArrayList<DiskImage>();
        for (int index=0; index < numberOfDisks; index++) {
            Guid imageId = Guid.NewGuid();
            DiskImage image = new DiskImage();
            image.setImageId(imageId);
            ArrayList<Guid> list = new ArrayList<Guid>();
            list.add(storageDomainId);
            image.setstorage_ids(list);
            image.setActualSize(actualDiskSize);
            listDisks.add(image);
        }
        return listDisks;
    }

    /** Mocks a call to {@link RemoveSnapshotCommand#getSourceImages()} and returns its image guid */
    private Guid mockSourceImage() {
        Guid imageId = Guid.NewGuid();
        DiskImage image = new DiskImage();
        image.setImageId(imageId);
        ArrayList<Guid> list = new ArrayList<Guid>();
        list.add(STORAGE_DOMAIN_ID);
        image.setstorage_ids(list);
        image.setActualSize(IMAGE_ACTUAL_SIZE_GB);
        image.setsize(40);
        doReturn(Collections.singletonList(image)).when(cmd).getSourceImages();
        return imageId;
    }

    private static storage_domains createStorageDomain(int availableSpace, Guid storageDomainId) {
        storage_domains sd = new storage_domains();
        sd.setstorage_domain_type(StorageDomainType.Master);
        sd.setstatus(StorageDomainStatus.Active);
        sd.setavailable_disk_size(availableSpace);
        sd.setused_disk_size(USED_SPACE_GB);
        sd.setId(storageDomainId);
        return sd;
    }
}
