package com.bocloud.ims.service.builder;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.bocloud.cmp.driver.compute.ServerDriver;
import com.bocloud.cmp.driver.compute.ServerTemplateDriver;
import com.bocloud.cmp.driver.storage.VolumeDriver;
import com.bocloud.cmp.model.NetcardOperation;
import com.bocloud.cmp.model.NetworkCardModel;
import com.bocloud.cmp.model.ServerModel;
import com.bocloud.cmp.model.VolumeModel;
import com.bocloud.cmp.vm.model.VmGenericParam;
import com.bocloud.common.model.BocloudResult;
import com.bocloud.common.model.RequestContext;
import com.bocloud.ims.entity.resource.*;
import com.bocloud.ims.repository.resource.*;
import com.bocloud.ims.service.transporter.vendor.SmartxTransporter;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class SmartServerBuilder implements ServerBuilder {

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

    private final CloudVendorRepository cloudVendorRepository;
    private final ServerRepository serverRepository;
    private final CloudServerRepository cloudServerRepository;
    private final VolumeRepository volumeRepository;
    private final VolumeTemplateRepository volumeTemplateRepository;
    private final ImageRepository imageRepository;
    private final SwitcherRepository switcherRepository;
    private final NetworkRepository networkRepository;
    private final SubnetRepository subnetRepository;
    private final ServerVolumeRepository serverVolumeRepository;
    private final ServerImageRepository serverImageRepository;
    private final ServerDriver serverDriver;
    private final VolumeDriver volumeDriver;
    private final ServerTemplateDriver serverTemplateDriver;
    private final SmartxTransporter smartTransporter;
    private final RequestContext context;

    private CloudVendor vendor;
    private VmGenericParam vmGenericParam;
    private ServerModel serverModel;

    public SmartServerBuilder(CloudVendorRepository cloudVendorRepository, ServerRepository serverRepository,
                              CloudServerRepository cloudServerRepository, VolumeRepository volumeRepository,
                              VolumeTemplateRepository volumeTemplateRepository, ImageRepository imageRepository,
                              SwitcherRepository switcherRepository, NetworkRepository networkRepository,
                              SubnetRepository subnetRepository, ServerVolumeRepository serverVolumeRepository,
                              ServerImageRepository serverImageRepository, ServerDriver serverDriver,
                              VolumeDriver volumeDriver, ServerTemplateDriver serverTemplateDriver,
                              SmartxTransporter smartTransporter,
                              RequestContext context) {
        this.cloudVendorRepository = cloudVendorRepository;
        this.serverRepository = serverRepository;
        this.cloudServerRepository = cloudServerRepository;
        this.volumeRepository = volumeRepository;
        this.volumeTemplateRepository = volumeTemplateRepository;
        this.imageRepository = imageRepository;
        this.switcherRepository = switcherRepository;
        this.networkRepository = networkRepository;
        this.subnetRepository = subnetRepository;
        this.serverVolumeRepository = serverVolumeRepository;
        this.serverImageRepository = serverImageRepository;
        this.serverDriver = serverDriver;
        this.volumeDriver = volumeDriver;
        this.serverTemplateDriver = serverTemplateDriver;
        this.smartTransporter = smartTransporter;
        this.context = context;
    }

    @Override
    public BocloudResult<ServerModel> prepare(JSONObject object) throws Exception {
        try {
            VmGenericParam vmGenericParam = object.toJavaObject(VmGenericParam.class);
            Long sourceServerId = null;
            ServerModel serverModel = new ServerModel();

            if (vmGenericParam.getTemplateId() != null) {
                // 从模板创建云主机
                sourceServerId = vmGenericParam.getTemplateId();

                CloudServer template = cloudServerRepository.query(vmGenericParam.getTemplateId());
                Assert.notNull(template, "id为" + vmGenericParam.getTemplateId() + "云主机模板不存在");
                vmGenericParam.setVendorId(template.getVendorId());

                serverModel.setInstanceId(template.getInstanceId());

                if (vmGenericParam.getFullClone() == null) {
                    vmGenericParam.setFullClone(true);
                }
                if (vmGenericParam.getFullClone() && vmGenericParam.getServerId() == 0) {
                    return new BocloudResult<>(false, "完全拷贝克隆需要明确指定目标云主机所属主机");
                }
                serverModel.setFullClone(vmGenericParam.getFullClone());

                serverModel.setVmHostName(vmGenericParam.getVmHostName());
                serverModel.setPassword(vmGenericParam.getPassword());
            } else if (vmGenericParam.getId() != null) {
                // 从云主机克隆云主机
                sourceServerId = vmGenericParam.getId();

                CloudServer server = cloudServerRepository.query(vmGenericParam.getId());
                Assert.notNull(server, "id为" + vmGenericParam.getId() + "云主机不存在");
                vmGenericParam.setVendorId(server.getVendorId());

                serverModel.setInstanceId(server.getInstanceId());

                if (vmGenericParam.getFullClone() == null) {
                    vmGenericParam.setFullClone(true);
                }
                if (vmGenericParam.getFullClone() && vmGenericParam.getServerId() == 0) {
                    return new BocloudResult<>(false, "完全拷贝克隆需要明确指定目标云主机所属主机");
                }
                serverModel.setFullClone(vmGenericParam.getFullClone());
            } else {
                // 创建新的空白云主机
            }

            vendor = cloudVendorRepository.query(vmGenericParam.getVendorId());
            Assert.notNull(vendor, "id为" + vmGenericParam.getVendorId() + "云平台不存在");

            Assert.notNull(vmGenericParam.getName(), "需要为云主机指定名称");
            CloudServer vmbyName = cloudServerRepository.getByName(vmGenericParam.getName(), vendor.getId());
            Assert.isNull(vmbyName, "名为" + vmGenericParam.getName() + "的云主机已经存在，请重新命名");
            serverModel.setName(vmGenericParam.getName());

            if (vmGenericParam.getHa() == null) {
                vmGenericParam.setHa(true);
            }
            serverModel.setHa(vmGenericParam.getHa());

            Assert.notNull(vmGenericParam.getServerId(), "需要为云主机指定所属主机");
            if (vmGenericParam.getServerId() == 0) {
                serverModel.setAutoSchedule(true);
            } else {
                Server host = serverRepository.query(vmGenericParam.getServerId());
                Assert.notNull(host, "id为" + vmGenericParam.getServerId() + "主机不存在");
                serverModel.setHostIp(host.getVmotionIp());
            }

            Assert.notNull(vmGenericParam.getCpu(), "需要为云主机指定CPU配置");
            serverModel.setCpu(vmGenericParam.getCpu());

            Assert.notNull(vmGenericParam.getMemory(), "需要为云主机指定内存配置");
            serverModel.setMemory(vmGenericParam.getMemory());

            Assert.notNull(vmGenericParam.getFirmware(), "需要为云主机指定引导选项");
            serverModel.setFirmware(vmGenericParam.getFirmware());

            Assert.notNull(vmGenericParam.getStatus(), "参数status不能为空");
            serverModel.setStatus(vmGenericParam.getStatus());

            if (StringUtils.isNotEmpty(vmGenericParam.getRemark())) {
                serverModel.setRemark(vmGenericParam.getRemark());
            }

            Assert.notEmpty(vmGenericParam.getDiskDevices(), "需要为云主机定义虚拟盘");
            List<VolumeModel> disks = new ArrayList<>();
            for (VolumeModel diskDevice : vmGenericParam.getDiskDevices()) {
                VolumeModel disk = new VolumeModel();
                disk.setType(diskDevice.getType());

                if ("reserveDisk".equals(diskDevice.getType())) {
                    Assert.notNull(diskDevice.getId(), "需要指定要保留的虚拟卷");
                    Volume volume = volumeRepository.query(diskDevice.getId());
                    Assert.notNull(volume, "id为" + diskDevice.getId() + "的虚拟卷不存在");

                    ServerVolume serverVolume = serverVolumeRepository
                            .getByServerIdAndVolumeId(sourceServerId, volume.getId());
                    Assert.notNull(serverVolume, "id为" + diskDevice.getId() + "的虚拟卷并未挂载当前云主机");

                    disk.setPath(volume.getPath());
                    disk.setTemplateUuid(serverVolume.getTemplateUuid());

                    if (StringUtils.isNotEmpty(diskDevice.getName())) {
                        disk.setName(diskDevice.getName());
                    } else {
                        disk.setName(volume.getName());
                    }

                    if (StringUtils.isNotEmpty(diskDevice.getBus())) {
                        disk.setBus(diskDevice.getBus());
                    } else {
                        disk.setBus(serverVolume.getBus());
                    }
                } else if ("removeDisk".equals(diskDevice.getType())) {
                    Assert.notNull(diskDevice.getId(), "需要指定要删除的虚拟卷");
                    Volume volume = volumeRepository.query(diskDevice.getId());
                    Assert.notNull(volume, "id为" + diskDevice.getId() + "的虚拟卷不存在");

                    ServerVolume serverVolume = serverVolumeRepository
                            .getByServerIdAndVolumeId(sourceServerId, volume.getId());
                    Assert.notNull(serverVolume, "id为" + diskDevice.getId() + "的虚拟卷并未挂载当前云主机");

                    disk.setPath(volume.getPath());
                } else if ("reserveISO".equals(diskDevice.getType())) {
                    Assert.notNull(diskDevice.getDiffKey(), "需要指定要保留的ISO映像的KEY");
                    List<ServerImage> serverImages = serverImageRepository.listByServerId(sourceServerId);
                    ServerImage serverImage = serverImages.stream()
                            .filter(si -> diskDevice.getDiffKey().equals(si.getDiffKey())).findFirst().orElse(null);
                    Assert.notNull(serverImage, "KEY为" + diskDevice.getDiffKey() + "的ISO映像不存在");
                    disk.setKey(serverImage.getDiffKey());
                    disk.setBus(serverImage.getBus());

                    if (diskDevice.getId() != null) {
                        Image image = imageRepository.query(diskDevice.getId());
                        Assert.notNull(image, "id为" + diskDevice.getId() + "的ISO映像不存在");
                        disk.setPath(image.getFilePath());
                    }

                    if (diskDevice.getDisabled() != null) {
                        disk.setDisabled(diskDevice.getDisabled());
                    } else {
                        disk.setDisabled(serverImage.getDisabled());
                    }
                } else if ("removeISO".equals(diskDevice.getType())) {
                    Assert.notNull(diskDevice.getDiffKey(), "需要指定要删除的ISO映像的KEY");
                    List<ServerImage> serverImages = serverImageRepository.listByServerId(sourceServerId);
                    ServerImage serverImage = serverImages.stream()
                            .filter(si -> diskDevice.getDiffKey().equals(si.getDiffKey())).findFirst().orElse(null);
                    Assert.notNull(serverImage, "KEY为" + diskDevice.getDiffKey() + "的ISO映像不存在");
                    disk.setKey(serverImage.getDiffKey());

                    Image image = imageRepository.query(serverImage.getImageId());
                    Assert.notNull(image, "id为" + diskDevice.getId() + "的ISO映像不存在");
                    if ("HIDE".equals(image.getCatalog())) {
                        disk.setPath("");
                    } else {
                        disk.setPath(image.getFilePath());
                    }
                } else if ("newDisk".equals(diskDevice.getType())) {
                    Assert.notNull(diskDevice.getName(), "需要为新建的虚拟卷指定名称");
                    Volume volumeByName = volumeRepository.getByName(vendor.getId(), diskDevice.getName());
                    Assert.isNull(volumeByName, "名为" + diskDevice.getName() + "的虚拟卷已经存在，请重新命名");
                    disk.setName(diskDevice.getName());

                    Assert.notNull(diskDevice.getSize(), "需要为新建的虚拟卷指定容量");
                    disk.setSize(diskDevice.getSize());
                    disk.setBlockSize(1D);
                    disk.setBlockCount(diskDevice.getSize() * 1024L * 1024L * 1024L);

                    Assert.notNull(diskDevice.getBus(), "需要为新建的虚拟卷指定总线");
                    disk.setBus(diskDevice.getBus());

                    Assert.notNull(diskDevice.getVolumeTemplateId(), "需要为新建的虚拟卷指定存储策略");
                    VolumeTemplate volumeTemplate = volumeTemplateRepository.get(diskDevice.getVolumeTemplateId());
                    Assert.notNull(volumeTemplate, "id为" + diskDevice.getVolumeTemplateId() + "的存储策略不存在");
                    disk.setVolumeTemplateUuid(volumeTemplate.getUuid());
                } else if ("mountDisk".equals(diskDevice.getType())) {
                    Assert.notNull(diskDevice.getId(), "需要指定要挂载的虚拟卷");
                    Volume volume = volumeRepository.query(diskDevice.getId());
                    Assert.notNull(volume, "id为" + diskDevice.getId() + "的虚拟卷不存在");
                    disk.setPath(volume.getPath());

                    Assert.notNull(diskDevice.getBus(), "需要为新建的虚拟卷指定总线");
                    disk.setBus(diskDevice.getBus());
                } else if ("mountISO".equals(diskDevice.getType())) {
                    disk.setBus("ide");

                    Assert.notNull(diskDevice.getId(), "需要指定要挂载的ISO映像");
                    Image image = imageRepository.query(diskDevice.getId());
                    Assert.notNull(image, "id为" + diskDevice.getId() + "的ISO映像不存在");
                    disk.setPath(image.getFilePath());

                    Assert.notNull(diskDevice.getDisabled(), "需要参数diskDevices.disabled");
                    disk.setDisabled(diskDevice.getDisabled());
                } else {
                    return new BocloudResult<>(false, "未知的虚拟盘类型");
                }
                disks.add(disk);
            }
            serverModel.setDiskDevices(disks);

            Assert.notEmpty(vmGenericParam.getOperations(), "需要为云主机定义虚拟网卡");
            List<NetworkCardModel> networkCards = new ArrayList<>();
            for (NetcardOperation netcard : vmGenericParam.getOperations()) {
                NetworkCardModel networkCard = new NetworkCardModel();

                Assert.notNull(netcard.getNetworkId(), "需要为虚拟网卡指定网络");
                Network network = networkRepository.query(netcard.getNetworkId());
                Assert.notNull(network, "未找到id为" + netcard.getNetworkId() + "的虚拟网络");
                networkCard.setNetworkUuid(network.getNetworkUuid());

                Switcher switcher = switcherRepository.query(network.getSwitcherId());
                Assert.notNull(switcher, "未找到id为" + network.getSwitcherId() + "的虚拟交换机");
                networkCard.setOvsbrName(switcher.getOvsbrName());

                List<Subnet> subnet = subnetRepository.listByNetwork(network.getId());
                Assert.notEmpty(subnet, "未在网络" + network.getName() + "下找到任何子网");
                networkCard.setVlan(subnet.get(0).getVlan());

                Assert.notNull(netcard.getLink(), "参数operation.link不能为空");
                networkCard.setUsed("up".equals(netcard.getLink()));

                if (!"Auto".equals(netcard.getIpPolicy())) {
                    if ("Dhcp".equals(netcard.getIpPolicy())) {
                        networkCard.setDhcp(true);
                    } else if ("Manual".equals(netcard.getIpPolicy())) {
                        networkCard.setDhcp(false);
                        networkCard.setIpAddress(netcard.getIp().getIp());
                        networkCard.setNetmask(netcard.getIp().getMask());
                    }
                }

                networkCards.add(networkCard);
            }
            serverModel.setNetworkCards(networkCards);

            this.serverModel = serverModel;
            this.vmGenericParam = vmGenericParam;

            object.remove("id");

            return new BocloudResult<>(true, serverModel, "success");
        } catch (Exception e) {
            logger.error("Prepare Smart-X云主机失败：{}", e.getMessage(), e);
            return new BocloudResult<>(false, e.getMessage());
        }
    }

    @Override
    public BocloudResult<CloudServer> builder(CloudServer server) throws Exception {
        try {
            // 执行创建
            BocloudResult createResult;
            if (vmGenericParam.getTemplateId() != null) {
                // 从模板创建云主机
                createResult = serverTemplateDriver
                        .createServer(vendor.getUuid(), null, serverModel.getInstanceId(), serverModel);
                Assert.isTrue(createResult.isSuccess(), createResult.getMessage());
            } else if (vmGenericParam.getId() != null) {
                // 从云主机克隆云主机
                createResult = serverDriver.cloneAsVm(vendor.getUuid(), null, serverModel);
                Assert.isTrue(createResult.isSuccess(), createResult.getMessage());
            } else {
                // 创建空白云主机
                createResult = serverDriver.create(vendor.getUuid(), null, serverModel);
                Assert.isTrue(createResult.isSuccess(), createResult.getMessage());
            }
            String taskUuid = createResult.getData().toString();
            // 保存任务数据
            VendorTaskMsg taskMsg = smartTransporter.getTask(vendor, taskUuid, context.getTarget());
            Assert.isTrue(taskMsg.getSmartSucceed(), taskMsg.getSmartFinalErrorMessage());
            JSONObject taskResource = JSON.parseObject(taskMsg.getResources());
            // 获取云主机数据
            String serverUuid;
            if (vmGenericParam.getTemplateId() != null) {
                // 从模板创建云主机
                serverUuid = null;
                for (String key : taskResource.keySet()) {
                    JSONObject value = taskResource.getJSONObject(key);
                    if (value != null && value.containsKey("type") && "KVM_VM".equals(value.getString("type"))) {
                        serverUuid = key;
                    }
                }
            } else if (vmGenericParam.getId() != null) {
                // 从云主机克隆云主机
                serverUuid = taskMsg.getTargetUuid();
            } else {
                // 创建空白云主机
                serverUuid = taskMsg.getTargetUuid();
            }
            Assert.notNull(serverUuid, "未在任务信息中找到云主机uuid");

            BocloudResult serverResult = serverDriver.detail(serverUuid, vendor.getUuid(), null);
            Assert.isTrue(serverResult.isSuccess(), serverResult.getMessage());
            ServerModel serverModel = (ServerModel) serverResult.getData();
            // 查询所属宿主机
            String serverIp = serverModel.getServerVal();
            Server host = serverRepository.queryBy3Ip(serverIp);
            Assert.notNull(host, "未找到IP为" + serverIp + "的主机");
            // 保存云主机数据
            smartTransporter.convert(serverModel, server);
            server.setHostId(host.getId());
            server.setVendorId(vendor.getId());
            server.setVendorType(vendor.getType());
            cloudServerRepository.update(server);
            // 保存新建的虚拟卷数据
            for (Map.Entry<String, Object> taskResourceEntry : taskResource.entrySet()) {
                String taskResourceUuid = taskResourceEntry.getKey();
                if (taskResourceUuid.equals(server.getInstanceId())) {
                    continue;
                }
                BocloudResult volumeResult = volumeDriver.detail(taskResourceUuid, vendor.getUuid(), null);
                Assert.isTrue(volumeResult.isSuccess(), volumeResult.getMessage());
                VolumeModel volumeModel = (VolumeModel) volumeResult.getData();

                if (volumeModel != null) {
                    logger.info("查询名为[{}]smart-x卷大小为：{}", volumeModel.getName(), volumeModel.getSize());
                    Volume existsVolume = volumeRepository.getByVolumeUuid(volumeModel.getUuid());
                    if (existsVolume == null) {
                        String volumeTemplateUuid = volumeModel.getVolumeTemplateUuid();
                        VolumeTemplate volumeTemplate = volumeTemplateRepository.getByUuid(volumeTemplateUuid);
                        Assert.notNull(volumeTemplate, "未找到UUID为" + volumeTemplateUuid + "的存储策略");

                        Volume volume = new Volume();
                        smartTransporter.convert(volumeModel, volume);
                        volume.setVendorId(vendor.getId());
                        volume.setTemplateId(volumeTemplate.getId());
                        volume.setCreatorId(context.getTarget());
                        volume.setGmtCreate(new Date());
                        volume.setRegionId(server.getRegionId());
                        volume.setRegion(server.getRegionId());
                        volumeRepository.save(volume);
                    }
                } else {
                    logger.warn("任务{}返回的resource当中，{}不是虚拟卷，具体内容：{}",
                            taskMsg.getKey(), taskResourceUuid, JSON.toJSONString(taskResourceEntry.getValue()));
                }
            }
            // 保存云主机网卡数据
            smartTransporter.syncNetworkCard(server, serverModel.getNetworkCards(), context.getTarget());
            // 保存云主机关联ISO镜像
            List<VolumeModel> imageModels = serverModel.getDiskDevices().stream()
                    .filter(d -> "cdrom".equals(d.getType())).collect(Collectors.toList());
            smartTransporter.syncServerImage(server, imageModels, context.getTarget());
            // 保存云主机关联虚拟盘
            List<VolumeModel> volumeModels = serverModel.getDiskDevices().stream()
                    .filter(d -> "disk".equals(d.getType())).collect(Collectors.toList());
            smartTransporter.syncServerVolume(server, volumeModels, context.getTarget());

            return new BocloudResult<>(true, server, "success");
        } catch (Exception e) {
            logger.error("Build Smart-X云主机失败：{}", e.getMessage(), e);
            return new BocloudResult<>(false, server, e.getMessage());
        }
    }
}
