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

import com.cloud.exception.InvalidParameterValueException;
import com.cloud.user.Account;
import com.cloud.user.AccountManager;
import com.cloud.user.User;
import com.cloud.uservm.UserVm;
import com.cloud.utils.Pair;
import com.cloud.utils.db.SearchCriteria;
import com.cloud.vm.UserVmManager;
import com.cloud.vm.VirtualMachine;
import org.apache.cloudstack.api.command.user.vm.CreateVMScheduleCmd;
import org.apache.cloudstack.api.command.user.vm.DeleteVMScheduleCmd;
import org.apache.cloudstack.api.command.user.vm.ListVMScheduleCmd;
import org.apache.cloudstack.api.command.user.vm.UpdateVMScheduleCmd;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.api.response.VMScheduleResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.vm.schedule.dao.VMScheduleDao;
import org.apache.commons.lang.time.DateUtils;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
import org.springframework.test.util.ReflectionTestUtils;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
import java.util.UUID;

import static org.junit.Assert.assertNotNull;

public class VMScheduleManagerImplTest {

    @Spy
    @InjectMocks
    VMScheduleManagerImpl vmScheduleManager = new VMScheduleManagerImpl();

    @Mock
    VMScheduleDao vmScheduleDao;

    @Mock
    VMScheduler vmScheduler;

    @Mock
    UserVmManager userVmManager;

    @Mock
    AccountManager accountManager;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        Account callingAccount = Mockito.mock(Account.class);
        User callingUser = Mockito.mock(User.class);
        CallContext.register(callingUser, callingAccount);
    }

    private void validateResponse(VMScheduleResponse response, VMSchedule vmSchedule, VirtualMachine vm) {
        assertNotNull(response);
        Assert.assertEquals(ReflectionTestUtils.getField(response, "id"), vmSchedule.getUuid());
        Assert.assertEquals(ReflectionTestUtils.getField(response, "vmId"), vm.getUuid());
        Assert.assertEquals(ReflectionTestUtils.getField(response, "schedule"), vmSchedule.getSchedule());
        Assert.assertEquals(ReflectionTestUtils.getField(response, "timeZone"), vmSchedule.getTimeZone());
        Assert.assertEquals(ReflectionTestUtils.getField(response, "action"), vmSchedule.getAction());
        Assert.assertEquals(ReflectionTestUtils.getField(response, "startDate"), vmSchedule.getStartDate());
        Assert.assertEquals(ReflectionTestUtils.getField(response, "endDate"), vmSchedule.getEndDate());
    }

    @Test
    public void createSchedule() {
        UserVm vm = Mockito.mock(UserVm.class);
        VMScheduleVO vmSchedule = Mockito.mock(VMScheduleVO.class);
        CreateVMScheduleCmd cmd = Mockito.mock(CreateVMScheduleCmd.class);

        Mockito.when(cmd.getVmId()).thenReturn(1L);
        Mockito.when(cmd.getSchedule()).thenReturn("0 0 * * *");
        Mockito.when(cmd.getTimeZone()).thenReturn("UTC");
        Mockito.when(cmd.getAction()).thenReturn("start");
        Mockito.when(cmd.getStartDate()).thenReturn(DateUtils.addDays(new Date(), 1));
        Mockito.when(cmd.getEndDate()).thenReturn(DateUtils.addDays(new Date(), 2));
        Mockito.when(vm.getUuid()).thenReturn(UUID.randomUUID().toString());
        Mockito.when(vmScheduleDao.persist(Mockito.any(VMScheduleVO.class))).thenReturn(vmSchedule);
        Mockito.when(userVmManager.getUserVm(Mockito.anyLong())).thenReturn(vm);
        Mockito.doNothing().when(accountManager).checkAccess(Mockito.any(Account.class), Mockito.isNull(), Mockito.eq(false), Mockito.eq(vm));
        VMScheduleResponse response = vmScheduleManager.createSchedule(cmd);
        Mockito.verify(vmScheduleDao, Mockito.times(1)).persist(Mockito.any(VMScheduleVO.class));

        validateResponse(response, vmSchedule, vm);
    }

    @Test
    public void createResponse() {
        VMSchedule vmSchedule = Mockito.mock(VMSchedule.class);
        UserVm vm = Mockito.mock(UserVm.class);
        Mockito.when(vmSchedule.getVmId()).thenReturn(1L);
        Mockito.when(userVmManager.getUserVm(vmSchedule.getVmId())).thenReturn(vm);

        VMScheduleResponse response = vmScheduleManager.createResponse(vmSchedule);
        validateResponse(response, vmSchedule, vm);
    }

    @Test
    public void listSchedule() {
        UserVm vm = Mockito.mock(UserVm.class);
        VMScheduleVO vmSchedule1 = Mockito.mock(VMScheduleVO.class);
        VMScheduleVO vmSchedule2 = Mockito.mock(VMScheduleVO.class);
        List<VMScheduleVO> vmScheduleList = new ArrayList<>();
        vmScheduleList.add(vmSchedule1);
        vmScheduleList.add(vmSchedule2);

        Mockito.when(vm.getUuid()).thenReturn(UUID.randomUUID().toString());
        Mockito.when(userVmManager.getUserVm(1L)).thenReturn(vm);
        Mockito.when(
                vmScheduleDao.searchAndCount(Mockito.anyLong(), Mockito.anyLong(), Mockito.any(),
                        Mockito.anyBoolean(), Mockito.anyLong(), Mockito.anyLong())
        ).thenReturn(new Pair<>(vmScheduleList, vmScheduleList.size()));
        Mockito.when(vmSchedule1.getVmId()).thenReturn(1L);
        Mockito.when(vmSchedule2.getVmId()).thenReturn(1L);

        ListVMScheduleCmd cmd = Mockito.mock(ListVMScheduleCmd.class);
        Mockito.when(cmd.getVmId()).thenReturn(1L);

        ListResponse<VMScheduleResponse> responseList = vmScheduleManager.listSchedule(cmd);
        Mockito.verify(vmScheduleDao, Mockito.times(1)).searchAndCount(Mockito.anyLong(), Mockito.anyLong(), Mockito.any(),
                Mockito.anyBoolean(), Mockito.anyLong(), Mockito.anyLong());
        assertNotNull(responseList);
        Assert.assertEquals(2, (int) responseList.getCount());
        Assert.assertEquals(2, responseList.getResponses().size());

        for (int i = 0; i < responseList.getResponses().size(); i++) {
            VMScheduleResponse response = responseList.getResponses().get(i);
            VMScheduleVO vmSchedule = vmScheduleList.get(i);
            validateResponse(response, vmSchedule, vm);
        }
    }

    @Test
    public void updateSchedule() {
        VMScheduleVO vmSchedule = Mockito.mock(VMScheduleVO.class);
        UpdateVMScheduleCmd cmd = Mockito.mock(UpdateVMScheduleCmd.class);
        UserVm vm = Mockito.mock(UserVm.class);
        Mockito.when(cmd.getId()).thenReturn(1L);
        Mockito.when(cmd.getSchedule()).thenReturn("0 0 * * *");
        Mockito.when(cmd.getTimeZone()).thenReturn("UTC");
        Mockito.when(cmd.getStartDate()).thenReturn(DateUtils.addDays(new Date(), 1));
        Mockito.when(cmd.getEndDate()).thenReturn(DateUtils.addDays(new Date(), 2));
        Mockito.when(vmScheduleDao.findById(Mockito.anyLong())).thenReturn(vmSchedule);
        Mockito.when(vmScheduleDao.update(Mockito.eq(cmd.getId()), Mockito.any(VMScheduleVO.class))).thenReturn(true);
        Mockito.when(vmSchedule.getVmId()).thenReturn(1L);
        Mockito.when(vmSchedule.getStartDate()).thenReturn(DateUtils.addDays(new Date(), 1));
        Mockito.when(userVmManager.getUserVm(vmSchedule.getVmId())).thenReturn(vm);

        VMScheduleResponse response = vmScheduleManager.updateSchedule(cmd);
        Mockito.verify(vmScheduleDao, Mockito.times(1)).update(Mockito.eq(cmd.getId()), Mockito.any(VMScheduleVO.class));

        validateResponse(response, vmSchedule, vm);
    }

    @Test
    public void removeScheduleByVmId() {
        UserVm vm = Mockito.mock(UserVm.class);
        VMScheduleVO vmSchedule1 = Mockito.mock(VMScheduleVO.class);
        VMScheduleVO vmSchedule2 = Mockito.mock(VMScheduleVO.class);
        List<VMScheduleVO> vmScheduleList = new ArrayList<>();
        vmScheduleList.add(vmSchedule1);
        vmScheduleList.add(vmSchedule2);
        SearchCriteria<VMScheduleVO> sc = Mockito.mock(SearchCriteria.class);

        Mockito.when(vm.getId()).thenReturn(1L);
        Mockito.when(vmScheduleDao.getSearchCriteriaForVMId(vm.getId())).thenReturn(sc);
        Mockito.when(vmScheduleDao.search(sc, null)).thenReturn(vmScheduleList);
        Mockito.when(vmSchedule1.getId()).thenReturn(1L);
        Mockito.when(vmSchedule2.getId()).thenReturn(2L);
        Mockito.when(vmScheduleDao.remove(sc)).thenReturn(2);

        long rowsRemoved = vmScheduleManager.removeScheduleByVmId(vm.getId(), false);

        Mockito.verify(vmScheduleDao, Mockito.times(1)).remove(sc);
        Assert.assertEquals(2, rowsRemoved);
    }

    @Test
    public void removeSchedule() {
        VMScheduleVO vmSchedule = Mockito.mock(VMScheduleVO.class);
        UserVm vm = Mockito.mock(UserVm.class);
        DeleteVMScheduleCmd cmd = Mockito.mock(DeleteVMScheduleCmd.class);

        Mockito.when(cmd.getId()).thenReturn(1L);
        Mockito.when(cmd.getVmId()).thenReturn(1L);
        Mockito.when(vmSchedule.getVmId()).thenReturn(1L);
        Mockito.when(userVmManager.getUserVm(cmd.getVmId())).thenReturn(vm);
        Mockito.when(vmScheduleDao.findById(Mockito.anyLong())).thenReturn(vmSchedule);
        Mockito.when(vmScheduleDao.removeSchedulesForVmIdAndIds(Mockito.anyLong(), Mockito.anyList())).thenReturn(1L);

        Long rowsRemoved = vmScheduleManager.removeSchedule(cmd);

        Mockito.verify(vmScheduleDao, Mockito.times(1)).removeSchedulesForVmIdAndIds(Mockito.anyLong(), Mockito.anyList());
        Assert.assertEquals(1L, (long) rowsRemoved);
    }

    @Test
    public void validateStartDateEndDate() {
        // Valid scenario 1
        // Start date is before end date
        Date startDate = DateUtils.addDays(new Date(), 1);
        Date endDate = DateUtils.addDays(new Date(), 2);
        vmScheduleManager.validateStartDateEndDate(startDate, endDate, TimeZone.getDefault());

        // Valid Scenario 2
        // Start date is before current date and end date is null
        endDate = null;
        vmScheduleManager.validateStartDateEndDate(startDate, endDate, TimeZone.getDefault());

        // Invalid Scenario 2
        // Start date is before current date
        startDate = DateUtils.addDays(new Date(), -1);
        try {
            vmScheduleManager.validateStartDateEndDate(startDate, endDate, TimeZone.getDefault());
            Assert.fail("Should have thrown InvalidParameterValueException");
        } catch (InvalidParameterValueException e) {
            Assert.assertTrue(e.getMessage().contains("Invalid value for start date. Start date") &&
                    e.getMessage().contains("can't be before current time"));
        }

        // Invalid Scenario 2
        // Start date is after end date
        startDate = DateUtils.addDays(new Date(), 2);
        endDate = DateUtils.addDays(new Date(), 1);
        try {
            vmScheduleManager.validateStartDateEndDate(startDate, endDate, TimeZone.getDefault());
            Assert.fail("Should have thrown InvalidParameterValueException");
        } catch (InvalidParameterValueException e) {
            Assert.assertTrue(e.getMessage().contains("Invalid value for end date. End date") &&
                    e.getMessage().contains("can't be before start date"));
        }

        // Invalid Scenario 3
        // End date is before current date
        startDate = DateUtils.addDays(new Date(), 1);
        endDate = DateUtils.addDays(new Date(), -1);
        try {
            vmScheduleManager.validateStartDateEndDate(startDate, endDate, TimeZone.getDefault());
            Assert.fail("Should have thrown InvalidParameterValueException");
        } catch (InvalidParameterValueException e) {
            Assert.assertTrue(e.getMessage().contains("Invalid value for end date. End date") &&
                    e.getMessage().contains("can't be before current time"));
        }
    }
}
