package com.bocloud.ims.service.ecs;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.bocloud.cmp.driver.compute.ServerDriver;
import com.bocloud.cmp.driver.storage.VolumeDriver;
import com.bocloud.cmp.entity.enums.BocloudStatus;
import com.bocloud.cmp.entity.enums.ResourceCategory;
import com.bocloud.cmp.model.ServerModel;
import com.bocloud.cmp.model.ServerVolumeModel;
import com.bocloud.cmp.model.VMBean;
import com.bocloud.cmp.model.VolumeModel;
import com.bocloud.common.enums.ResourceCatalog;
import com.bocloud.common.model.*;
import com.bocloud.common.utils.Common;
import com.bocloud.common.utils.ListTool;
import com.bocloud.common.utils.MapTools;
import com.bocloud.ims.entity.resource.*;
import com.bocloud.ims.interfaces.resource.CloudServerService;
import com.bocloud.ims.interfaces.resource.ResourceAllocateService;
import com.bocloud.ims.repository.resource.*;
import com.bocloud.ims.service.common.AbstractVmCreation;
import com.bocloud.ims.service.utils.PwdRandom;
import com.bocloud.ims.service.utils.ResourceEventPublisher;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.NoArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.apache.commons.lang3.StringUtils;

import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Component
@NoArgsConstructor
@Scope("prototype")
public class HuaweiVmCreation extends AbstractVmCreation {

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

    public HuaweiVmCreation(JSONObject params, CloudVendor vendor, RequestContext context,
                            CloudServerRepository cloudServerRepository, CloudServerService serverService, AmqpTemplate amqpTemplate,
                            ResourceEventPublisher resourceEventPublisher) {
        super(params, vendor, context, cloudServerRepository, serverService, amqpTemplate, resourceEventPublisher);
    }

    @Autowired
    private ImageRepository imageRepository;
    @Autowired
    private FlavorRepository flavorRepository;
    @Autowired
    private SubnetRepository subnetRepository;
    @Autowired
    private ServerDriver serverDriver;
    @Autowired
    private ServerLabelRepository serverLabelRepository;
    @Autowired
    private VolumeRepository volumeRepository;
    @Autowired
    private SecurityGroupRepository securityGroupRepository;
    @Autowired
    private ServerSgroupRepository serverSgroupRepository;
    @Autowired
    private ResourceRepository resourceRepository;
    @Autowired
    private ResourceEventRepository resourceEventRepository;
    @Autowired
    private KeypairRepository keypairRepository;
    @Autowired
    private ServerKeypairRepository serverKeypairRepository;
    @Autowired
    private ResourceAllocateService resourceAllocateService;
    @Autowired
    private VpcRepository vpcRepository;
    @Autowired
    private VolumeDriver volumeDriver;
    @Autowired
    private RegionRepository regionRepository;

    @Override
    public Result paramCheck() {
        String vpcUuid = params.getString("vpcId");
        if (StringUtils.isEmpty(vpcUuid)) {
            Long vpcId = params.getLong("networkId");
            Vpc vpc = vpcRepository.query(vpcId);
            if (null == vpc) {
                return new BocloudResult<ServerModel>(false, model, "VPC网络数据异常！");
            }
            vpcUuid = vpc.getVpcId();
        }
        params.put("vpcUuid", vpcUuid);

        Long subnetId = params.getLong("subnetId");
        Subnet subnet = subnetRepository.query(subnetId);
        if (null == subnet) {
            return new BocloudResult<ServerModel>(false, model, "子网不可以为空");
        }
        params.put("subnetUuid", subnet.getSubnetUuid());

        return Result.SUCCESS("success");
    }

    @Override
    public BocloudResult<ServerModel> prepare() {
        ServerModel model = new ServerModel();
        try {
            String groups = params.getString("groups");// 安全组
            Long imageId = params.getLong("imageId");// 镜像
            String password = params.getString("password");// 密码
            String keypairName = params.getString("keypairName");// 密钥
            String name = params.getString("name");// 虚拟机名称
            String zoneId = params.getString("zoneId");
            Integer rootSize = params.getInteger("rootSize");// 根磁盘大小

            String regionId = params.getString("regionId");
            // 自服务端申请
            Long diskSize = params.getLong("disk");
            model = new ServerModel();
            model.setRegionId(regionId);
            model.setCount(1);
            model.setVpcId(params.getString("vpcUuid"));
            model.setSubnetUuid(params.getString("subnetUuid"));

            Image image = null;
            String account = "root";
            if (null != imageId && 0L != imageId) {
                image = imageRepository.query(imageId);
                if (null == image) {
                    return new BocloudResult<ServerModel>(false, model, "镜像不存在");
                }
                model.setOsName(image.getOsName());
                model.setOsCategory(image.getOsType());
                model.setBootFromVolume(true);
                model.setImageUuid(image.getImageUuid());
                if ("Windows".equalsIgnoreCase(image.getOsType())) {
                    account = "Administrator";
                }
            }

            int imageMinDisk = image.getMinDisk().intValue();

            if (null == rootSize || rootSize <= 0) {
                rootSize = imageMinDisk;
            }
            if (rootSize < imageMinDisk) {
                return new BocloudResult<ServerModel>(false, model, "系统盘大小不允许小于镜像盘大小");
            }

            if (null != diskSize && diskSize > 0) {
                if (diskSize < imageMinDisk) {
                    return new BocloudResult<ServerModel>(false, model, "系统盘大小不允许小于镜像盘大小");
                } else {
                    rootSize = Integer.parseInt(diskSize + "");
                }
            }

            model.setRootSize(rootSize);
            model.setDisk(rootSize);
            model.setZone(zoneId);

            // 虚拟机标签
            if (!StringUtils.isEmpty(params.getString("labelIds"))) {
                List<Long> labelIds = JSONArray.parseArray(params.getString("labelIds"), Long.class);
                model.setLabelIds(labelIds);
            }

            model.setSystemDiskCategory(params.getString("systemDiskCategory"));
            // 数据盘
            JSONArray dataDisks = params.getJSONArray("diskDevices");
            if (null != dataDisks && dataDisks.size() > 0) {
                List<VolumeModel> volumeModels = JSONArray.parseArray(JSON.toJSONString(dataDisks), VolumeModel.class);
                if (ListTool.isEmpty(volumeModels)) {
                    return new BocloudResult<ServerModel>(false, model, "参数格式不正确");
                }
                model.setDiskDevices(volumeModels);
            }

            // 如果未设置密码则随机生成并保存
            if (StringUtils.isEmpty(keypairName) && StringUtils.isEmpty(password)) {
                password = randomStr(12);
            }
            if (!StringUtils.isEmpty(keypairName)) {
                // 查询密钥是否存在
                Keypair keypair = keypairRepository.getByName(keypairName, vendor.getId());
                if (null == keypair) {
                    return new BocloudResult<ServerModel>(false, model, "密钥不存在");
                }
            }
            model.setKeypairName(keypairName);
            // 规格
            String flavorUuid = params.getString("instanceType");
            Flavor flavor = flavorRepository.getByFlavorId(flavorUuid);
            if (null == flavor) {
                flavor = flavorRepository.query(params.getLong("flavorId"));
            }
            if (null == flavor) {
                return new BocloudResult<ServerModel>(false, model, "规格不存在");
            }
            flavorUuid = flavor.getFlavorUuid();
            model.setCpu(flavor.getCpu());
            model.setMemory(flavor.getMemory());
            List<String> securityGroups = Lists.newArrayList();
            if (!StringUtils.isEmpty(groups)) {
                securityGroups = JSONArray.parseArray(groups, String.class);
            }
            model.prepareData(name, account, password, flavorUuid, null, securityGroups);
            return new BocloudResult<ServerModel>(true, model, "success");
        } catch (Exception e) {
            logger.error("创建虚机封装model异常：" + e.getMessage());
            return new BocloudResult<ServerModel>(false, model, "fail");
        }
    }

    @Override
    public BocloudResult<CloudServer> builder(CloudServer cloudServer, ServerModel serverModel) {
        // 创建虚拟机
        BocloudResult result = null;
        try {
            // 操作记录
            Long userId = context.getTarget();
            String handler = "虚拟机创建";
            ResourceEvent event = null;
            Resource resource = null;
            // 创建虚拟机
            result = serverDriver.create(vendor.getUuid(), cloudServer.getRegionId(), model);
            if (result.isSuccess()) {
                event = new ResourceEvent(cloudServer.getId(), handler, "成功", userId, new Date(), "VM", vendor.getId(),
                        context.getCatalog());
                String content = JSON.toJSONString(result.getData());
                CloudServer server = JSON.parseObject(content, CloudServer.class);
                List<Volume> diskDevices = server.getDiskDevices();
                Integer diskSize = 0;
                for (Volume volume : diskDevices) {
                    BocloudResult volumeResult = volumeDriver.detail(volume.getVolumeUuid(), vendor.getUuid(),
                            server.getRegionId());
                    if (volumeResult.isSuccess()) {
                        volume = JSON.parseObject(JSON.toJSONString(volumeResult.getData()), Volume.class);
                    }
                    List<ServerVolumeModel> models = volume.getModels();
                    Map<String, ServerVolumeModel> serverVolumeMap = Maps.newHashMap();
                    if (!ListTool.isEmpty(models)) {
                        for (ServerVolumeModel model : models) {
                            serverVolumeMap.put(model.getInstanceId(), model);
                        }
                    }
                    diskSize += volume.getSize();
                    ServerVolumeModel model = serverVolumeMap.get(server.getInstanceId());
                    String device = null;
                    if (null != model) {
                        device = model.getDevice();
                    }
                    Volume existVolume = volumeRepository.getByVolumeUuid(volume.getVolumeUuid());
                    Region region = regionRepository.getByRegionId(volume.getRegionId(), vendor.getId());
                    if (null != region) {
                        volume.setRegion(region.getRegionId());
                    }

                    if (null == existVolume) {
                        volume.setCreatorId(userId);
                        volume.setGmtCreate(new Date());
                        volume.setVendorId(vendor.getId());
                        volume.setDevice(device);
                        volume.setServerId(cloudServer.getId());
                        volume.setRegionId(cloudServer.getRegionId());
                        volume.setTenantId(context.getTenant());
                        volume.setProjectId(context.getProject());
                        volume.setInstanceId(server.getInstanceId());
                        if ("/dev/vda".equals(device) || "/dev/sda".equals(device)) {
                            volume.setBootVolume(true);
                        }
                        volumeRepository.save(volume);
                    } else {
                        // 更新
                        existVolume.setStatus("IN_USE");
                        existVolume.setDevice(device);
                        existVolume.setGmtModify(new Date());
                        existVolume.setMenderId(context.getTarget());
                        existVolume.setServerId(cloudServer.getId());
                        existVolume.setTenantId(context.getTenant());
                        existVolume.setProjectId(context.getProject());
                        existVolume.setInstanceId(server.getInstanceId());
                        existVolume.setRegionId(cloudServer.getRegionId());
                        existVolume.setBootVolume(true);
                        volumeRepository.update(existVolume);
                    }
                }
                cloudServer.setDisk(model.getDisk());

                List<Volume> listByServerId = volumeRepository.listByServerId(cloudServer.getId());
                if (null != listByServerId && listByServerId.size() > 0) {
                    cloudServer.setDiskDevices(listByServerId);
                }

                cloudServer.setAccount(model.getAccount());
                cloudServer.setPswd(model.getPassword());
                cloudServer.setImageUuid(server.getImageUuid());
                cloudServer.setGroups(server.getGroups());
                cloudServer.setInstanceId(server.getInstanceId());
                cloudServer.setSubnetUuid(model.getSubnetUuid());
                cloudServer.setUuid(server.getUuid());
                cloudServer.setZone(server.getZone());
                cloudServer.setHostName(server.getHostName());
                cloudServer.setOsCategory(server.getOsCategory());
                cloudServer.setOsName(server.getOsName());
                cloudServer.setOsVersion(server.getOsVersion());
                cloudServer.setRegionId(server.getRegionId());
                cloudServer.setNetworkUuid(server.getNetworkUuid());
                String flavorUuid = server.getFlavorUuid();
                if (!StringUtils.isEmpty(flavorUuid)) {
                    Flavor flavor = flavorRepository.getByFlavorUuid(flavorUuid);
                    if (null != flavor) {
                        cloudServer.setCpu(flavor.getCpu());
                        cloudServer.setMemory(flavor.getMemory());
                    }
                }
                cloudServer.setKeypairName(server.getKeypairName());
                cloudServer.setFlavorUuid(flavorUuid);
                cloudServer.setGmtVmCreate(server.getGmtVmCreate());
                cloudServer.setManagerIp(server.getManagerIp());
                cloudServer.setRegion(server.getRegion());
                cloudServer.setRecycle(false);
                String state = server.getStatus();
                logger.info("Start to update vm status...");
                updateStatus(cloudServer, state);
                logger.info("End to update vm status...");
                if (state.equalsIgnoreCase(BocloudStatus.Server.EXCEPTION.name())
                        || state.equalsIgnoreCase(BocloudStatus.Server.SUSPENDED.name())) {
                    event = new ResourceEvent(cloudServer.getId(), handler, "失败", userId, new Date(), "VM",
                            vendor.getId(), context.getCatalog());
                }
                logger.info("Start to sync vm port...");
                // 设置内网所有ip
                cloudServer.setPrivateIps(server.getPrivateIps());
                // 保存安全组信息
                if (!StringUtils.isEmpty(server.getGroups())) {
                    logger.info("Start to sync vm security group...");
                    // 删除旧安全组与虚拟机的关系
                    serverSgroupRepository.removeBySid(cloudServer.getId());
                    List<String> groups = JSON.parseArray(server.getGroups(), String.class);
                    for (String group : groups) {
                        SecurityGroup securityGroup = securityGroupRepository.getByName(group, server.getRegionId(),
                                vendor.getId());
                        if (null != securityGroup) {
                            // 保存虚拟机和安全组关系
                            serverSgroupRepository.save(new ServerSgroup(cloudServer.getId(), securityGroup.getId()));
                        }
                    }
                    logger.info("End to sync vm security group...");
                }
                if (BocloudStatus.Server.ERROR.name().equals(cloudServer.getStatus())) {
                    cloudServer.setStatus(BocloudStatus.Server.EXCEPTION.name());
                }
                serverRepository.update(cloudServer);
                // 保存标签信息
                List<Long> labelIds = model.getLabelIds();
                if (!ListTool.isEmpty(labelIds)) {
                    List<ServerLabel> serverLabels = Lists.newArrayList();
                    for (Long labelId : labelIds) {
                        serverLabels.add(new ServerLabel(cloudServer.getId(), labelId, "VM", vendor.getId()));
                    }
                    if (!ListTool.isEmpty(serverLabels)) {
                        serverLabelRepository.batchSave(serverLabels);
                    }
                }
                // 保存资源
                List<String> ips = Lists.newArrayList();
                if (!StringUtils.isEmpty(cloudServer.getPrivateIps())) {
                    List<JSONObject> objects = JSON.parseArray(cloudServer.getPrivateIps(), JSONObject.class);
                    for (JSONObject object : objects) {
                        String address = (String) object.get("address");
                        ips.add(address);
                    }
                }
                resource = resourceRepository.query(ResourceCategory.Computer.name(), ResourceCatalog.LOGICAL.name(),
                        cloudServer.getId());
                if (null == resource) {
                    resource = new Resource(ResourceCategory.Computer.name(), ResourceCatalog.LOGICAL.name(),
                            cloudServer.getId(), JSON.toJSONString(ips), cloudServer.getOsCategory(), vendor.getId());
                    resource.preSave(cloudServer.getName(), cloudServer.getStatus(), userId, context.getTenant(),
                            context.getProject());
                    resource.setRegionId(cloudServer.getRegionId());
                    resource.setInventory(JSON.toJSONString(cloudServer));
                    resourceRepository.save(resource);
                } else {
                    resource.setInventory(JSON.toJSONString(cloudServer));
                    resource.setStatus(cloudServer.getStatus());
                    resource.setMenderId(userId);
                    resource.setCreatorId(userId);
                    resource.setGmtModify(new Date());
                    resource.setGmtCreate(new Date());
                    resource.setPrivateIps(JSON.toJSONString(ips));
                    resource.setRegionId(cloudServer.getRegionId());
                    resourceRepository.update(resource);
                }
            } else {
                logger.info("Remove the cloned virtual machine failure");
                event = new ResourceEvent(cloudServer.getId(), handler, "失败", userId, new Date(), "VM", vendor.getId(),
                        context.getCatalog());
                serverRepository.remove(cloudServer.getId(), userId);
                serverLabelRepository.deleteByTarget(cloudServer.getId(), "VM");
            }
            resourceEventRepository.save(event);
        } catch (Exception e) {
            logger.error("Get error message:", e);
            return new BocloudResult<CloudServer>(false, cloudServer, "create fail");
        }
        return result.isSuccess() ? new BocloudResult<CloudServer>(true, cloudServer, result.getMessage())
                : new BocloudResult<CloudServer>(false, cloudServer, result.getMessage());
    }

    @Override
    public BocloudResult processOrder(CloudServer server) {
        // JSONObject object = new JSONObject();
        // object.put("id", server.getId());
        // object.put("tenantId", server.getTenantId());
        // object.put("projectId", server.getProjectId());
        // object.put("skuId", server.getSkuId());
        // object.put("month", params.getInteger("month"));
        // return resourceAllocateService.assign(object,
        // ResourceCategory.Computer, context);
        return new BocloudResult(true, "分配成功！");
    }

    public Flavor getFlavorId() {
        Long cpu = params.getLong("cpu");
        Long memory = params.getLong("memory");
        Long vendorId = params.getLong("vendorId");
        if (null == cpu || null == memory) {
            return null;
        }
        List<Param> parameter = Lists.newArrayList();
        // 匹配
        parameter.add(new Param(MapTools.simpleMap("cpu", cpu), Sign.EQ));
        parameter.add(new Param(MapTools.simpleMap("memory", memory), Sign.EQ));
        parameter.add(new Param(MapTools.simpleMap("family", "CPU_V5_GC"), Sign.EQ));
        parameter.add(new Param(MapTools.simpleMap(Common.VENDORID, vendorId), Sign.EQ));
        // 排序
        Map<String, String> sorter = new HashMap<>();
        sorter.put("cpu", "0");
        List<Flavor> flavors = flavorRepository.list(parameter, sorter);
        if (ListTool.isEmpty(flavors) || null == flavors.get(0)) {
            return null;
        }
        return flavors.get(0);
    }

    /**
     * 返回随机字符串，同时包含数字、大小写字母
     *
     * @param len 字符串长度，不能小于3
     * @return String 随机字符串
     */
    public static String randomStr(int len) {
        if (len < 3) {
            throw new IllegalArgumentException("字符串长度不能小于3");
        }
        // 数组，用于存放随机字符
        char[] chArr = new char[len];
        // 为了保证必须包含数字、大小写字母
        chArr[0] = (char) ('0' + PwdRandom.uniform(0, 10));
        chArr[1] = (char) ('A' + PwdRandom.uniform(0, 26));
        chArr[2] = (char) ('a' + PwdRandom.uniform(0, 26));

        char[] codes = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I',
                'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c',
                'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
                'x', 'y', 'z'};
        // charArr[3..len-1]随机生成codes中的字符
        for (int i = 3; i < len; i++) {
            chArr[i] = codes[PwdRandom.uniform(0, codes.length)];
        }

        // 将数组chArr随机排序
        for (int i = 0; i < len; i++) {
            int r = i + PwdRandom.uniform(len - i);
            char temp = chArr[i];
            chArr[i] = chArr[r];
            chArr[r] = temp;
        }

        return new String(chArr);
    }

    /**
     * 更新状态
     *
     * @param server
     * @throws Exception
     */
    public void updateStatus(CloudServer server, String status) throws Exception {
        if (!status.equalsIgnoreCase(BocloudStatus.Server.BUILDING.name())) {
            server.setStatus(status);
            return;
        }
        Long start = System.currentTimeMillis();
        boolean running = true;
        while (running) {
            // 超时和延时
            if (System.currentTimeMillis() - start > 1000 * 60 * 10) {
                server.setStatus(BocloudStatus.Server.EXCEPTION.name());
                running = false;
            }
            BocloudResult result = serverDriver.detail(server.getInstanceId(), vendor.getUuid(), null);
            if (result.isSuccess()) {
                VMBean bean = JSONObject.parseObject(JSONObject.toJSONString(result.getData()), VMBean.class);
                String state = bean.getStatus();
                // 虚拟机为错误状态
                if (state.equalsIgnoreCase(BocloudStatus.Server.ERROR.name())) {
                    server.setStatus(BocloudStatus.Server.EXCEPTION.name());
                    running = false;
                    break;
                } else if (server.getStatus().equalsIgnoreCase(BocloudStatus.Server.BUILDING.name())) {
                    Thread.sleep(3000L);
                } else {
                    server.setStatus(state);
                    server.setGmtVmCreate(bean.getGmtVmCreate());
                    running = false;
                }
            } else {
                server.setStatus(BocloudStatus.Server.EXCEPTION.name());
                running = false;
            }
        }
    }

}
