package com.bocloud.ims.service.builder;

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.*;
import com.bocloud.common.enums.ResourceCatalog;
import com.bocloud.common.model.BocloudResult;
import com.bocloud.common.model.RequestContext;
import com.bocloud.common.utils.ListTool;
import com.bocloud.ims.entity.resource.*;
import com.bocloud.ims.repository.resource.*;
import com.bocloud.ims.service.utils.PwdRandom;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

/**
 * 青云
 *
 * @author ymh
 * @时间 2019年10月29日
 */
public class QcloudServerBuilder implements ServerBuilder {

    private static Logger logger = LoggerFactory.getLogger(QcloudServerBuilder.class);
    private CloudServerRepository cloudServerRepository;
    private SubnetRepository subnetRepository;
    private ImageRepository imageRepository;
    private FlavorRepository flavorRepository;
    private VolumeRepository volumeRepository;
    private IpRepository ipRepository;
    private ResourceRepository resourceRepository;
    private ResourceEventRepository resourceEventRepository;
    private SecurityGroupRepository securityGroupRepository;
    private ServerSgroupRepository serverSgroupRepository;
    private KeypairRepository keypairRepository;
    private RegionRepository regionRepository;
    private ServerDriver serverDriver;
    private VolumeDriver volumeDriver;
    private RequestContext context;
    private CloudVendor vendor;
    private ServerModel model;
    private ServerKeypairRepository serverKeyPairRepository;

    public QcloudServerBuilder(CloudServerRepository cloudServerRepository, NetworkRepository networkRepository,
                               SubnetRepository subnetRepository, ImageRepository imageRepository, FlavorRepository flavorRepository,
                               VolumeRepository volumeRepository, PortRepository portRepository,
                               PortFixedIpRepository portFixedIpRepository, IpRepository ipRepository, IpPoolRepository ipPoolRepository,
                               ResourceRepository resourceRepository, ResourceEventRepository resourceEventRepository,
                               SecurityGroupRepository securityGroupRepository, ServerSgroupRepository serverSgroupRepository,
                               KeypairRepository keypairRepository,
                               RegionRepository regionRepository, ServerDriver serverDriver, VolumeDriver volumeDriver,
                               RequestContext context, CloudVendor vendor, ZoneRepository zoneRepository,
                               ServerKeypairRepository serverKeyPairRepository) {
        this.cloudServerRepository = cloudServerRepository;
        this.subnetRepository = subnetRepository;
        this.imageRepository = imageRepository;
        this.flavorRepository = flavorRepository;
        this.volumeRepository = volumeRepository;
        this.ipRepository = ipRepository;
        this.resourceRepository = resourceRepository;
        this.resourceEventRepository = resourceEventRepository;
        this.securityGroupRepository = securityGroupRepository;
        this.serverSgroupRepository = serverSgroupRepository;
        this.keypairRepository = keypairRepository;
        this.regionRepository = regionRepository;
        this.serverDriver = serverDriver;
        this.volumeDriver = volumeDriver;
        this.context = context;
        this.vendor = vendor;
        this.serverKeyPairRepository = serverKeyPairRepository;
    }

    @Override
    public BocloudResult<ServerModel> prepare(JSONObject object) throws Exception {
        try {
            String groupId = object.getString("groupId");// 安全组
            Long imageId = object.getLong("imageId");// 镜像
            Long flovorId = object.getLong("flavorId");// 规格
            String password = object.getString("password");// 密码
            String keypairName = object.getString("keypairName");// 密钥
            String name = object.getString("name");// 云主机名称
            String zoneId = object.getString("zoneId");
            Integer rootSize = object.getInteger("rootSize");// 根磁盘大小
            String vpcUuid = object.getString("networkId");
            Long subnetId = object.getLong("subnetId");
            String ip = object.getString("ip");
            String regionId = object.getString("regionId");
            model = new ServerModel();
            if (org.apache.commons.lang3.StringUtils.isEmpty(regionId)) {
                return new BocloudResult<>(false, model, "区域信息错误！");
            }
            model.setRegionId(regionId);
            model.setCount(1);
            Subnet subnet = subnetRepository.query(subnetId);
            if (null == subnet) {
                return new BocloudResult<>(false, model, "子网不可以为空");
            }
            List<NetworkBean> networkBeans = new ArrayList<NetworkBean>();
            NetworkBean networkBean = new NetworkBean();
            networkBean.setNetworkUuid(subnet.getSubnetUuid());
            if (org.apache.commons.lang3.StringUtils.isNotEmpty(ip)) {
                networkBean.setFixIp(ip);
            }
            networkBeans.add(networkBean);
            model.setVpcId(vpcUuid);
            model.setSubnetUuid(subnet.getSubnetUuid());
            Image image = null;
            String account = "root";
            if (null != imageId && 0L != imageId) {
                image = imageRepository.query(imageId);
                if (null == image) {
                    return new BocloudResult<>(false, model, "镜像不存在");
                }
                if ("PRIVATE".equalsIgnoreCase(image.getImageType())) {
                    model.setOsName(image.getOsName());
                } else {
                    model.setOsName(image.getName());
                }
                model.setImageUuid(image.getImageUuid());
                if ("Windows".equalsIgnoreCase(image.getOsType())) {
                    account = "Administrator";
                }
            }

            int imageMinDisk = image.getMinDisk().intValue();
            if (rootSize < imageMinDisk) {
                return new BocloudResult<>(false, model, "系统盘大小不允许小于镜像盘大小");
            }
            model.setRootSize(rootSize);
            model.setDisk(rootSize);
            model.setZone(zoneId);
            // 数据盘
            JSONArray dataDisks = object.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<>(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(), regionId);
                if (null == keypair) {
                    return new BocloudResult<>(false, model, "密钥不存在");
                }
                model.setKeypairName(keypair.getFingerprint());
            }
            // 规格
            Flavor flavor = flavorRepository.query(flovorId);
            if (null == flavor) {
                return new BocloudResult<>(false, model, "规格不存在");
            }
            model.setCpu(flavor.getCpu());
            model.setMemory(flavor.getMemory());
            List<String> securityGroups = Lists.newArrayList();
            if (null != groupId && !"".equals(groupId)) {
                SecurityGroup securityGroup = securityGroupRepository.getByGroupId(groupId);
                if (null == securityGroup) {
                    return new BocloudResult<ServerModel>(false, model, "安全组不存在！");
                }
                securityGroups.add(groupId);
            }
            model.prepareData(name, account, password, flavor.getFlavorUuid(), networkBeans, securityGroups);
        } catch (Exception e) {
            logger.error("Get error Message:", e);
            return new BocloudResult<>(false, model, "fail");
        }
        return new BocloudResult<>(true, model, "success");
    }

    /**
     * 回收IP
     *
     * @param networkBeans
     * @param vendorId
     * @return
     */
    private void recycleIp(List<NetworkBean> networkBeans, Long vendorId) {
        try {
            if (!ListTool.isEmpty(networkBeans)) {
                for (NetworkBean networkBean : networkBeans) {
                    List<AddressBean> addresses = networkBean.getAddresses();
                    if (ListTool.isEmpty(addresses)) {
                        continue;
                    }
                    for (AddressBean address : addresses) {
                        ipRepository.delete(address.getAddress(), address.getSubnetId(), vendor.getId());
                    }
                }
            }
        } catch (Exception e) {
            logger.error("recycle ip error:", e);
        }
    }

    @Override
    public BocloudResult<CloudServer> builder(CloudServer cloudServer) throws Exception {
        // 创建云主机
        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 = JSONObject.toJSONString(result.getData());
                CloudServer server = JSONObject.parseObject(content, CloudServer.class);
                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(model.getZone());
                cloudServer.setHostName(server.getHostName());
                cloudServer.setRegionId(model.getRegionId());
                cloudServer.setNetworkUuid(model.getVpcUuid());
                String flavorUuid = server.getFlavorUuid();
                if (!StringUtils.isEmpty(flavorUuid)) {
                    Flavor flavor = flavorRepository.getByFlavorUuid(flavorUuid);
                    if (null != flavor) {
                        cloudServer.setCpu(flavor.getCpu());
                        cloudServer.setMemory(flavor.getMemory());
                    }
                }
                Image image = imageRepository.getByImageUuid(server.getImageUuid());
                if (null != image) {
                    cloudServer.setOsCategory(image.getOsCategory());
                    cloudServer.setOsName(image.getOsName());
                    cloudServer.setOsVersion(image.getOsVersion());
                }
                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());
                }
                // 设置内网所有ip
                cloudServer.setPrivateIps(server.getPrivateIps());
                // 保存安全组信息
                if (!StringUtils.isEmpty(server.getGroups())) {
                    logger.info("Start to sync vm security group...");
                    // 删除旧安全组与云主机的关系
                    serverSgroupRepository.removeBySid(cloudServer.getId());
                    SecurityGroup securityGroup = securityGroupRepository.getByGroupId(server.getGroups());
                    if (null != securityGroup) {
                        // 保存云主机和安全组关系
                        serverSgroupRepository.save(new ServerSgroup(cloudServer.getId(), securityGroup.getId()));
                    }
                    logger.info("End to sync vm security group...");
                }
                if (!StringUtils.isEmpty(server.getKeypairName())) {
                    logger.info("Start to sync keypair ...");
                    String keypairName = server.getKeypairName();
                    if (null != keypairName && !"".equals(keypairName)) {
                        List<String> keyPairList = JSON.parseArray(server.getKeypairName(), String.class);
                        ServerKeypair serverKeypair = null;
                        for (String keyId : keyPairList) {
                            Keypair byFingerprint = keypairRepository.getByFingerprint(keyId,
                                    cloudServer.getVendorId());
                            if (null != byFingerprint) {
                                serverKeypair = new ServerKeypair();
                                serverKeypair.setKeypairId(byFingerprint.getId());
                                serverKeypair.setServerId(cloudServer.getId());
                                serverKeyPairRepository.save(serverKeypair);
                            }
                        }
                    }
                    logger.info("End to sync keypair ...");
                }
                if (BocloudStatus.Server.ERROR.name().equals(cloudServer.getStatus())) {
                    cloudServer.setStatus(BocloudStatus.Server.EXCEPTION.name());
                }
                cloudServerRepository.update(cloudServer);
                // 保存资源
                List<String> ips = Lists.newArrayList();
                if (!StringUtils.isEmpty(cloudServer.getPrivateIps())) {
                    List<JSONObject> objects = JSONObject.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(), JSONObject.toJSONString(ips), cloudServer.getOsCategory(),
                            vendor.getId());
                    resource.preSave(cloudServer.getName(), cloudServer.getStatus(), userId, context.getTenant(),
                            context.getProject());
                    resource.setRegionId(cloudServer.getRegionId());
                    resource.setInventory(JSONObject.toJSONString(cloudServer));
                    resource.setOsCategory(cloudServer.getOsName());
                    resourceRepository.save(resource);
                } else {
                    resource.setInventory(JSONObject.toJSONString(cloudServer));
                    resource.setStatus(cloudServer.getStatus());
                    resource.setMenderId(userId);
                    resource.setCreatorId(userId);
                    resource.setGmtModify(new Date());
                    resource.setGmtCreate(new Date());
                    resource.setPrivateIps(JSONObject.toJSONString(ips));
                    resource.setRegionId(cloudServer.getRegionId());
                    resource.setOsCategory(cloudServer.getOsName());
                    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());
                cloudServerRepository.remove(cloudServer.getId(), userId);
                // 回收IP
                recycleIp(model.getNetworkBeans(), vendor.getId());
            }
            resourceEventRepository.save(event);
        } catch (Exception e) {
            logger.error("Get error message:", e);
            // 回收IP
            recycleIp(model.getNetworkBeans(), vendor.getId());
            return new BocloudResult<>(false, cloudServer, "create fail");
        }
        return result.isSuccess() ? new BocloudResult<>(true, cloudServer, result.getMessage())
                : new BocloudResult<>(false, cloudServer, result.getMessage());
    }

    /**
     * 更新状态
     *
     * @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;
            }
        }
    }

    /**
     * 返回随机字符串，同时包含数字、大小写字母
     *
     * @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);
    }

}
