package com.mortals.xhx.module.app.service.impl;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ZipUtil;
import com.mortals.framework.exception.AppException;
import com.mortals.framework.model.Context;
import com.mortals.framework.service.impl.AbstractCRUDServiceImpl;
import com.mortals.xhx.base.system.param.service.ParamService;
import com.mortals.xhx.base.system.upload.service.UploadService;
import com.mortals.xhx.common.code.AppTypeEnum;
import com.mortals.xhx.common.code.YesNoEnum;
import com.mortals.xhx.module.app.dao.AppPublishDao;
import com.mortals.xhx.module.app.model.AppPublishEntity;
import com.mortals.xhx.module.app.service.AppPublishService;
import com.mortals.xhx.utils.EncodeUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Date;
import java.util.Map;

/**
 * AppPublishService
 * 应用发布部署 service实现
 *
 * @author zxfei
 * @date 2023-02-01
 */
@Service("appPublishService")
public class AppPublishServiceImpl extends AbstractCRUDServiceImpl<AppPublishDao, AppPublishEntity, Long> implements AppPublishService {

    @Autowired
    private UploadService uploadService;

    @Autowired
    private ParamService paramService;

    @Override
    protected void saveBefore(AppPublishEntity entity, Context context) throws AppException {
        Map<String, String> appPublish = paramService.getParamByFirstOrganize("appPublish");
        String appName = appPublish.get(entity.getAppCode());
        entity.setAppName(appName);
        String targetPath = "/home/mortals/app/data/apps";
        String fileNewName = entity.getAppCode() + "-" + entity.getVersion() + "." + FileUtil.getSuffix(entity.getFilePath());

        //将zip包移动到指定目录 并更改名称
        String realFilePath = uploadService.getFilePath(entity.getFilePath());
        if (FileUtil.isEmpty(new File(realFilePath))) {
            throw new AppException("部署文件不存在！");
        }
        if (!FileUtil.getSuffix(entity.getFilePath()).equals("zip")) {
            throw new AppException("部署只支持zip文件！");
        }
        log.info("realFilePath:" + realFilePath);
        log.info("targetPath:" + targetPath + fileNewName);
        //移动文件并重命名
        FileUtil.move(new File(realFilePath), new File(targetPath + fileNewName), true);
        entity.setFilePath(targetPath + fileNewName);
        super.saveBefore(entity, context);
    }


    @Override
    protected void updateBefore(AppPublishEntity entity, Context context) throws AppException {
        super.updateBefore(entity, context);
        AppPublishEntity appPublishEntity = this.get(entity.getId());
        if (!entity.getFilePath().equals(appPublishEntity.getFilePath())) {
            Map<String, String> appPublish = paramService.getParamByFirstOrganize("appPublish");

            String appName = appPublish.get(entity.getAppCode());
            entity.setAppName(appName);

            String targetPath = "/home/mortals/app/data/apps";
            String fileNewName = entity.getAppCode() + "-" + entity.getVersion() + "." + FileUtil.getSuffix(entity.getFilePath());
            //将zip包移动到指定目录 并更改名称
            String realFilePath = uploadService.getFilePath(entity.getFilePath());
            if (FileUtil.isEmpty(new File(realFilePath))) {
                throw new AppException("部署文件不存在！");
            }
            if (!FileUtil.getSuffix(entity.getFilePath()).equals("zip")) {
                throw new AppException("部署只支持zip文件！");
            }
            log.info("realFilePath:" + realFilePath);
            log.info("targetPath:" + targetPath + fileNewName);
            //移动文件并重命名
            FileUtil.move(new File(realFilePath), new File(targetPath + fileNewName), true);
            entity.setFilePath(targetPath + fileNewName);
        }


    }

    @Override
    public void active(AppPublishEntity appPublishEntity, Context context) throws IOException {
        if (AppTypeEnum.前端.getValue() == appPublishEntity.getAppType()) {
            String targetFilePath = appPublishEntity.getFilePath();
            String disPath = "/home/publish/" + appPublishEntity.getAppCode();
            boolean directory = FileUtil.isDirectory(new File(disPath));
            if (directory) {
                FileUtil.del(new File(disPath));
            }
            String fileEncode = "UTF-8";
            try {
                fileEncode = EncodeUtil.getEncode(targetFilePath, true);
            } catch (Exception e) {
                log.error("异常", e);
            }
            ZipUtil.unzip(targetFilePath, disPath, Charset.forName(fileEncode));
            //更新
            appPublishEntity.setDistributeFilePath(disPath);
            appPublishEntity.setDistribute(YesNoEnum.YES.getValue());
            appPublishEntity.setUpdateTime(new Date());

        } else {
            //后端部署 执行sh 脚本程序部署
            //解压到临时目录
            String targetFilePath = appPublishEntity.getFilePath();
            String fileEncode = "UTF-8";
            try {
                fileEncode = EncodeUtil.getEncode(targetFilePath, true);
            } catch (Exception e) {
                log.error("异常", e);
            }
            String disPath = "/home/mortals/app/data/temp";
            ZipUtil.unzip(targetFilePath, disPath, Charset.forName(fileEncode));
            String scriptPath = "/home/mortals/app/data/temp/" + appPublishEntity.getAppCode() + "/deploy.sh";
            // RuntimeUtil.exec("chmod 755 "+scriptPath);
            ProcessBuilder sh = new ProcessBuilder("sh", scriptPath);
            File file = new File("/home/mortals/app/data/temp/" + appPublishEntity.getAppCode() + "/deploy.sh");
            asynExeLocalComand(file, sh);

        }
        this.update(appPublishEntity, context);

    }


    public void asynExeLocalComand(File file, ProcessBuilder pb) throws IOException {
        // 不使用Runtime.getRuntime().exec(command)的方式,因为无法设置以下特性
        // Java执行本地命令是启用一个子进程处理,默认情况下子进程与父进程I/O通过管道相连(默认ProcessBuilder.Redirect.PIPE)
        // 当服务执行自身重启的命令时,父进程关闭导致管道连接中断,将导致子进程也崩溃,从而无法完成后续的启动
        // 解决方式,(1)设置子进程IO输出重定向到指定文件;(2)设置属性子进程的I/O源或目标将与当前进程的相同,两者相互独立
        if (file == null || !file.exists()) {
            // 设置属性子进程的I/O源或目标将与当前进程的相同,两者相互独立
            pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
            pb.redirectError(ProcessBuilder.Redirect.INHERIT);
            pb.redirectInput(ProcessBuilder.Redirect.INHERIT);
        } else {
            // 设置子进程IO输出重定向到指定文件
            // 错误输出与标准输出,输出到一块
            pb.redirectErrorStream(true);
            // 设置输出日志
            pb.redirectOutput(ProcessBuilder.Redirect.appendTo(file));
        }
        // 执行命令进程
        pb.start();
    }

}