package cd.casic.ci.process.engine.worker.afl;

import cd.casic.ci.process.common.WorkAtom;
import cd.casic.ci.process.dto.req.resource.ResourceQueryReq;
import cd.casic.ci.process.dto.resp.resource.ResourceDetailResp;
import cd.casic.ci.process.engine.constant.AFLSlotCompileConstant;
import cd.casic.ci.process.engine.runContext.TaskRunContext;
import cd.casic.ci.process.engine.worker.base.DockerWorker;
import cd.casic.ci.process.process.dataObject.pipeline.PipPipeline;
import cd.casic.ci.process.process.dataObject.target.TargetVersion;
import cd.casic.ci.process.process.dataObject.task.PipTask;
import cd.casic.ci.process.process.service.target.TargetVersionService;
import cd.casic.ci.process.util.SftpUploadUtil;
import cd.casic.module.execute.docker.dataobject.dto.DockerEndpointDo;
import cd.casic.module.machine.dal.dataobject.MachineInfoDO;
import cd.casic.module.machine.utils.CryptogramUtil;
import com.alibaba.fastjson.JSON;
import com.jcraft.jsch.*;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;

import java.io.File;
import java.util.*;

import static cd.casic.ci.process.engine.constant.PipelineVariableConstant.*;


@WorkAtom(taskType = "AFL_SLOT_COMPILE")
@Slf4j
public class AFLSlotCompileWorker extends DockerWorker {
    @Resource
    private TargetVersionService targetVersionService;
    @Override
    public void execute(TaskRunContext context) {
        PipTask task = context.getContextDef() instanceof PipTask ? ((PipTask) context.getContextDef()) : null;
        // 每一个流水线不同目录互不干扰
        String workDir = AFL_DOCKER_WORK_DIR_PREFIX + task.getPipelineId();
        Map<String, Object> globalVariables = context.getGlobalVariables();
        if (!globalVariables.containsKey(workDir)) {
            globalVariables.put(AFL_DOCKER_WORK_DIR_KEY,workDir);
        }
        Map<String, Object> taskProperties = task.getTaskProperties();
        String managerId = taskProperties.get(AFLSlotCompileConstant.MANAGER_ID) instanceof String ? ((String) taskProperties.get(AFLSlotCompileConstant.MANAGER_ID)) : null;
        // ssh 上传目标文件
        ResourceDetailResp resourceById = resourceManagerService.findResourceDetailById(managerId);
        String imageName = taskProperties.get(AFLSlotCompileConstant.IMAGE_NAME) instanceof String ? ((String) taskProperties.get(AFLSlotCompileConstant.IMAGE_NAME)) : null;
        MachineInfoDO machineInfo = resourceById.getMachineInfo();
        DockerEndpointDo dockerInfo = resourceById.getDockerInfo();
        if (Objects.isNull(machineInfo)||Objects.isNull(dockerInfo)) {
            append(context,"该资源不支持docker或者ssh");
        }
        PipPipeline pipeline = (PipPipeline) getContextManager().getContext(task.getPipelineId()).getContextDef();
        // 获取目标文件
        TargetVersion targetVersion = targetVersionService.getById(pipeline.getTargetVersionId());
        String fileName = targetVersion.getFileName();
        append(context,"读取到当前文件名称:"+fileName);
        String filePath = targetVersion.getFilePath();
        File file = new File(filePath);
        if (!file.exists() || !file.canRead()) {
            log.error("目标文件不存在或不可读");
            append(context,"目标文件不存在或不可读");
            toBadEnding();
        }
        // 工作目录下的相对路径,/test 作为根路径
        // 卷挂载目录+流水线目录 + task编译目录
        String realPath = AFL_VOLUME_WORK_DIR_PREFIX+File.separator+ workDir;
        // 上传目标文件 到指定资源服务器
        try {
            append(context,"AFL编译，上传文件路径:"+realPath);
            SftpUploadUtil.uploadFileViaSftp(
                    machineInfo.getHostIp()
                    ,machineInfo.getSshPort(),machineInfo.getUsername(), CryptogramUtil.doDecrypt(machineInfo.getPassword()),"",filePath,realPath,file.getName());
        } catch (SftpUploadUtil.SftpUploadException e) {
            append(context,"上传文件失败，请确认资源信息是否有误:"+JSON.toJSONString(machineInfo));
            log.error("上传文件报错",e);
            toBadEnding();
        }
        // 执行预设命令 ,进入目录
        String allCommand = "docker run -v "+ AFL_VOLUME_WORK_DIR_PREFIX +":"+ AFL_DOCKER_BASE_DIR+"  -it "+imageName+" bash\n" +
                "cd "+ AFL_DOCKER_BASE_DIR+"\n"+// 进入到容器中的卷挂载目录
        "mkdir -p "+workDir+"\n"+
        "chmod -R o+rwx "+ workDir +"\n"+
        "cd "+workDir+"\n"+handleZipFile(fileName);
        Object commandScriptObj = taskProperties.get(AFLSlotCompileConstant.COMMAND_SCRIPT);
        String commandScript = commandScriptObj instanceof String ? ((String) commandScriptObj) : null;
        allCommand += commandScript+"\n";
        log.info("AFL插装编译容器内执行的命令：{}",allCommand);
        append(context,"AFL插装编译容器内执行的命令："+allCommand);
        dockerRun(allCommand,dockerInfo,context);
        // 更新全局变量
        // /test目录下当前流水线工作目录
        globalVariables.put(AFL_DOCKER_WORK_DIR_KEY,workDir);
        // 获取编译结果并存放
        List<String> compileResult = getCompileResult(context, realPath + File.separator + getSourceName(fileName), machineInfo);
//        if (Objects.isNull(getGlobalVariable(context, COMPILE_RESULT_KEY))) {
            // 如果全局变量不存在则放入全局变量中
            putGlobalVariable(context,COMPILE_RESULT_KEY,compileResult);;
//        }
        // 放入最近一级作用域
        putSecondStageVariable(context,COMPILE_RESULT_KEY,compileResult);
        // 放入 所选资源供测试用例生成，AFL模糊测试节点使用
//        if (Objects.isNull(getGlobalVariable(context, AFL_RESOURCE_MANAGER_ID_KEY))) {
            // 如果全局变量不存在则放入全局变量中
            putGlobalVariable(context, AFL_RESOURCE_MANAGER_ID_KEY,managerId);;
//        }
        putSecondStageVariable(context,AFL_RESOURCE_MANAGER_ID_KEY,managerId);
        // 放入镜像名称
//        if (Objects.isNull(getGlobalVariable(context, IMAGE_NAME))) {
//            // 如果全局变量不存在则放入全局变量中
            putGlobalVariable(context, IMAGE_NAME,imageName);
//        }
        putSecondStageVariable(context,IMAGE_NAME,imageName);
    }
    private String handleZipFile(String fileName){
        if (StringUtils.isEmpty(fileName)) {
            return "";
        }
        int i = fileName.lastIndexOf(".");
        if (i==-1||i>=fileName.length()-1) {
            return "";
        }
        String prefix = fileName.substring(0, i);
        String suffix = fileName.substring(i + 1);
        if ("zip".equals(suffix)) {
            String cmd ="unzip -o "+fileName+"\n" +
                    "chmod -R o+rwx "+prefix+"/\n"+
                    "cd "+prefix+"\n";
            return cmd;
        }
        if (fileName.contains(".tar")||fileName.contains(".tar.gz")) {
            String cmd ="tar -zxvf "+fileName+"\n" +
                    "chmod -R o+rwx "+prefix+"/\n"+
                    "cd "+fileName.replace(".tar.gz","").replace(".tar","")+"\n";
            return cmd;
        }
        return "";
    }

    private List<String> getCompileResult(TaskRunContext context, String path, MachineInfoDO machineInfo) {
        String host = machineInfo.getHostIp();
        String user = machineInfo.getUsername();
        append(context, "AFL编译完毕");
        List<String> fileNameList = null;

        try {
            JSch jsch = new JSch();
            Session session = jsch.getSession(user, host, 22);
            session.setPassword(CryptogramUtil.doDecrypt(machineInfo.getPassword()));
            session.setConfig("StrictHostKeyChecking", "no");
            session.connect();
            ChannelSftp sftpChannel = (ChannelSftp) session.openChannel("sftp");
            sftpChannel.connect();

            // 递归获取文件列表
            fileNameList = getFilesRecursively(sftpChannel, path, path);

            sftpChannel.disconnect();
            session.disconnect();
        } catch (JSchException e) {
            e.printStackTrace();
        }

        if (!CollectionUtils.isEmpty(fileNameList) && context != null) {
            // 放入全局上下文中
            append(context, "AFL编译完毕:" + JSON.toJSONString(fileNameList));
            return fileNameList;
        }
        return Collections.emptyList();
    }

    /**
     * 递归获取文件夹下的所有文件
     * @param sftpChannel SFTP通道
     * @param currentPath 当前路径
     * @param basePath 基础路径（用于计算相对路径）
     * @return 相对路径文件列表
     */
    private List<String> getFilesRecursively(ChannelSftp sftpChannel, String currentPath, String basePath) {
        List<String> fileList = new ArrayList<>();

        try {
            Vector<ChannelSftp.LsEntry> entries = sftpChannel.ls(currentPath);

            for (ChannelSftp.LsEntry entry : entries) {
                String fileName = entry.getFilename();

                // 跳过 . 和 .. 目录
                if (".".equals(fileName) || "..".equals(fileName)) {
                    continue;
                }

                String fullPath = currentPath + "/" + fileName;
                String relativePath = fullPath.substring(basePath.length());

                // 去掉开头的斜杠
                if (relativePath.startsWith("/")) {
                    relativePath = relativePath.substring(1);
                }

                if (entry.getAttrs().isDir()) {
                    // 如果是目录，递归获取子目录中的文件
                    List<String> subFiles = getFilesRecursively(sftpChannel, fullPath, basePath);
                    fileList.addAll(subFiles);
                } else {
                    // 如果是文件，检查是否没有扩展名（可能是二进制文件）
                    if (!fileName.contains(".")) {
                        fileList.add(relativePath);
                    }
                }
            }
        } catch (SftpException e) {
            e.printStackTrace();
        }

        return fileList;
    }
    public String getSourceName(String fileName){
        int dotIndex = fileName.lastIndexOf(".");
        if (dotIndex!=-1) {
            return fileName.substring(0, dotIndex);
        }
        return "";
    }
}
