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


import cd.casic.ci.process.common.WorkAtom;
import cd.casic.ci.process.dto.req.report.ReportAssetTaskReq;
import cd.casic.ci.process.dto.resp.report.ReportResp;
import cd.casic.ci.process.engine.context.ConstantContextHolder;
import cd.casic.ci.process.engine.postHandler.ExecuteTaskPostHandler;
import cd.casic.ci.process.engine.runContext.BaseRunContext;
import cd.casic.ci.process.engine.runContext.TaskRunContext;
import cd.casic.ci.process.engine.worker.base.HttpWorker;
import cd.casic.ci.process.process.dao.pipeline.PipTaskDao;
import cd.casic.ci.process.process.dataObject.base.BaseIdReq;
import cd.casic.ci.process.process.dataObject.base.PipBaseElement;
import cd.casic.ci.process.process.dataObject.history.PipPipelineHisInstance;
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.pipeline.PipelineService;
import cd.casic.ci.process.process.service.report.impl.ReportServiceImpl;
import cd.casic.ci.process.process.service.target.impl.TargetVersionServiceImpl;
import cd.casic.ci.process.process.service.vulInfo.VulInfoService;
import cd.casic.framework.commons.exception.ServiceException;
import cd.casic.framework.commons.exception.enums.GlobalErrorCodeConstants;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.*;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ObjectUtils;
import org.springframework.web.client.RestTemplate;

import java.io.File;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.*;

/**
 * @author HopeLi
 * @version v1.0
 * @ClassName ScaSbomWorker
 * @Date: 2025/5/21 9:30
 * @Description:
 */
@Slf4j
@WorkAtom(taskType = "MIRROR")
public class ScaMirrorWorker extends HttpWorker {

    private static final int POLLING_INTERVAL = 5000; // 轮询间隔，单位：毫秒
    private static final int MAX_POLLING_TIMES = 100; // 最大退出次数

    @Resource
    private PipelineService pipelineService;

    @Resource
    private TargetVersionServiceImpl targetVersionService;

    @Resource
    private PipTaskDao pipTaskDao;
    @Resource
    private VulInfoService vulInfoService;

    @Resource
    private ReportServiceImpl reportService;

    @Override
    public void execute(TaskRunContext context) {
        int statusCode = 0;
        Map<String, Object> localVariables = context.getLocalVariables();
//        PipTaskLog pipTaskLog = (PipTaskLog) localVariables.get(EngineRuntimeConstant.LOG_KEY);

        PipBaseElement contextDef = context.getContextDef();
        append(context,"SCA-MIRROR节点开始执行");
        append(context,"SCA-MIRROR节点配置：" + contextDef);
        if (ObjectUtil.isEmpty(contextDef)) {
            log.error("未查询到节点[ScaMirror]配置");
            localVariables.put("statusCode", "-1");
        }
        if (ObjectUtil.isEmpty(contextDef)) {
            log.error("未查询到节点[ScaMirror]配置");
            append(context,"未查询到节点[ScaMirror]配置");
            localVariables.put("statusCode", "-1");
        }

        String filePath = "";
        if (contextDef instanceof PipTask pipTask){
            // 查询并下载目标文件
            String pipelineId = pipTask.getPipelineId();
            //根据流水线id查询流水线信息
            PipPipeline pipeline = pipelineService.getById(pipelineId);
            //根据目标id查询目标信息
            if (StringUtils.isEmpty(pipeline.getTargetVersionId())){
                throw new ServiceException(GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR.getCode(),"目标文件不存在");
            }
            TargetVersion targetVersion = targetVersionService.getById(pipeline.getTargetVersionId());
            filePath = targetVersion.getFilePath();

            try {


                File file = new File(filePath);
                if (!file.exists() || !file.canRead()) {
                    log.error("目标文件不存在或不可读");
                    localVariables.put("statusCode", "-1");
                    append(context,"目标文件不存在或不可读");
                }

                handleUpload(pipTask, file,context);
            }catch (Exception e){
                append(context,"==================SCA-MIRROR节点执行失败=================");
                append(context,"SCA-MIRROR节点执行失败失败，请检查当前节点配置!");
                append(context,e.getMessage());
                log.error("==================SCA-MIRROR节点执行失败=================",e);
                throw new ServiceException(GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR.getCode(),"SCA-MIRROR节点执行失败");
            }
            Integer scaTaskId = pipTask.getTaskProperties().get("scaTaskId") instanceof Integer ? ((Integer) pipTask.getTaskProperties().get("scaTaskId")) : null;
            String fileName = targetVersion.getFileName();
            postHandlerManager.registerPostHandler(new ExecuteTaskPostHandler(pipTask.getId(),pipelineId) {
                @Override
                public void executeAfterDone(PipPipelineHisInstance pipPipelineHisInstance) {
                    vulInfoService.scaToVulInfoSave(scaTaskId,pipeline.getTargetType(),fileName,"长沙市",pipPipelineHisInstance.getId(),taskId,"MIRROR");
                }
            });
        }

        localVariables.put("statusCode", statusCode + "");
//        localVariables.put(EngineRuntimeConstant.LOG_KEY, pipTaskLog);
    }

    private void handleUpload(PipTask pipTask, File file, BaseRunContext context) throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
        Map<String, Object> scaMirrorConfigInfo = pipTask.getTaskProperties();
        RestTemplate restTemplate = getRestTemplateWithoutSANCheck();
        String scaUploadUrl = ConstantContextHolder.getScaIp() + "/openapi/v1/image/detect-file";
        MultiValueMap<String, Object> body = buildRequestBody(scaMirrorConfigInfo, file);
        HttpHeaders headers = createHeaders();
        headers.setContentType(MediaType.MULTIPART_FORM_DATA);
        headers.add("OpenApiUserToken", ConstantContextHolder.getScaToken());
        HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, headers);

        log.info("SCA上传接口:" + scaUploadUrl);
        JSONObject response = restTemplate.postForObject(scaUploadUrl, requestEntity, JSONObject.class);
        String message = response.getString("message");

        if (message.equals("success")) {
            append(context,"===============SCA上传成功=================");
            JSONObject data = response.getJSONObject("data");
            Integer scaTaskId = data.getInteger("scaTaskId");
            append(context,"SCA-MIRROR节点执行成功，SCA任务ID为：" + scaTaskId);

            pollTaskStatus(restTemplate, scaTaskId,context,scaMirrorConfigInfo,pipTask);
        } else if (message.equals("应用已经存在")) {
            append(context,message);
            Integer oldScaTaskId = (Integer) scaMirrorConfigInfo.get("scaTaskId");
            Integer oldApplicationId = (Integer) scaMirrorConfigInfo.get("applicationId");
            append(context,"SCA-MIRROR节点应用已存在，重新开始执行检测任务，SCA任务ID为：" + oldScaTaskId);
            int restartResult = reStartTask(restTemplate, oldApplicationId,context);
            if (restartResult != 0) {
                append(context,"SCA-MIRROR节点执行失败");
                return;
            }
            pollTaskStatus(restTemplate, oldScaTaskId,context, scaMirrorConfigInfo, pipTask);
        } else {
            append(context,"==================SCA接口异常，调用失败=================");
            append(context,message);
            throw new ServiceException(GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR.getCode(),"SCA-MIRROR节点执行失败");
        }
    }

    private MultiValueMap<String, Object> buildRequestBody(Map<String, Object> scaMirrorConfigInfo, File file) {
        MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
        body.add("file", new FileSystemResource(file));
        body.add("projectName", scaMirrorConfigInfo.get("projectName"));
        body.add("applicationName", scaMirrorConfigInfo.get("applicationName"));

        // 敏感信息检测(非必填)
        if (!ObjectUtils.isEmpty(scaMirrorConfigInfo.get("sensitive"))){
            body.add("sensitive", scaMirrorConfigInfo.get("sensitive"));
        }
        // 二进制分析(非必填)
        if (!ObjectUtils.isEmpty(scaMirrorConfigInfo.get("binaryDetect"))){
            body.add("binaryDetect", scaMirrorConfigInfo.get("binaryDetect"));
        }
        return body;
    }

    /**
     * 创建请求头
     *
     * @return HttpHeaders
     */
    private HttpHeaders createHeaders() {
        HttpHeaders headers = new HttpHeaders();
        headers.add("OpenApiUserToken", ConstantContextHolder.getScaToken());
        return headers;
    }

    /**
     * 轮询请求任务状态
     *
     * @param restTemplate
     * @param scaTaskId
     * @param scaMirrorConfigInfo
     * @param pipTask
     */
    public void pollTaskStatus(RestTemplate restTemplate, Integer scaTaskId, BaseRunContext context, Map<String, Object> scaMirrorConfigInfo, PipTask pipTask) {
        int currentPollingTimes = 0;
        while (currentPollingTimes < MAX_POLLING_TIMES) {
            try {
                HttpHeaders headers = new HttpHeaders();
                headers.add("OpenApiUserToken", ConstantContextHolder.getScaToken());
                HttpEntity<String> requestEntity = new HttpEntity<>(null, headers);
                String scaStatusUrl = ConstantContextHolder.getScaIp() + "/openapi/v1/task/" + scaTaskId;
                ResponseEntity<JSONObject> response = restTemplate.exchange(scaStatusUrl, HttpMethod.GET, requestEntity, JSONObject.class);

                if (Objects.requireNonNull(response.getBody()).getString("message").equals("success")) {
                    //"status": 5, //状态 0-未审计 1-未检测 2-排队中 3-检测中 4-检测暂停 5-检测完成 6-检测超时 7-手动停止 8-检测异常 9-已删除 10-拉取中 11-停止中 12-下载中
                    int status = response.getBody().getJSONObject("data").getInteger("status");
                    append(context,"\"状态 0-未审计 1-未检测 2-排队中 3-检测中 4-检测暂停 5-检测完成 6-检测超时 7-手动停止 8-检测异常 9-已删除 10-拉取中 11-停止中 12-下载中\"");
                    log.info("当前任务状态: " + status);
                    append(context,"当前任务状态: " + status);
                    if (status == 5) {
                        System.out.println("任务已完成，停止轮询。");
                        log.info("任务已完成，停止轮询。");
                        append(context,"任务已完成，停止状态获取。" );

                        //回调报告生成接口
                        ReportAssetTaskReq req = new ReportAssetTaskReq();
                        req.setScaTaskId(scaTaskId);
                        ReportResp reportResp = reportService.assetReportTask(req);
                        if (!ObjectUtils.isEmpty(reportResp)){
                            if (reportResp.getMessage().equals("success")){
                                append(context,"回调报告生成接口成功");
                                scaMirrorConfigInfo.put("reportId",reportResp.getId());
                                scaMirrorConfigInfo.put("reportType",req.getType());
                            }else {
                                append(context,"回调报告生成接口失败,scaTaskId = " + scaTaskId + "=====错误原因=====" + reportResp.getMessage());
                            }
                        }

                        //插入报告数据到reportInfo
                        BaseIdReq baseIdReq = new BaseIdReq();
                        baseIdReq.setTaskId(pipTask.getId());
                        baseIdReq.setScaTaskId(String.valueOf(scaTaskId));
                        reportService.createVulListByScaTaskId(baseIdReq);

                        //更新scaTaskId数据到task表
                        scaMirrorConfigInfo.put("scaTaskId",scaTaskId);
                        pipTask.setTaskProperties(scaMirrorConfigInfo);
                        pipTaskDao.updateById(pipTask);

                        break;
                    }
                } else {
                    log.error("获取任务状态失败: " + response.getBody().getString("message"));
                    append(context,"获取任务状态失败" );
                    break;
                }
            } catch (Exception e) {
                log.error("获取任务状态时发生错误: " + e.getMessage());
                append(context,"获取任务状态时发生错误" );
            }
            try {
                // 轮询间隔 5 秒
                Thread.sleep(POLLING_INTERVAL);
            } catch (InterruptedException e) {
//                Thread.currentThread().interrupt();
                toBadEnding();
                log.error("轮询被中断: " + e.getMessage());
            }
            currentPollingTimes++;
        }
        System.out.println("停止轮询");
    }

    /**
     * 重新检测接口
     *
     * @param restTemplate
     * @param applicationId
     */
    public int reStartTask(RestTemplate restTemplate, Integer applicationId,  BaseRunContext context) {
        try {
            String url = ConstantContextHolder.getScaIp() + "/openapi/v1/task/batch/detect";
            HttpHeaders headers = new HttpHeaders();
            headers.add("OpenApiUserToken", ConstantContextHolder.getScaToken());
            headers.setContentType(MediaType.APPLICATION_JSON);
            headers.add("Accept", MediaType.APPLICATION_JSON.toString());
            Map<String, List<Integer>> param = new HashMap<>();
            param.put("applicationIds", Arrays.asList(applicationId));
            String s = JSON.toJSONString(param);
            HttpEntity<String> formEntry = new HttpEntity<>(s, headers);

            JSONObject res = restTemplate.postForObject(url, formEntry, JSONObject.class);
            if (res.getString("message").equals("success")) {
                log.info("重新检测成功");
                append(context,"重新检测成功");
                return 0;
            } else {
                log.error("重新检测失败");
                log.error(res.getString("message"));
                append(context,"重新检测失败:" + res.getString("message"));
                return -1;
            }
        } catch (Exception e) {
            log.error("重新检测失败");
            log.error(e.getMessage());
            append(context,"重新检测失败:" + e.getMessage());
        }
        return 0;
    }

}
