package com.bocloud.ims.service.installation;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.bocloud.cmp.entity.enums.installation.EndPoints;
import com.bocloud.cmp.entity.enums.installation.InstallImageType;
import com.bocloud.cmp.entity.enums.installation.InstallStatus;
import com.bocloud.cmp.entity.enums.installation.RaidType;
import com.bocloud.cmp.entity.enums.installation.TaskType;
import com.bocloud.cmp.model.installation.DriveId;
import com.bocloud.cmp.model.installation.InstallOptions;
import com.bocloud.cmp.model.installation.InstallResultBean;
import com.bocloud.cmp.model.installation.InstallResultBeans;
import com.bocloud.cmp.model.installation.Options;
import com.bocloud.cmp.model.installation.Raid;
import com.bocloud.cmp.model.installation.RaidOptions;
import com.bocloud.cmp.model.installation.User;
import com.bocloud.cmp.model.storage.StorageDiskModel;
import com.bocloud.common.encrypt.AESEncrptor;
import com.bocloud.common.enums.BoCloudMethod;
import com.bocloud.common.model.BocloudResult;
import com.bocloud.common.model.GridBean;
import com.bocloud.common.model.Pager;
import com.bocloud.common.model.Result;
import com.bocloud.common.model.SimpleBean;
import com.bocloud.common.utils.GridHelper;
import com.bocloud.common.utils.MapTools;
import com.bocloud.ims.common.utils.constant.Constant;
import com.bocloud.ims.entity.installation.InstallResult;
import com.bocloud.ims.entity.installation.InstallTask;
import com.bocloud.ims.entity.resource.Disk;
import com.bocloud.ims.entity.resource.Image;
import com.bocloud.ims.entity.resource.Mdisk;
import com.bocloud.ims.entity.resource.Server;
import com.bocloud.ims.entity.resource.ServerConfig;
import com.bocloud.ims.entity.resource.installation.ServerRaidCtrl;
import com.bocloud.ims.interfaces.installation.CatalogService;
import com.bocloud.ims.interfaces.installation.InstallTaskService;
import com.bocloud.ims.interfaces.installation.RackHdService;
import com.bocloud.ims.repository.installation.InstallResultRepository;
import com.bocloud.ims.repository.installation.InstallTaskRepository;
import com.bocloud.ims.repository.resource.ImageRepository;
import com.bocloud.ims.repository.resource.ServerConfigRepository;
import com.bocloud.ims.repository.resource.ServerRepository;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

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

/**
 * 任务管理实现类
 *
 * @author sxp
 * @since Nov 15, 2017
 */
@Service("installTaskService")
public class InstallTaskServiceImpl implements InstallTaskService {

    public static final String DELL = "Dell";
    public static final String SAS2 = "SAS2";
    public static final String SAS3 = "SAS3";
    private static Logger logger = LoggerFactory.getLogger(InstallTaskServiceImpl.class);

    @Value("${install.disk:}")
    private String installDisk;
    @Autowired
    private RackHdService rackHdService;
    @Value("${samba.repo:\\\\localhost\\windowsServer2012}")
    private String smbRepo;
    @Value("${samba.username:bocloud}")
    private String smbUser;
    @Value("${samba.password:bocloud}")
    private String smbPassword;
    @Autowired
    private CatalogService catalogService;
    @Autowired
    private InstallTaskRepository installTaskRepository;
    @Autowired
    private InstallResultRepository installResultRepository;
    @Autowired
    private ServerRepository serverRepository;
    @Autowired
    private ServerConfigRepository serverConfigRepository;
    @Autowired
    private ImageRepository imageRepository;

    /**
     * 查询任务历史
     *
     * @author sxp
     * @since Nov 16, 2017
     */
    @Override
    public BocloudResult<GridBean<InstallTask>> list(Pager pager) {
        List<InstallTask> list;
        List<SimpleBean> beans;
        GridBean gridBean;
        try {
            pager.getSorter().put("gmtStart", "1");
            if (pager.getSimple()) {
                beans = this.installTaskRepository.list(pager.getParams(), pager.getSorter());
                gridBean = new GridBean(1, 1, beans.size(), beans);
            } else {
                int total = this.installTaskRepository.count(pager.getParams());
                list = this.installTaskRepository.list(pager.getPage(), pager.getRows(), pager.getParams(), pager.getSorter());
                processTask(list);
                gridBean = GridHelper.getBean(pager.getPage(), pager.getRows(), total, list);
            }
            return new BocloudResult<>(true, gridBean, "查询成功");
        } catch (Exception e) {
            logger.error("Query task history failed:", e);
            return new BocloudResult<>(false, "查询失败");
        }
    }

    /**
     * 为任务历史增加额外的输出字段
     * <ul>
     * <li>targetHostNames: 该任务对应的物理机名称字符串</li>
     * </ul>
     *
     * @author sxp
     * @since Nov 16, 2017
     */
    private void processTask(List<InstallTask> list) throws Exception {
        if (list == null || list.isEmpty()) {
            return;
        }
        for (InstallTask task : list) {
            // targetHostNames: 该任务对应的物理机名称字符串
            List<String> hostNames = Lists.newArrayList();
            List<Long> nodeIds = JSONObject.parseArray(task.getNodes(), Long.class);
            for (Long nodeId : nodeIds) {
                Server server = serverRepository.query(nodeId);
                if (null == server) {
                    continue;
                }
                hostNames.add(server.getName());
            }
            task.setTargetHostNames(hostNames.toString());
            Image image = imageRepository.query(task.getImageId());
            if (image == null) {
                logger.warn("Get image id = {} failed.", task.getImageId());
                continue;
            }
            task.setImageName(image.getName());
        }
    }

    @Override
    public BocloudResult<InstallResultBeans> detail(Long id) {
        try {
            List<InstallResult> results = installResultRepository.queryByTaskId(id);
            if (results.size() == 0) {
                logger.warn("根据任务id查询结果为空");
                return new BocloudResult<>(false, "查询失败");
            }
            InstallResultBeans resultBeans = new InstallResultBeans();
            List<InstallResultBean> beans = Lists.newArrayList();
            int total = 0;
            int successes = 0;
            for (InstallResult result : results) {
                total++;
                Server server = serverRepository.query(result.getNodeId());
                if (null == server) {
                    return new BocloudResult<>(false, "查询失败");
                }
                ServerConfig serverConfig = serverConfigRepository.getBySid(server.getId());
                if (null == serverConfig) {
                    return new BocloudResult<>(false, "查询失败");
                }
                server.setConfig(serverConfig);

                long cost;
                if (result.getGmtFinish() == null) {
                    cost = (System.currentTimeMillis() - result.getGmtStart().getTime()) / 1000;
                } else {
                    cost = (result.getGmtFinish().getTime() - result.getGmtStart().getTime()) / 1000;
                }
                beans.add(new InstallResultBean(result.getName(), server.getConfig().getIpmiAddress(), cost,
                        result.getResult() == null ? "装机任务正在进行中" : result.getResult(),
                        InstallStatus.INSTALL_SUCCEED.getText().equals(result.getStatus()), result.getStatus()));
                if (InstallStatus.INSTALL_SUCCEED.getText().equals(result.getStatus())) {
                    successes++;
                }
            }
            resultBeans.setResults(beans);
            resultBeans.setSuccesses(successes);
            resultBeans.setTotal(total);
            return new BocloudResult<>(true, resultBeans, "查询成功");
        } catch (Exception e) {
            logger.error("Query task history detail failed:", e);
            return new BocloudResult<>(false, "查询失败");
        }
    }

    @Override
    public BocloudResult install(String rackHdNodeId, InstallOptions installOptions, InstallImageType imageType,
                                 Server server, Long taskId, Date startTime) {
        String thisInstallDisk = installDisk;
        if (StringUtils.isEmpty(thisInstallDisk)) {
            DriveId driveId = catalogService.driveId(rackHdNodeId);
            if (driveId == null) {
                logger.error("driveid信息为空");
                return new BocloudResult<>(false, "硬盘信息为空");
            }
            switch (imageType) {
                case CENTOS: thisInstallDisk = driveId.getLinuxWwid(); break;
                case UBUNTU: thisInstallDisk = driveId.getLinuxWwid(); break;
                case REDHAT: thisInstallDisk = driveId.getLinuxWwid(); break;
                case ESXI: thisInstallDisk = driveId.getEsxiWwid(); break;
                case SUSE: thisInstallDisk = driveId.getWwnWwid(); break;
                case WINDOWSSERVER: thisInstallDisk = null; break;
                default: return new BocloudResult<>(false, "不支持的装机类型");
            }
        }
        installOptions.setInstallDisk(thisInstallDisk);

        String taskName;
        switch (imageType) {
            case CENTOS: taskName = Constant.INSTALL_CENTOS; break;
            case UBUNTU: taskName = Constant.INSTALL_UBUNTU; break;
            case REDHAT: taskName = Constant.INSTALL_RHEL; break;
            case ESXI: taskName = Constant.INSTALL_ESXI; break;
            case SUSE: taskName = Constant.INSTALL_SUSE; break;
            case WINDOWSSERVER: taskName = Constant.INSTALL_WINDOWS; break;
            default: return new BocloudResult<>(false, "不支持的装机类型");
        }

        if (StringUtils.isNotEmpty(installOptions.getPassword())) {
            installOptions.setPassword(new AESEncrptor().decrypt(installOptions.getPassword(), null));
        }
        if (StringUtils.isNotEmpty(installOptions.getRootPassword())) {
            installOptions.setRootPassword(new AESEncrptor().decrypt(installOptions.getRootPassword(), null));
        }
        if (installOptions.getUsers() != null) {
            for (User user : installOptions.getUsers()) {
                if (user.getPassword() != null) {
                    user.setPassword(new AESEncrptor().decrypt(user.getPassword(), null));
                }
            }
        }
        Map<String, Object> data = Maps.newHashMap();
        Map<String, Object> defMap = Maps.newHashMap();
        if (InstallImageType.UBUNTU.name().equalsIgnoreCase(imageType.name())) {
            defMap.put(Constant.DEFAULTS_UBUNTU, installOptions);
        } else {
            defMap.put(Constant.DEFAULTS, installOptions);
        }
        data.put(Constant.OPTIONS, defMap);
        Map<String, String> paramMap = Maps.newHashMap();
        paramMap.put(Constant.NAME, taskName);
        logger.info("install params={}", JSONObject.toJSONString(data));
        Result result = rackHdService.request(JSONObject.toJSONString(data),
                rackHdService.buildUrl(EndPoints.NODE_WORKFLOWS_V2, rackHdNodeId, paramMap), BoCloudMethod.POST);

        if (result.isFailed()) {
            logger.error("下发装机任务给rackhd失败:{}", result.getMessage());
            return new BocloudResult<>(false, result.getMessage(), "下发装机任务给rackhd失败:" + result.getMessage());
        }

        JSONObject resultObject = JSONObject.parseObject(result.getMessage());
        InstallResult installResult = new InstallResult(server.getId(), taskId,
                resultObject.getString(Constant.INSTANCE_ID), "物理机[" + server.getName() + "]安装" + imageType,
                InstallStatus.INSTALLING.getText(), TaskType.OSINSTALL.name(), result.getMessage(), startTime);
        installResult.setPayload(JSON.toJSONString(data));
        try {
            installResultRepository.save(installResult);
        } catch (Exception e) {
            logger.error("下发装机任务给rackhd失败, 保存任务失败:{}", result.getMessage());
            return new BocloudResult<>(false, result.getMessage(), "下发装机任务给rackhd失败, 保存任务失败");
        }

        return new BocloudResult<>(true, result.getMessage(), "下发装机任务给rackhd成功");
    }

    private List<Raid> hddArr(List<Raid> config) {
        List<Raid> hddArr = new ArrayList<>();
        for (Raid raid : config) {
            List<String> drives = new ArrayList<>();
            for (Disk disk : JSONArray.parseArray(JSONObject.toJSONString(raid.getDisks()), Disk.class)) {
                drives.add(disk.getSlotId());
            }
            raid.setDrives(drives.toString());
            raid.setEnclosure(Integer.parseInt(raid.getDisks().get(0).getEnclosureId()));
            raid.setDisks(null);
            hddArr.add(raid);
        }
        return hddArr;
    }

    @Override
    public BocloudResult raid(Server server, Options options, Long taskId, Date startTime) {
        Map<String, String> paramMap = Maps.newHashMap();
        RaidOptions raidOptions = options.getRaidOptions();
        String graphName;
        try {
            if (server.getRaids().isEmpty()) {
                // catalog信息中没有raid信息，报错
                logger.error("物理机{}的raid信息异常，配置raid失败", server.getName());
                return new BocloudResult<>(false, "物理机" + server.getName() + "的raid信息异常，配置raid失败");
            }
            ServerRaidCtrl serverRaidCtrl = server.getRaids().get(0);
            // 根据raid卡的类型来区分参数
            if ("STORCLI".equalsIgnoreCase(serverRaidCtrl.getType())) {
                // storcli类型的卡
                raidOptions.setPath("/opt/MegaRAID/storcli/storcli64");
                graphName = "Graph.Bootstrap.Megaraid.Configure";
                raidOptions.setHddArr(hddArr(raidOptions.getConfig()));
                raidOptions.setSsdStoragePoolArr(new ArrayList<>());
                raidOptions.setSsdCacheCadeArr(new ArrayList<>());

                // LSI SAS3108最多支持64个RAID，创建10/50/60时，必须指定pdperarray参数
                raidOptions.getHddArr().forEach(raid -> {
                    if (RaidType.raid10.name().equalsIgnoreCase(raid.getType().name())) {
                        raid.setPdperarray(2);
                    } else if (RaidType.raid50.name().equalsIgnoreCase(raid.getType().name())) {
                        raid.setPdperarray(3);
                    } else if (RaidType.raid60.name().equalsIgnoreCase(raid.getType().name())) {
                        raid.setPdperarray(4);
                    }
                });
            } else if ("PERCCLI".equalsIgnoreCase(serverRaidCtrl.getType())) {
                // perccli类型的卡
                raidOptions.setPath("/opt/MegaRAID/perccli/perccli64");
                graphName = "Graph.Raid.Create.Perccli";
                raidOptions.setHddArr(hddArr(raidOptions.getConfig()));
                raidOptions.setSsdStoragePoolArr(new ArrayList<>());
                raidOptions.setSsdCacheCadeArr(new ArrayList<>());
            } else if ("HPSA".equalsIgnoreCase(serverRaidCtrl.getType())) {
                raidOptions.setPath(serverRaidCtrl.getPath());
                List<Raid> hddArr = new ArrayList<>();
                for (Raid raid : raidOptions.getConfig()) {
                    List<String> drives = raid.getDisks().stream().map(StorageDiskModel::getDiskUuid).collect(Collectors.toList());
                    raid.setDrives(drives.toString());
                    raid.setDisks(null);
                    hddArr.add(raid);
                }
                raidOptions.setHddArr(hddArr);
                raidOptions.setGroupArr(
                        serverRaidCtrl.getDiskGroups().stream().map(Mdisk::getMdiskUuid).collect(Collectors.toList()));
                graphName = "Graph.Bootstrap.Hpsaraid.Configure";
            } else if (SAS2.equalsIgnoreCase(serverRaidCtrl.getType())
                    || SAS3.equalsIgnoreCase(serverRaidCtrl.getType())) {
                raidOptions.setPath(serverRaidCtrl.getPath());
                raidOptions.setHddArr(hddArr(raidOptions.getConfig()));
                graphName = "Graph.Bootstrap.Sasraid.Configure";
            } else {
                logger.error("物理机{}的raid卡类型不支持，配置raid失败", server.getName());
                return new BocloudResult<>(false, "物理机" + server.getName() + "的raid卡类型不支持，配置raid失败");
            }
        } catch (Exception e) {
            logger.error("确认raid类型异常:", e);
            return new BocloudResult<>(false, "物理机" + server.getName() + "的raid信息异常，配置raid失败");
        }
        raidOptions.setConfig(null);
        paramMap.put(Constant.NAME, graphName);
        Map<String, Object> data = MapTools.simpleMap("options", MapTools.simpleMap("config-raid", raidOptions));
        logger.info("raid params={}", JSONObject.toJSONString(data));
        Result result = rackHdService.request(JSON.toJSONString(data),
                rackHdService.buildUrl(EndPoints.NODE_WORKFLOWS_V2, server.getRackhdNodeId(), paramMap),
                BoCloudMethod.POST);
        if (result.isFailed()) {
            logger.error("下发RAID配置任务失败:{}", result.getMessage());
            return new BocloudResult<>(false, result.getMessage(), "下发RAID配置任务失败:" + result.getMessage());
        }

        JSONObject resultObject = JSONObject.parseObject(result.getMessage());
        InstallResult installResult = new InstallResult(server.getId(), taskId,
                resultObject.getString(Constant.INSTANCE_ID), "物理机[" + server.getName() + "]配置RAID",
                InstallStatus.INSTALLING.getText(), TaskType.RAID.name(), result.getMessage(), startTime);
        installResult.setPayload(JSON.toJSONString(data));
        try {
            installResultRepository.save(installResult);
        } catch (Exception e) {
            logger.error("下发RAID配置任务失败, 保存任务失败:{}", result.getMessage());
            return new BocloudResult<>(false, result.getMessage(), "下发RAID配置任务失败, 保存任务失败");
        }

        return new BocloudResult<>(true, result.getMessage(), "下发RAID配置任务成功");
    }

    @Override
    public BocloudResult discovery(String rackHdNodeId) {
        Map<String, String> paramMap = Maps.newHashMap();
        paramMap.put(Constant.NAME, "Graph.rancherDiscovery");
        Result result = rackHdService.request(null,
                rackHdService.buildUrl(EndPoints.NODE_WORKFLOWS_V2, rackHdNodeId, paramMap), BoCloudMethod.POST);
        return new BocloudResult<>(result.isSuccess(), result.getMessage(),
                result.isSuccess() ? "下发重新发现机器成功" : "下发重新发现机器失败:" + result.getMessage());
    }
}
