package com.bocloud.ims.service.engine;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.bocloud.cmp.driver.storage.DataStoreDriver;
import com.bocloud.cmp.entity.enums.BocloudStatus;
import com.bocloud.cmp.entity.enums.PoolGroupTarget;
import com.bocloud.cmp.model.DataStoreModel;
import com.bocloud.common.model.BocloudResult;
import com.bocloud.common.model.BocloudResult;
import com.bocloud.common.utils.ListTool;
import com.bocloud.ims.entity.resource.CloudServer;
import com.bocloud.ims.entity.resource.CloudVendor;
import com.bocloud.ims.entity.resource.Cluster;
import com.bocloud.ims.entity.resource.DataStore;
import com.bocloud.ims.entity.resource.NetworkHost;
import com.bocloud.ims.entity.resource.PoolGroup;
import com.bocloud.ims.entity.resource.PoolGroupRelation;
import com.bocloud.ims.entity.resource.PortGroup;
import com.bocloud.ims.entity.resource.SchedulerPolicy;
import com.bocloud.ims.entity.resource.Server;
import com.bocloud.ims.entity.resource.ServerConfig;
import com.bocloud.ims.entity.resource.StoreHost;
import com.bocloud.ims.entity.resource.Switcher;
import com.bocloud.ims.entity.resource.Vdc;
import com.bocloud.ims.repository.resource.CloudServerRepository;
import com.bocloud.ims.repository.resource.CloudVendorRepository;
import com.bocloud.ims.repository.resource.ClusterRepository;
import com.bocloud.ims.repository.resource.DataStoreRepository;
import com.bocloud.ims.repository.resource.NetworkHostRepository;
import com.bocloud.ims.repository.resource.PoolGroupRelationRepository;
import com.bocloud.ims.repository.resource.PoolGroupRepository;
import com.bocloud.ims.repository.resource.PortGroupRepository;
import com.bocloud.ims.repository.resource.SchedulerPolicyRepository;
import com.bocloud.ims.repository.resource.ServerConfigRepository;
import com.bocloud.ims.repository.resource.ServerRepository;
import com.bocloud.ims.repository.resource.StoreHostRepository;
import com.bocloud.ims.repository.resource.SwitcherRepository;
import com.bocloud.ims.repository.resource.VdcRepository;
import com.google.common.collect.Lists;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.apache.commons.lang3.StringUtils;

import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 资源调度引擎类
 *
 * @author wangyu
 * @version 4.1.0-SNAPSHOT
 * @since 2018/12/25
 */
@Component
public class ResSchedulerEngine {

    private static Logger logger = LoggerFactory.getLogger(ResSchedulerEngine.class);

    @Autowired
    private ClusterRepository clusterRepository;
    @Autowired
    private ServerRepository serverRepository;
    @Autowired
    private CloudServerRepository cloudServerRepository;
    @Autowired
    private PoolGroupRelationRepository relationRepository;
    @Autowired
    private StoreHostRepository storeHostRepository;
    @Autowired
    private ServerConfigRepository serverConfigRepository;
    @Autowired
    private DataStoreRepository dataStoreRepository;
    @Autowired
    private SchedulerPolicyRepository schedulerPolicyRepository;
    @Autowired
    private DataStoreDriver dataStoreDriver;
    @Autowired
    private PoolGroupRepository poolGroupRepository;
    @Autowired
    private CloudVendorRepository cloudVendorRepository;
    @Autowired
    private VdcRepository vdcRepository;
    @Autowired
    private PortGroupRepository portGroupRepository;
    @Autowired
    private SwitcherRepository switcherRepository;
    @Autowired
    private NetworkHostRepository networkHostRepository;

    /**
     * 分配数据存储
     *
     * @param hostId
     * @param addDiskList
     * @return
     * @throws Exception
     */
    public List<String> assignStore(Long hostId, List<JSONObject> addDiskList) throws Exception {
        List<String> storeNames = new ArrayList<>();
        List<StoreHost> storeHosts = storeHostRepository.list(hostId, "PM");
        if (ListTool.isEmpty(storeHosts)) {
            return null;
        }
        List<DataStore> stores = new ArrayList<>();
        for (StoreHost storeHost : storeHosts) {
            DataStore store = dataStoreRepository.query(storeHost.getStoreId());
            if (null != store) {
                // 非活动的存储过滤掉
                if ("inactive".equals(store.getStatus())) {
                    continue;
                }
                stores.add(store);
            }
        }
        for (JSONObject ignored : addDiskList) {
            String findStoreName;
            findStoreName = stores.get(new Random().nextInt(stores.size())).getName();
            if (StringUtils.isEmpty(findStoreName)) {
                return null;
            }
            storeNames.add(findStoreName);
        }
        return storeNames;
    }

    /**
     * 通过策略在相应的计算资源池选择集群
     *
     * @param poolId    资源池ID
     * @param sort      排序(cpu,memory,disk)
     * @param assignMap 申请的规格大小以及数量
     * @return
     * @throws Exception
     */
    public BocloudResult<List<JSONObject>> selectHost(Long poolId, String sort, Map<String, Object> assignMap,
                                                      String diskCategory) throws Exception {
        if (!StringUtils.isEmpty(diskCategory)) {
            logger.info("需要为系统盘过滤类型为【" + diskCategory + "】的数据存储");
        }
        Assert.notNull(assignMap, "");
        List<JSONObject> resultObjs = Lists.newArrayList();
        // 申请的虚拟机CPU(C)
        Long cpu = (Long) assignMap.get("Cpu");
        // 申请的虚拟机内存(GB)
        BigDecimal memory = (BigDecimal) assignMap.get("Memory");
        // 申请的虚拟机磁盘(GB)
        Long disk = (Long) assignMap.get("Disk");
        // 申请的虚拟机个数(个)
        Integer instance = null == assignMap.get("Instance") ? 1 : (Integer) assignMap.get("Instance");
        PoolGroup group = poolGroupRepository.query(poolId);
        if (null == group) {
            logger.warn("Pool group does not exist");
            return new BocloudResult<>(false, "所选计算资源池不存在");
        }
        CloudVendor vendor = cloudVendorRepository.query(group.getVendorId());
        if (null == vendor) {
            logger.warn("Cloud vendor does not exist");
            return new BocloudResult<>(false, "所选计算资源池所在云平台不存在");
        }
        // 查询策略是否存在
        SchedulerPolicy schedulerPolicy = schedulerPolicyRepository.query(group.getPolicyId());
        if (null == schedulerPolicy) {
            logger.warn("Policy does not exist");
            return new BocloudResult<>(false, "策略不存在");
        }
        // 过滤出资源池下关联的主机和数据存储
        Map<Long, Server> serverMap = new HashMap<>(8);
        Set<Long> relationStorage = new HashSet<>();
        List<PoolGroupRelation> relations = relationRepository.listByGroupId(poolId);
        for (PoolGroupRelation relation : relations) {
            if (relation.getEnabled()) {
                 if (PoolGroupTarget.Host.name().equalsIgnoreCase(relation.getCategory())) {
                    Server server = serverRepository.query(relation.getTarget());
                    if (null != server) {
                        serverMap.put(server.getId(), server);
                    }
                }else if (PoolGroupTarget.Storage.name().equalsIgnoreCase(relation.getCategory())){
                    relationStorage.add(relation.getTarget());
                }
            }
        }
        List<Server> hosts = new ArrayList<>(serverMap.values());
        if (ListTool.isEmpty(hosts)) {
            logger.warn("No suitable host to allocate vm");
            return new BocloudResult<>(false, "未找到合适的宿主机");
        }
        List<Server> newHosts = new ArrayList<>();
        for (Server host : hosts) {
            // 如果宿主机不是开机状态，则不能分配资源
            if (!BocloudStatus.Server.RUNNING.name().equals(host.getStatus())) {
                continue;
            }
            ServerConfig config = serverConfigRepository.getBySid(host.getId());
            if (null == config) {
                continue;
            }
            int cpuTotal = 0;
            int memoryTotal = 0;
            if (null != host.getClusterId()) {
                //从平台获取超分比
                CloudVendor cloudVendor = cloudVendorRepository.query(host.getVendorId());
                Assert.notNull(cloudVendor, "云平台不存在");
                JSONObject authentication = JSONObject.parseObject(cloudVendor.getAuthentication());
                // 超分比
                Long excessRatio = Long.valueOf(String.valueOf(authentication.get("excessRatio")));
                // 计算每个宿主机剩余可用cpu，内存,存储容量，看是否有可分配的主机
                int cpuUsed = 0;
                BigDecimal memUsed = BigDecimal.valueOf(0);
                List<CloudServer> vms = cloudServerRepository.listByHid(host.getId());
                for (CloudServer vm : vms) {
                    if (null != vm.getIsTemplate() && !vm.getIsTemplate()) {
                        cpuUsed += null == vm.getCpu() ? 0 : vm.getCpu();
                        memUsed = memUsed.add(null == vm.getMemory() ? BigDecimal.valueOf(0) : vm.getMemory());
                    }
                }
                // 计算cpu剩余可分配总量
                if (null != excessRatio && 0L != excessRatio) {
                    // cpu核芯数*超分比
                    cpuTotal = BigDecimal.valueOf(config.getCpuCore()).multiply(BigDecimal.valueOf(excessRatio))
                            .intValue();
                    // mem*超分比
                    memoryTotal = null == config.getMemoryCapacity() ? 0
                            : BigDecimal.valueOf(config.getMemoryCapacity()).multiply(BigDecimal.valueOf(excessRatio))
                            .intValue();
                } else {
                    cpuTotal = BigDecimal.valueOf(config.getCpuCore()).intValue();
                    memoryTotal = null == config.getMemoryCapacity() ? 0
                            : BigDecimal.valueOf(config.getMemoryCapacity()).intValue();
                }
                host.setCpuRemainder(Math.max(Long.valueOf(cpuTotal) - cpuUsed, 0L));
                host.setMemoryRemainder(Math.max(Long.valueOf(memoryTotal) - memUsed.longValue(), 0L));
            } else {
                host.setCpuRemainder(Long.valueOf(config.getCpuCore()));
                host.setMemoryRemainder(config.getMemoryCapacity().longValue());
            }
            int diskTotal = 0;
            int diskFree = 0;
            List<StoreHost> storeHosts = storeHostRepository.list(host.getId(), "PM");
            List<StoreHost> newStoreHosts = new ArrayList<>();
            for (StoreHost storeHost : storeHosts) {
                DataStore store = dataStoreRepository.query(storeHost.getStoreId());
                if (null != store) {
                    Vdc vdc = vdcRepository.query(store.getVdcId());
                    if (null == vdc) {
                        continue;
                    }
                    // 非活动的存储过滤掉
                    if ("inactive".equals(store.getStatus())) {
                        continue;
                    }
                    if (!relationStorage.contains(store.getId())){
                        continue;
                    }
                    // 获取实时存储已使用
                    BocloudResult result = dataStoreDriver.detail(vdc.getName(), store.getName(), vendor.getUuid());
                    if (result.isFailed()) {
                        continue;
                    }
                    DataStoreModel model = JSONObject.parseObject(JSONObject.toJSONString(result.getData()),
                            DataStoreModel.class);
                    if (null == store.getDistributableRatio()) {
                        diskTotal += store.getCapacity();
                    } else {
                        diskTotal += BigDecimal.valueOf(store.getCapacity()).multiply(store.getDistributableRatio())
                                .divide(BigDecimal.valueOf(100)).intValue();
                    }
                    diskFree += model.getFreeSpace();
                }
                newStoreHosts.add(storeHost);
            }
            if (ListTool.isEmpty(newStoreHosts)) {
                continue;
            }
            host.setStoreHosts(newStoreHosts);
            int diskUsed = diskTotal - diskFree;
            host.setDiskRemainder(Math.max((long) diskTotal - diskUsed, 0L));
            newHosts.add(host);
        }
        if (ListTool.isEmpty(newHosts)) {
            return new BocloudResult<>(false, "资源分配失败，未找到合适的宿主机");
        }
        switch (SchedulerPolicy.policy.valueOf(schedulerPolicy.getPolicy())) {
            case focus:
                // 对集群进行排序，如果用户选择资源密集型策略，则倒叙排列，然后取资源剩余最适合分配的那个集群，否则正序排列
                this.sort(newHosts, sort, true);
                // 资源集中式
                for (Server host : newHosts) {
                    logger.info("cpu:{},mem:{},disk:{}", host.getCpuRemainder(), host.getMemoryRemainder(), host.getDiskRemainder());
                    if (instance * cpu <= host.getCpuRemainder()
                            && memory.multiply(BigDecimal.valueOf(instance)).longValue() <= host.getMemoryRemainder()
                            && instance * disk <= host.getDiskRemainder()) {
                        for (int i = 0; i < instance; i++) {
                            JSONObject object = new JSONObject();
                            object.put("hostId", host.getId());
                            List<StoreHost> storeHosts = host.getStoreHosts();
                            Long storeId = storeHosts.get(new Random().nextInt(storeHosts.size())).getStoreId();
                            object.put("storeId", storeId);
                            object.put("vdcId", host.getVdcId());
                            resultObjs.add(object);
                        }
                        break;
                    }
                }
                break;
            case dispersion:
                this.sort(newHosts, sort, false);
                int i = 0;
                int j = 1;
                boolean flag = true;
                while (flag) {
                    if (i >= newHosts.size()) {
                        break;
                    }
                    Server host = newHosts.get(i);
                    Long cpuRemainder = host.getCpuRemainder();
                    Long memoryRemainder = host.getMemoryRemainder();
                    Long diskRemainder = host.getDiskRemainder();
                    if (cpu <= cpuRemainder && memory.longValue() <= memoryRemainder && disk <= diskRemainder) {
                        if (j > instance) {
                            flag = false;
                            break;
                        }
                        JSONObject object = new JSONObject();
                        object.put("hostId", host.getId());
                        List<StoreHost> storeHosts = host.getStoreHosts();
                        Long storeId = storeHosts.get(new Random().nextInt(storeHosts.size())).getStoreId();
                        object.put("storeId", storeId);
                        object.put("vdcId", host.getVdcId());
                        host.setCpuRemainder(cpuRemainder - cpu);
                        host.setMemoryRemainder(memoryRemainder - memory.longValue());
                        host.setDiskRemainder(diskRemainder - disk);
                        resultObjs.add(object);
                    } else {
                        i++;
                    }
                    j++;
                }
                break;
            case random:
                Server server = newHosts.get(new Random().nextInt(newHosts.size()));
                Long hostId = server.getId();
                List<StoreHost> storeHosts = server.getStoreHosts();
                for (int index = 0; index < instance; index++) {
                    Long storeId = storeHosts.get(new Random().nextInt(storeHosts.size())).getStoreId();
                    JSONObject object = new JSONObject();
                    object.put("hostId", hostId);
                    object.put("storeId", storeId);
                    object.put("vdcId", server.getVdcId());
                    resultObjs.add(object);
                }
                break;
            default:
                break;
        }
        if (ListTool.isEmpty(resultObjs)) {
            return new BocloudResult<>(false, "资源分配失败，可能是资源不足导致");
        }
        return new BocloudResult<>(true, resultObjs, "success");
    }

    /**
     * 对集群剩余资源进行排序
     *
     * @param hosts   宿主机列表
     * @param sort    排序字段
     * @param reverse 是否倒叙
     */
    private void sort(List<Server> hosts, String sort, Boolean reverse) {
        Collections.sort(hosts, (first, second) -> {
            if (null == first || second == null) {
                return 0;
            }
            Long preCpuRemainder = first.getCpuRemainder();
            Long preMemRemainder = first.getMemoryRemainder();
            Long preDiskRemainder = first.getDiskRemainder();
            Long nextCpuRemainder = second.getCpuRemainder();
            Long nextMemRemainder = second.getMemoryRemainder();
            Long nextDiskRemainder = second.getDiskRemainder();
            if (reverse) {
                preCpuRemainder = second.getCpuRemainder();
                preMemRemainder = second.getMemoryRemainder();
                preDiskRemainder = second.getDiskRemainder();
                nextCpuRemainder = first.getCpuRemainder();
                nextMemRemainder = first.getMemoryRemainder();
                nextDiskRemainder = first.getDiskRemainder();
            }
            if ("Cpu".equalsIgnoreCase(sort)) {
                return nextCpuRemainder.compareTo(preCpuRemainder);
            } else if ("Memory".equalsIgnoreCase(sort)) {
                return nextMemRemainder.compareTo(preMemRemainder);
            } else if ("Disk".equalsIgnoreCase(sort)) {
                return nextDiskRemainder.compareTo(preDiskRemainder);
            } else {
                return nextCpuRemainder.compareTo(preCpuRemainder);
            }
        });
    }

    /**
     * 根据vlan筛选宿主机
     *
     * @param vendorId
     * @param vlanId
     * @param instance
     * @param diskCategory
     * @param networkType
     * @return
     * @throws Exception
     */
    public BocloudResult<List<JSONObject>> byVlan(Long vendorId, Integer vlanId, Integer instance, String diskCategory,
                                                  String networkType) throws Exception {
        if (!StringUtils.isEmpty(diskCategory)) {
            logger.info("需要为系统盘过滤类型为【" + diskCategory + "】的数据存储");
        }
        // 查询vlan下的端口组
        List<PortGroup> portGroups = portGroupRepository.listByVlanId(vlanId, networkType, vendorId);
        Assert.notEmpty(portGroups, "根据IP池设置的VLAN未匹配到端口组");
        String networkName = null;
        String portType = null;
        Long hostId = null;
        for (PortGroup pg : portGroups) {
            if ("DVPORT".equals(pg.getType())) {
                Long networkId = pg.getNetworkId();
                if (null == networkId) {
                    continue;
                }
                List<NetworkHost> networkHosts = networkHostRepository.listByCategory(networkId, "PM");
                if (ListTool.isEmpty(networkHosts)) {
                    continue;
                }
                List<Server> hosts = new ArrayList<>();
                // 过滤宿主机
                for (NetworkHost networkHost : networkHosts) {
                    Server host = serverRepository.query(networkHost.getVhostId());
                    if (!BocloudStatus.Server.RUNNING.name().equals(host.getStatus())) {
                        continue;
                    }
                    if (StringUtils.isEmpty(host.getResourceLabel())
                            || !host.getResourceLabel().contains("Available")) {
                        continue;
                    }
                    ServerConfig config = serverConfigRepository.getBySid(host.getId());
                    if (null == config) {
                        continue;
                    }
                    hosts.add(host);
                }
                if (ListTool.isEmpty(hosts)) {
                    continue;
                }
                hostId = hosts.get(new Random().nextInt(hosts.size())).getId();
                portType = "DVPORT";
                networkName = pg.getName();
                break;
            } else {
                Switcher switcher = switcherRepository.query(pg.getSwitcherId());
                if (null == switcher) {
                    continue;
                }
                Server host = serverRepository.query(switcher.getHostId());
                if (null == host) {
                    continue;
                }
                if (!BocloudStatus.Server.RUNNING.name().equals(host.getStatus())) {
                    continue;
                }
                if (StringUtils.isEmpty(host.getResourceLabel()) || !host.getResourceLabel().contains("Available")) {
                    continue;
                }
                ServerConfig config = serverConfigRepository.getBySid(host.getId());
                if (null == config) {
                    continue;
                }
                hostId = host.getId();
                networkName = pg.getName();
                portType = "VIRTUAL";
                break;
            }
        }
        logger.info("此次分配的网络名称：{}", networkName);
        logger.info("此次分配的网络类型：{}", portType);
        Assert.notNull(hostId, "根据网络标签未找到合适的宿主机");
        Assert.notNull(networkName, "未找到合适的网络标签");
        List<StoreHost> storeHosts = storeHostRepository.list(hostId, "PM");
        List<StoreHost> newStoreHosts = new ArrayList<>();
        for (StoreHost storeHost : storeHosts) {
            DataStore store = dataStoreRepository.query(storeHost.getStoreId());
            if (null == store) {
                continue;
            }
            Vdc vdc = vdcRepository.query(store.getVdcId());
            if (null == vdc) {
                continue;
            }
            // 非活动的存储过滤掉
            if ("inactive".equals(store.getStatus())) {
                continue;
            }
//			if (!StringUtils.isEmpty(diskCategory)) {
//				if (StringUtils.isEmpty(store.getResourceLabel()) || !store.getResourceLabel().contains(diskCategory)) {
//					continue;
//				}
//			}
            newStoreHosts.add(storeHost);
        }
        Assert.notEmpty(newStoreHosts, "未找到合适的数据存储");
        List<JSONObject> resultObjs = new ArrayList<>();
        for (int index = 0; index < instance; index++) {
            Long storeId = newStoreHosts.get(new Random().nextInt(newStoreHosts.size())).getStoreId();
            JSONObject object = new JSONObject();
            object.put("hostId", hostId);
            object.put("storeId", storeId);
            object.put("networkName", networkName);
            object.put("portType", portType);
            resultObjs.add(object);
        }
        if (ListTool.isEmpty(resultObjs)) {
            return new BocloudResult<>(false, "资源分配失败，可能是资源不足导致");
        }
        return new BocloudResult<>(true, resultObjs, "success");
    }

    /**
     * 随机分配
     *
     * @param vendorId
     * @param instance
     * @param diskCategory
     * @return
     * @throws Exception
     */
    public BocloudResult<List<JSONObject>> byRandom(Long vendorId, Integer instance, String diskCategory)
            throws Exception {
        List<Server> hosts = serverRepository.listByVendor(vendorId);
        Assert.notNull(hosts, "宿主机列表为空");
        hosts = hosts.stream().filter(
                host -> !StringUtils.isEmpty(host.getResourceLabel()) && host.getResourceLabel().contains("Available"))
                .collect(Collectors.toList());
        Assert.notNull(hosts, "未找到标签为可用状态的宿主机");
        List<JSONObject> targetObjs = new ArrayList<>();
        for (int i = 0; i < instance; i++) {
            JSONObject targetObj = new JSONObject();
            Server host = hosts.get(new Random().nextInt(hosts.size()));
            List<StoreHost> storeHosts = storeHostRepository.list(host.getId(), "PM");
            List<StoreHost> newStoreHosts = new ArrayList<>();
            for (StoreHost storeHost : storeHosts) {
                DataStore store = dataStoreRepository.query(storeHost.getStoreId());
                if (null != store) {
                    Vdc vdc = vdcRepository.query(store.getVdcId());
                    if (null == vdc) {
                        continue;
                    }
                    // 非活动的存储过滤掉
                    if ("inactive".equals(store.getStatus())) {
                        continue;
                    }
//					if (!StringUtils.isEmpty(diskCategory)) {
//						if (StringUtils.isEmpty(store.getResourceLabel())
//								|| !store.getResourceLabel().contains(diskCategory)) {
//							continue;
//						}
//					}
                }
                newStoreHosts.add(storeHost);
            }
            Long storeId = storeHosts.get(new Random().nextInt(newStoreHosts.size())).getStoreId();
            targetObj.put("hostId", host.getId());
            targetObj.put("storeId", storeId);
            targetObj.put("vdcId", host.getVdcId());
            targetObjs.add(targetObj);
        }
        if (ListTool.isEmpty(targetObjs)) {
            return new BocloudResult<>(false, "资源分配失败，可能是资源不足导致");
        }
        return new BocloudResult<>(true, targetObjs, "success");
    }

}
