// 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.dedicated.manager;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.when;

import java.io.IOException;
import java.util.UUID;

import javax.inject.Inject;

import org.apache.cloudstack.affinity.AffinityGroupService;
import org.apache.cloudstack.affinity.dao.AffinityGroupDao;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.dedicated.DedicatedResourceManagerImpl;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.test.utils.SpringUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.AnnotationConfigContextLoader;

import com.cloud.dc.DedicatedResourceVO;
import com.cloud.dc.dao.ClusterDao;
import com.cloud.dc.dao.DataCenterDao;
import com.cloud.dc.dao.DedicatedResourceDao;
import com.cloud.dc.dao.HostPodDao;
import com.cloud.domain.DomainVO;
import com.cloud.domain.dao.DomainDao;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.host.dao.HostDao;
import com.cloud.user.Account;
import com.cloud.user.AccountManager;
import com.cloud.user.AccountVO;
import com.cloud.user.User;
import com.cloud.user.UserVO;
import com.cloud.user.dao.AccountDao;
import com.cloud.utils.component.ComponentContext;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.vm.dao.UserVmDao;

import junit.framework.Assert;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader = AnnotationConfigContextLoader.class)
public class DedicatedApiUnitTest {
    @Inject
    DedicatedResourceManagerImpl _dedicatedService = new DedicatedResourceManagerImpl();

    @Inject
    AccountManager _acctMgr;

    @Inject
    AccountDao _accountDao;

    @Inject
    DomainDao _domainDao;

    @Inject
    UserVmDao _vmDao;

    @Inject
    DedicatedResourceDao _dedicatedDao;

    @Inject
    DataCenterDao _dcDao;

    @Inject
    HostPodDao _podDao;

    @Inject
    ClusterDao _clusterDao;

    @Inject
    HostDao _hostDao;

    @Inject
    ConfigurationDao _configDao;

    private static long domainId = 5L;
    private static long accountId = 5L;
    private static String accountName = "admin";

    @Before
    public void setUp() {
        ComponentContext.initComponentsLifeCycle();
        AccountVO account = new AccountVO(accountName, domainId, "networkDomain", Account.Type.NORMAL, "uuid");
        DomainVO domain = new DomainVO("rootDomain", 5L, 5L, "networkDomain");

        UserVO user = new UserVO(1, "testuser", "password", "firstname", "lastName", "email", "timezone", UUID.randomUUID().toString(), User.Source.UNKNOWN);

        CallContext.register(user, account);
        when(_acctMgr.finalizeOwner(any(Account.class), nullable(String.class), nullable(Long.class), nullable(Long.class))).thenReturn(account);
        when(_accountDao.findByIdIncludingRemoved(0L)).thenReturn(account);
        when(_accountDao.findById(anyLong())).thenReturn(account);
        when(_domainDao.findById(domainId)).thenReturn(domain);
    }

    @After
    public void tearDown() {
        CallContext.unregister();
    }

    @Test(expected = InvalidParameterValueException.class)
    public void InvalidDomainIDForAccountTest() {
        _dedicatedService.dedicateZone(10L, domainId, accountName);
    }

    @Test(expected = InvalidParameterValueException.class)
    public void dedicateResourceInvalidAccountIDTest() {
        _dedicatedService.dedicateZone(10L, domainId, accountName);
    }

    @Test
    public void releaseDedicatedZoneInvalidIdTest() {
        when(_dedicatedDao.findByZoneId(10L)).thenReturn(null);
        try {
            _dedicatedService.releaseDedicatedResource(10L, null, null, null);
        } catch (InvalidParameterValueException e) {
            Assert.assertTrue(e.getMessage().contains("No Dedicated Resource available to release"));
        }
    }

    /*    @Test
        public void runDedicateZoneTest() {
            DataCenterVO dc = new DataCenterVO(10L, "TestZone", "Dedicated",
                    "8.8.8.8", null, "10.0.0.1", null, "10.0.0.1/24", null, null,
                    NetworkType.Basic, null, null);
            when(_dcDao.findById(10L)).thenReturn(dc);
            try {
                List<DedicatedResourceVO> result = _dedicatedService.dedicateZone(10L, domainId, accountName);
                Assert.assertNotNull(result);
            } catch (Exception e) {
                logger.info("exception in testing dedication of zone "
                        + e.toString());
            }
        }

        @Test
        public void runDedicatePodTest() {
            HostPodVO pod = new HostPodVO("TestPod", 20L, "10.0.0.1", "10.0.0.0",
                    22, null);
            when(_podDao.findById(10L)).thenReturn(pod);
            try {
                List<DedicatedResourceVO> result = _dedicatedService.dedicatePod(10L, domainId, accountName);
                Assert.assertNotNull(result);
            } catch (Exception e) {
                logger.info("exception in testing dedication of pod "
                        + e.toString());
            }
        }

        @Test
        public void runDedicateClusterTest() {
            ClusterVO cluster = new ClusterVO(10L, 10L, "TestCluster");
            when(_clusterDao.findById(10L)).thenReturn(cluster);
            try {
                List<DedicatedResourceVO> result = _dedicatedService.dedicateCluster(10L, domainId, accountName);
                Assert.assertNotNull(result);
            } catch (Exception e) {
                logger.info("exception in testing dedication of cluster "
                        + e.toString());
            }
        }

        @Test
        public void runDedicateHostTest() {
            HostVO host = new HostVO(10L, "Host-1", Host.Type.Routing, null,
                    "10.0.0.0", null, null, null, null, null, null, null, null,
                    Status.Up, null, null, null, 10L, 10L, 30L, 10233, null, null,
                    null, 0, null);
            when(_hostDao.findById(10L)).thenReturn(host);
            try {
                List<DedicatedResourceVO> result = _dedicatedService.dedicateHost(10L, domainId, accountName);
                Assert.assertNotNull(result);
            } catch (Exception e) {
                logger.info("exception in testing dedication of host "
                        + e.toString());
            }
        }
    */

    @Test(expected = CloudRuntimeException.class)
    public void dedicateZoneExistTest() {
        DedicatedResourceVO dr = new DedicatedResourceVO(10L, null, null, null, domainId, accountId, 12L);
        when(_dedicatedDao.findByZoneId(10L)).thenReturn(dr);
        _dedicatedService.dedicateZone(10L, domainId, accountName);
    }

    @Test(expected = CloudRuntimeException.class)
    public void dedicatePodExistTest() {
        DedicatedResourceVO dr = new DedicatedResourceVO(null, 10L, null, null, domainId, accountId, 12L);
        when(_dedicatedDao.findByPodId(10L)).thenReturn(dr);
        _dedicatedService.dedicatePod(10L, domainId, accountName);
    }

    @Test(expected = CloudRuntimeException.class)
    public void dedicateClusterExistTest() {
        DedicatedResourceVO dr = new DedicatedResourceVO(null, null, 10L, null, domainId, accountId, 12L);
        when(_dedicatedDao.findByClusterId(10L)).thenReturn(dr);
        _dedicatedService.dedicateCluster(10L, domainId, accountName);
    }

    @Test(expected = CloudRuntimeException.class)
    public void dedicateHostExistTest() {
        DedicatedResourceVO dr = new DedicatedResourceVO(null, null, null, 10L, domainId, accountId, 12L);
        when(_dedicatedDao.findByHostId(10L)).thenReturn(dr);
        _dedicatedService.dedicateHost(10L, domainId, accountName);
    }

    @Test(expected = InvalidParameterValueException.class)
    public void releaseDedicatedPodInvalidIdTest() {
        when(_dedicatedDao.findByPodId(10L)).thenReturn(null);
        _dedicatedService.releaseDedicatedResource(null, 10L, null, null);
    }

    @Test(expected = InvalidParameterValueException.class)
    public void releaseDedicatedClusterInvalidIdTest() {
        when(_dedicatedDao.findByClusterId(10L)).thenReturn(null);
        _dedicatedService.releaseDedicatedResource(null, null, 10L, null);
    }

    @Test(expected = InvalidParameterValueException.class)
    public void releaseDedicatedHostInvalidIdTest() {
        when(_dedicatedDao.findByHostId(10L)).thenReturn(null);
        _dedicatedService.releaseDedicatedResource(null, null, null, 10L);
    }

    @Configuration
    @ComponentScan(basePackageClasses = {DedicatedResourceManagerImpl.class},
                   includeFilters = {@Filter(value = TestConfiguration.Library.class, type = FilterType.CUSTOM)},
                   useDefaultFilters = false)
    public static class TestConfiguration extends SpringUtils.CloudStackTestConfiguration {

        @Bean
        public AccountDao accountDao() {
            return Mockito.mock(AccountDao.class);
        }

        @Bean
        public DomainDao domainDao() {
            return Mockito.mock(DomainDao.class);
        }

        @Bean
        public DedicatedResourceDao dedicatedDao() {
            return Mockito.mock(DedicatedResourceDao.class);
        }

        @Bean
        public HostDao hostDao() {
            return Mockito.mock(HostDao.class);
        }

        @Bean
        public AccountManager acctManager() {
            return Mockito.mock(AccountManager.class);
        }

        @Bean
        public UserVmDao userVmDao() {
            return Mockito.mock(UserVmDao.class);
        }

        @Bean
        public DataCenterDao dataCenterDao() {
            return Mockito.mock(DataCenterDao.class);
        }

        @Bean
        public HostPodDao hostPodDao() {
            return Mockito.mock(HostPodDao.class);
        }

        @Bean
        public ClusterDao clusterDao() {
            return Mockito.mock(ClusterDao.class);
        }

        @Bean
        public ConfigurationDao configDao() {
            return Mockito.mock(ConfigurationDao.class);
        }

        @Bean
        public AffinityGroupService affinityGroupService() {
            return Mockito.mock(AffinityGroupService.class);
        }

        @Bean
        public AffinityGroupDao affinityGroupDao() {
            return Mockito.mock(AffinityGroupDao.class);
        }

        public static class Library implements TypeFilter {
            @Override
            public boolean match(MetadataReader mdr, MetadataReaderFactory arg1) throws IOException {
                ComponentScan cs = TestConfiguration.class.getAnnotation(ComponentScan.class);
                return SpringUtils.includedInBasePackageClasses(mdr.getClassMetadata().getClassName(), cs);
            }
        }
    }
}
