package com.njcn.gather.detection.service.impl;


import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
import com.njcn.common.pojo.exception.BusinessException;
import com.njcn.gather.detection.handler.SocketContrastResponseService;
import com.njcn.gather.detection.handler.SocketDevResponseService;
import com.njcn.gather.detection.handler.SocketSourceResponseService;
import com.njcn.gather.detection.pojo.constant.DetectionCommunicateConstant;
import com.njcn.gather.detection.pojo.enums.DetectionResponseEnum;
import com.njcn.gather.detection.pojo.enums.SourceOperateCodeEnum;
import com.njcn.gather.detection.pojo.param.ContrastDetectionParam;
import com.njcn.gather.detection.pojo.param.FormalProgressParam;
import com.njcn.gather.detection.pojo.param.PreDetectionParam;
import com.njcn.gather.detection.pojo.param.SimulateDetectionParam;
import com.njcn.gather.detection.pojo.vo.FormalProgressVO;
import com.njcn.gather.detection.pojo.vo.SocketMsg;
import com.njcn.gather.detection.service.FormalProgressService;
import com.njcn.gather.detection.service.PreDetectionService;
import com.njcn.gather.detection.util.business.DetectionCommunicateUtil;
import com.njcn.gather.detection.util.socket.CnSocketUtil;
import com.njcn.gather.detection.util.socket.FormalTestManager;
import com.njcn.gather.detection.util.socket.SocketManager;
import com.njcn.gather.detection.util.socket.XiNumberManager;
import com.njcn.gather.detection.util.socket.cilent.NettyContrastClientHandler;
import com.njcn.gather.detection.util.socket.websocket.WebServiceManager;
import com.njcn.gather.device.pojo.enums.CheckResultEnum;
import com.njcn.gather.device.pojo.enums.CheckStateEnum;
import com.njcn.gather.device.pojo.enums.CommonEnum;
import com.njcn.gather.device.pojo.po.PqDev;
import com.njcn.gather.device.pojo.po.PqDevSub;
import com.njcn.gather.device.pojo.vo.PreDetection;
import com.njcn.gather.device.service.IPqDevService;
import com.njcn.gather.device.service.IPqDevSubService;
import com.njcn.gather.plan.pojo.enums.DataSourceEnum;
import com.njcn.gather.plan.pojo.po.AdPlan;
import com.njcn.gather.plan.pojo.po.AdPlanSource;
import com.njcn.gather.plan.service.IAdPlanService;
import com.njcn.gather.plan.service.IAdPlanSourceService;
import com.njcn.gather.report.pojo.enums.PowerIndexEnum;
import com.njcn.gather.script.pojo.param.PqScriptCheckDataParam;
import com.njcn.gather.script.pojo.param.PqScriptIssueParam;
import com.njcn.gather.script.pojo.po.SourceIssue;
import com.njcn.gather.script.service.IPqScriptCheckDataService;
import com.njcn.gather.script.service.IPqScriptDtlsService;
import com.njcn.gather.source.pojo.po.SourceInitialize;
import com.njcn.gather.source.service.IPqSourceService;
import com.njcn.gather.storage.service.SimAndDigHarmonicService;
import com.njcn.gather.storage.service.SimAndDigNonHarmonicService;
import com.njcn.gather.system.cfg.service.ISysTestConfigService;
import com.njcn.gather.system.config.PathConfig;
import com.njcn.gather.system.dictionary.pojo.enums.DictDataEnum;
import com.njcn.gather.system.dictionary.service.IDictDataService;
import com.njcn.web.utils.HttpServletUtil;
import com.njcn.web.utils.RequestUtil;
import io.netty.channel.Channel;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;


@Service
@RequiredArgsConstructor
@Slf4j
public class PreDetectionServiceImpl implements PreDetectionService {

    private final IPqDevService iPqDevService;
    private final IDictDataService dictDataService;
    private final IAdPlanService iAdPlanService;
    private final IAdPlanSourceService adPlanSourceService;
    private final IPqSourceService pqSourceService;
    private final IPqScriptDtlsService pqScriptDtlsService;

    private final SocketDevResponseService socketDevResponseService;
    private final SocketSourceResponseService socketSourceResponseService;
    private final SocketContrastResponseService socketContrastResponseService;
    private final IPqScriptCheckDataService iPqScriptCheckDataService;
    private final SocketManager socketManager;
    private final ISysTestConfigService sysTestConfigService;
    private final FormalProgressService formalProgressService;
    private final IPqDevSubService pqDevSubService;
    private final SimAndDigHarmonicService harmonicService;
    private final SimAndDigNonHarmonicService nonHarmonicService;


    //    @Value("${report.reportDir}")
//    private String alignDataFilePath;
    private final PathConfig pathConfig;


    @Override
    public void sourceCommunicationCheck(PreDetectionParam param) {
        // 参数校验，目前仅检查IP是否重复，后续可在里面扩展
        checkDevIp(param.getDevIds());
        //用于处理异常导致的socket通道未关闭，socket交互异常
        DetectionCommunicateUtil.checkCommunicateChannel(param);
        /*
         * 先组装源通讯协议
         * 查询计划什么模式的（除了对比式，其他都是一个计划对应一个源）
         */
        AdPlan plan = iAdPlanService.getById(param.getPlanId());
        param.setScriptId(plan.getScriptId());
        param.setErrorSysId(plan.getErrorSysId());
        param.setCode(String.valueOf(plan.getCode()));
        if (param.isFormalTestSelected()) {
            List<PqDevSub> devSubs = pqDevSubService.lambdaQuery()
                    .in(PqDevSub::getDevId, param.getDevIds())
                    .list();
            boolean hasSaved = devSubs.stream().anyMatch(this::isSavedFormalProgress);
            if (param.isResumeFormal() && !hasSaved) {
                throw new BusinessException(CommonResponseEnum.FAIL, "未找到正式检测暂存进度");
            }
        }
        if (param.isFormalTestSelected() && !param.isResumeFormal()) {
            FormalTestManager.resumeBaseFormalCheckTime = null;
            FormalTestManager.resumeFormalStartTime = null;
            FormalTestManager.resumeNextSort = null;
            FormalTestManager.resumeFormalPending = false;
        }
        if (param.isFormalTestSelected() && param.isResumeFormal()) {
            FormalTestManager.resumeBaseFormalCheckTime = iPqDevService.getMinFormalCheckTime(param.getDevIds());
            pqDevSubService.lambdaUpdate()
                    .set(PqDevSub::getCheckState, CheckStateEnum.CHECKING.getValue())
                    .in(PqDevSub::getDevId, param.getDevIds())
                    .update();
        }
        if (ObjectUtil.isNotNull(plan)) {
            String code = dictDataService.getDictDataById(plan.getPattern()).getCode();
            DictDataEnum dictDataEnumByCode = DictDataEnum.getDictDataEnumByCode(code);
            if (Objects.nonNull(dictDataEnumByCode)) {
                switch (dictDataEnumByCode) {
                    case DIGITAL:
                    case SIMULATE:
                        sendYtxSocket(param);
                        break;
                    case CONTRAST:
                        break;
                    default:
                        throw new BusinessException(DetectionResponseEnum.PLAN_PATTERN_NOT);
                }
            } else {
                throw new BusinessException(DetectionResponseEnum.PLAN_PATTERN_NOT);
            }
        } else {
            throw new BusinessException(DetectionResponseEnum.PLAN_NOT_EXIST);
        }

    }

    /**
     * 原本系数校准单独写的，现在合并了，该方法过期了，没有调用了
     */
    @Deprecated
    @Override
    public void coefficientCheck(PreDetectionParam param) {
        // 检测是否存在连接的通道，后期需要做成动态，如果组合中不是第一位，则不需要关闭，也不用初始化 todo....
        DetectionCommunicateUtil.checkCommunicateChannel(param);
        AdPlanSource planSource = adPlanSourceService.getOne(new LambdaQueryWrapper<AdPlanSource>()
                .eq(AdPlanSource::getPlanId, param.getPlanId())
        );
        if (ObjectUtil.isNotNull(planSource)) {
            SourceInitialize sourceParam = pqSourceService.getSourceInitializeParam(planSource.getSourceId());
            param.setSourceName(sourceParam.getSourceId());
            if (ObjectUtil.isNotNull(sourceParam)) {
                //开始组装socket报文请求头
                socketDevResponseService.initList(param);
                socketSourceResponseService.initList(param);
                SocketMsg<String> msg = new SocketMsg<>();
                msg.setRequestId(SourceOperateCodeEnum.YJC_YTXJY.getValue());
                msg.setOperateCode(SourceOperateCodeEnum.INIT_GATHER.getValue());
                msg.setData(JSON.toJSONString(sourceParam));
                param.setSourceId(sourceParam.getSourceId());
                // 使用智能发送工具类，自动管理连接
                socketManager.smartSendToSource(param, JSON.toJSONString(msg));
            } else {
                throw new BusinessException(DetectionResponseEnum.SOURCE_INFO_NOT);
            }
        } else {
            throw new BusinessException(DetectionResponseEnum.PLAN_AND_SOURCE_NOT);
        }
    }


    /**
     * 发送源通信校验Socket连接（数字式和模拟式检测模式）
     *
     * <p>该方法用于建立与程控源设备的Socket连接，进行源通信校验。主要流程：</p>
     * <ul>
     *   <li>1. 存储检测参数到全局管理器</li>
     *   <li>2. 根据计划ID获取计划源信息</li>
     *   <li>3. 获取源设备初始化参数</li>
     *   <li>4. 初始化设备和源响应服务列表</li>
     *   <li>5. 组装Socket请求报文</li>
     *   <li>6. 建立Netty客户端连接</li>
     * </ul>
     *
     * @param param 预检测参数，包含计划ID、用户ID等信息
     * @throws BusinessException 当计划源信息不存在或源初始化参数为空时抛出
     * @see SourceOperateCodeEnum#YJC_YTXJY 源通信校验操作码
     * @see SourceOperateCodeEnum#INIT_GATHER 初始化采集操作码
     */
    private void sendYtxSocket(PreDetectionParam param) {
        AdPlanSource planSource = adPlanSourceService.getOne(new LambdaQueryWrapper<AdPlanSource>().eq(AdPlanSource::getPlanId, param.getPlanId()));
        param.setSourceId(planSource.getSourceId());
        String loginName = RequestUtil.getLoginNameByToken();
        WebServiceManager.addPreDetectionParam(loginName, param);
        if (ObjectUtil.isNotNull(planSource)) {
            //获取源初始化参数
            SourceInitialize sourceParam = pqSourceService.getSourceInitializeParam(planSource.getSourceId());
            param.setSourceName(sourceParam.getSourceId());
            if (ObjectUtil.isNotNull(sourceParam)) {
                //开始组装socket报文请求头
                socketDevResponseService.initList(param);
                socketSourceResponseService.initList(param);
                SocketMsg<String> socketMsg = new SocketMsg<>();
                socketMsg.setRequestId(SourceOperateCodeEnum.YJC_YTXJY.getValue());
                socketMsg.setOperateCode(SourceOperateCodeEnum.INIT_GATHER.getValue());
                socketMsg.setData(JSON.toJSONString(sourceParam));
                //使用智能发送工具类，自动管理与源控程序的socket连接
                socketManager.smartSendToSource(param, JSON.toJSONString(socketMsg));
            } else {
                throw new BusinessException(DetectionResponseEnum.SOURCE_INFO_NOT);
            }
        } else {
            throw new BusinessException(DetectionResponseEnum.PLAN_AND_SOURCE_NOT);
        }
    }

    /**
     * 发送源通信校验Socket连接（仿真模式）
     *
     * <p>该方法专门用于仿真检测模式下的源通信校验。与普通模式的区别：</p>
     * <ul>
     *   <li>直接使用传入的sourceId获取源初始化参数</li>
     *   <li>不需要通过计划ID查询计划源信息</li>
     *   <li>适用于独立的源设备通信测试</li>
     * </ul>
     *
     * @param param 预检测参数，必须包含sourceId和userPageId
     * @throws BusinessException 当源初始化参数为空时抛出
     * @see #sendYtxSocket(PreDetectionParam) 普通检测模式的源通信校验
     * @see SourceOperateCodeEnum#YJC_YTXJY 源通信校验操作码
     * @see SourceOperateCodeEnum#INIT_GATHER 初始化采集操作码
     */
    private void sendYtxSocketSimulate(PreDetectionParam param) {
        SourceInitialize sourceParam = pqSourceService.getSourceInitializeParam(param.getSourceId());
        param.setSourceId(sourceParam.getSourceId());
        param.setSourceName(sourceParam.getSourceId());
        String loginName = RequestUtil.getLoginNameByToken();
        WebServiceManager.addPreDetectionParam(loginName, param);
        if (ObjectUtil.isNotNull(sourceParam)) {
            SocketMsg<String> socketMsg = new SocketMsg<>();
            socketMsg.setRequestId(SourceOperateCodeEnum.YJC_YTXJY.getValue());
            socketMsg.setOperateCode(SourceOperateCodeEnum.INIT_GATHER.getValue());
            socketMsg.setData(JSON.toJSONString(sourceParam));
            // 使用智能发送工具类，自动管理连接
            socketManager.smartSendToSource(param, JSON.toJSONString(socketMsg));
        } else {
            throw new BusinessException(DetectionResponseEnum.SOURCE_INFO_NOT);
        }
    }

    @Override
    public boolean temStopTest(String userPageId, boolean waitCurrentItem) {
        if (waitCurrentItem) {
            FormalTestManager.requestWaitPause();
            return true;
        }

        FormalTestManager.requestImmediatePause();
        SocketManager.ensureFirst(FormalTestManager.currentIssue);
        socketDevResponseService.initRestart();
        PreDetectionParam runtimeParam = WebServiceManager.getPreDetectionParam(userPageId);
        String pushUserPageId = runtimeParam != null && StrUtil.isNotBlank(runtimeParam.getUserPageId())
                ? runtimeParam.getUserPageId()
                : userPageId;
        WebServiceManager.sendDetectionMessage(pushUserPageId, "preStopTest", "stop", null, null);
        return true;
    }

    @Override
    public boolean restartTemTest(PreDetectionParam param) {
        PreDetectionParam runtimeParam = WebServiceManager.getPreDetectionParam(param.getUserPageId());
        if (runtimeParam == null) {
            if (StrUtil.isBlank(param.getSourceId()) || StrUtil.isBlank(param.getScriptId()) || StrUtil.isBlank(param.getCode())) {
                throw new BusinessException(CommonResponseEnum.FAIL, "未找到暂停检测上下文，请重新开始检测");
            }
            runtimeParam = param;
        }
        FormalTestManager.clearPauseState();
        socketDevResponseService.initRestart();
        List<SourceIssue> sourceIssueList = SocketManager.getSourceList();
        if (StrUtil.isBlank(runtimeParam.getSourceName()) && StrUtil.isNotBlank(runtimeParam.getSourceId())) {
            SourceInitialize sourceInitialize = pqSourceService.getSourceInitializeParam(runtimeParam.getSourceId());
            if (sourceInitialize == null) {
                throw new BusinessException(DetectionResponseEnum.SOURCE_INFO_NOT);
            }
            runtimeParam.setSourceName(sourceInitialize.getSourceId());
        }
        if (CollUtil.isNotEmpty(sourceIssueList)) {
            SourceIssue sourceIssues = SocketManager.getSourceList().get(0);
            String type = resolveFormalIssueType(sourceIssues);
            sendBigItemStartIfFirstFormalIssue(runtimeParam, sourceIssues, type);
            if (StrUtil.isNotBlank(sourceIssues.getOtherType())) {
                sourceIssues.setType(PowerIndexEnum.LF.getKey().equals(sourceIssues.getOtherType()) ? PowerIndexEnum.F.getKey() : PowerIndexEnum.V.getKey());
            }
            SocketMsg<String> xuMsg = new SocketMsg<>();
            xuMsg.setOperateCode(SourceOperateCodeEnum.OPER_GATHER.getValue());
            xuMsg.setData(JSON.toJSONString(sourceIssues));
            xuMsg.setRequestId(SourceOperateCodeEnum.FORMAL_REAL.getValue() + "&&" + type);
            SocketManager.sendMsg(runtimeParam.getUserPageId() + DetectionCommunicateConstant.SOURCE, JSON.toJSONString(xuMsg));
            // Resume_Success
        } else {
            //TODO 是否最终检测完成需要推送给用户  检测完成
            PqScriptCheckDataParam checkDataParam = new PqScriptCheckDataParam();
            checkDataParam.setScriptId(runtimeParam.getScriptId());
            checkDataParam.setIsValueTypeName(false);
            List<String> adType = iPqScriptCheckDataService.getValueType(checkDataParam);

            iPqDevService.updateResult(runtimeParam.getDevIds(), adType, runtimeParam.getCode(), runtimeParam.getUserId(), runtimeParam.getTemperature(), runtimeParam.getHumidity(), true);
            CnSocketUtil.quitSend(runtimeParam);
        }

        return true;
    }

    @Override
    public void saveFormalProgress(FormalProgressParam param) {
        if (param.getFormalCheckTime() == null) {
            throw new BusinessException(CommonResponseEnum.FAIL, "正式检测耗时不能为空");
        }
        PreDetectionParam preParam = WebServiceManager.getPreDetectionParam(param.getUserPageId());
        Map<String, Integer> checkResultMap = summarizeSavedFormalCheckResult(param, preParam);
        Integer formalCheckTime = Math.max(param.getFormalCheckTime(), 1);
        Map<Integer, List<String>> devIdsByResult = param.getDevIds().stream()
                .collect(Collectors.groupingBy(devId -> checkResultMap.getOrDefault(devId, CheckResultEnum.UNCHECKED.getValue())));
        devIdsByResult.forEach((checkResult, devIds) -> pqDevSubService.update(new LambdaUpdateWrapper<PqDevSub>()
                .set(PqDevSub::getCheckState, CheckStateEnum.CHECKING.getValue())
                .set(PqDevSub::getFormalCheckTime, formalCheckTime)
                .set(PqDevSub::getCheckResult, checkResult)
                .in(PqDevSub::getDevId, devIds)));
        refreshPlanTestState(param.getPlanId());

        if (preParam != null) {
            CnSocketUtil.quitSend(preParam);
        }
        SocketManager.clearFormalRuntime();
        FormalTestManager.clearFormalRuntime();
        WebServiceManager.removePreDetectionParam(param.getUserPageId());
    }

    @Override
    public FormalProgressVO formalProgress(FormalProgressParam param) {
        return formalProgressService.restoreAndRelease(param);
    }

    private Map<String, Integer> summarizeSavedFormalCheckResult(FormalProgressParam param, PreDetectionParam preParam) {
        Map<String, Integer> result = new HashMap<>();
        if (CollUtil.isEmpty(param.getDevIds())) {
            return result;
        }
        param.getDevIds().forEach(devId -> result.put(devId, CheckResultEnum.UNCHECKED.getValue()));

        AdPlan plan = iAdPlanService.getById(param.getPlanId());
        String code = preParam != null && StrUtil.isNotBlank(preParam.getCode())
                ? preParam.getCode()
                : plan != null && plan.getCode() != null ? String.valueOf(plan.getCode()) : null;
        String scriptId = preParam != null && StrUtil.isNotBlank(preParam.getScriptId())
                ? preParam.getScriptId()
                : plan != null ? plan.getScriptId() : null;
        if (StrUtil.isBlank(code) || StrUtil.isBlank(scriptId)) {
            return result;
        }

        List<Integer> completedSorts = resolveCompletedFormalSorts(preParam);
        if (completedSorts != null && CollUtil.isEmpty(completedSorts)) {
            return result;
        }

        Map<String, List<Integer>> resultFlags = new HashMap<>();
        mergeResultFlags(resultFlags, nonHarmonicService.resultFlagsByDevIdsAndSorts(code, scriptId, param.getDevIds(), completedSorts));
        mergeResultFlags(resultFlags, harmonicService.resultFlagsByDevIdsAndSorts(code, scriptId, param.getDevIds(), completedSorts));
        param.getDevIds().forEach(devId -> result.put(devId, toCheckResult(resultFlags.get(devId))));
        return result;
    }

    private List<Integer> resolveCompletedFormalSorts(PreDetectionParam preParam) {
        if (preParam == null) {
            return null;
        }
        List<SourceIssue> allIssues = buildAllFormalIssues(preParam);
        if (CollUtil.isEmpty(allIssues)) {
            return Collections.emptyList();
        }
        List<SourceIssue> remainingIssues = SocketManager.getSourceList();
        Set<Integer> remainingSorts = CollUtil.isEmpty(remainingIssues)
                ? Collections.emptySet()
                : remainingIssues.stream()
                .map(SourceIssue::getIndex)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        return allIssues.stream()
                .map(SourceIssue::getIndex)
                .filter(Objects::nonNull)
                .filter(sort -> !remainingSorts.contains(sort))
                .distinct()
                .sorted()
                .collect(Collectors.toList());
    }

    private List<SourceIssue> buildAllFormalIssues(PreDetectionParam param) {
        PqScriptIssueParam issueParam = new PqScriptIssueParam();
        issueParam.setPlanId(param.getPlanId());
        issueParam.setSourceId(param.getSourceName());
        issueParam.setDevIds(param.getDevIds());
        issueParam.setScriptId(param.getScriptId());
        issueParam.setIsPhaseSequence(CommonEnum.FORMAL_TEST.getValue());
        return pqScriptDtlsService.listSourceIssue(issueParam);
    }

    private void sendBigItemStartIfFirstFormalIssue(PreDetectionParam param, SourceIssue currentIssue, String type) {
        if (param == null || currentIssue == null || currentIssue.getIndex() == null || StrUtil.isBlank(type)) {
            return;
        }
        List<SourceIssue> allIssues = buildAllFormalIssues(param);
        if (isFirstFormalIssueOfBigItem(allIssues, currentIssue, type)) {
            WebServiceManager.sendDetectionMessage(param.getUserPageId(), type + CnSocketUtil.START_TAG, null, new ArrayList<>(), null);
        }
    }

    static boolean isFirstFormalIssueOfBigItem(List<SourceIssue> allIssues, SourceIssue currentIssue, String type) {
        if (CollUtil.isEmpty(allIssues) || currentIssue == null || currentIssue.getIndex() == null || StrUtil.isBlank(type)) {
            return false;
        }
        Integer firstSort = allIssues.stream()
                .filter(issue -> type.equals(resolveFormalIssueType(issue)))
                .map(SourceIssue::getIndex)
                .filter(Objects::nonNull)
                .min(Integer::compareTo)
                .orElse(null);
        return Objects.equals(firstSort, currentIssue.getIndex());
    }

    static String resolveFormalIssueType(SourceIssue issue) {
        if (issue == null) {
            return null;
        }
        return StrUtil.isNotBlank(issue.getOtherType()) ? issue.getOtherType() : issue.getType();
    }

    private void mergeResultFlags(Map<String, List<Integer>> target, Map<String, List<Integer>> source) {
        if (CollUtil.isEmpty(source)) {
            return;
        }
        source.forEach((devId, flags) -> target.computeIfAbsent(devId, key -> new ArrayList<>()).addAll(flags));
    }

    private Integer toCheckResult(List<Integer> resultFlags) {
        if (CollUtil.isEmpty(resultFlags)) {
            return CheckResultEnum.UNCHECKED.getValue();
        }
        List<Integer> effectiveFlags = resultFlags.stream()
                .filter(flag -> flag != null && flag != 4 && flag != 5)
                .collect(Collectors.toList());
        if (CollUtil.isEmpty(effectiveFlags)) {
            return CheckResultEnum.ACCORD.getValue();
        }
        boolean hasFailed = effectiveFlags.stream().anyMatch(flag -> !Objects.equals(flag, 1));
        return hasFailed ? CheckResultEnum.NOT_ACCORD.getValue() : CheckResultEnum.ACCORD.getValue();
    }

    private boolean isSavedFormalProgress(PqDevSub dev) {
        return CheckStateEnum.CHECKING.getValue().equals(dev.getCheckState())
                && ObjectUtil.isNotNull(dev.getFormalCheckTime())
                && dev.getFormalCheckTime() > 0;
    }

    private void refreshPlanTestState(String planId) {
        if (StrUtil.isBlank(planId)) {
            return;
        }
        List<String> planDevIds = iPqDevService.lambdaQuery()
                .eq(PqDev::getPlanId, planId)
                .list()
                .stream()
                .map(PqDev::getId)
                .collect(Collectors.toList());
        if (CollUtil.isEmpty(planDevIds)) {
            iAdPlanService.lambdaUpdate()
                    .set(AdPlan::getTestState, CheckStateEnum.UNCHECKED.getValue())
                    .set(AdPlan::getResult, CheckResultEnum.UNCHECKED.getValue())
                    .eq(AdPlan::getId, planId)
                    .update();
            return;
        }
        List<PqDevSub> devSubs = pqDevSubService.lambdaQuery()
                .in(PqDevSub::getDevId, planDevIds)
                .list();
        boolean allUnchecked = devSubs.stream()
                .allMatch(dev -> CheckStateEnum.UNCHECKED.getValue().equals(dev.getCheckState()));
        boolean allArchived = CollUtil.isNotEmpty(devSubs) && devSubs.stream()
                .allMatch(dev -> dev.getCheckState() != null && dev.getCheckState() >= CheckStateEnum.DOCUMENTED.getValue());
        Integer testState = allUnchecked
                ? CheckStateEnum.UNCHECKED.getValue()
                : allArchived ? CheckStateEnum.CHECKED.getValue() : CheckStateEnum.CHECKING.getValue();
        Integer planResult = resolvePlanCheckResult(devSubs);
        iAdPlanService.lambdaUpdate()
                .set(AdPlan::getTestState, testState)
                .set(AdPlan::getResult, planResult)
                .eq(AdPlan::getId, planId)
                .update();
    }

    private Integer resolvePlanCheckResult(List<PqDevSub> devSubs) {
        if (CollUtil.isEmpty(devSubs)) {
            return CheckResultEnum.UNCHECKED.getValue();
        }
        Set<Integer> resultSet = devSubs.stream()
                .map(PqDevSub::getCheckResult)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        if (resultSet.contains(CheckResultEnum.NOT_ACCORD.getValue())) {
            return CheckResultEnum.NOT_ACCORD.getValue();
        }
        if (resultSet.contains(CheckResultEnum.UNCHECKED.getValue()) || CollUtil.isEmpty(resultSet)) {
            return CheckResultEnum.UNCHECKED.getValue();
        }
        return CheckResultEnum.ACCORD.getValue();
    }

    @Override
    public void ytxCheckSimulate(SimulateDetectionParam param) {
        PreDetectionParam preDetectionParam = new PreDetectionParam();
        preDetectionParam.setSourceId(param.getSourceId());
        preDetectionParam.setUserPageId(param.getUserPageId());
        preDetectionParam.setSendWebMsg(true);

        DetectionCommunicateUtil.checkCommunicateChannel(preDetectionParam);
        sendYtxSocketSimulate(preDetectionParam);
    }

    @Override
    public void sendScript(SimulateDetectionParam param) {
        Channel channel = SocketManager.getChannelByUserId(param.getUserPageId() + DetectionCommunicateConstant.SOURCE);
        if (Objects.isNull(channel) || !channel.isActive()) {
            // 进行源通信连接
            PreDetectionParam preDetectionParam = new PreDetectionParam();
            preDetectionParam.setSourceId(param.getSourceId());
            preDetectionParam.setUserPageId(param.getUserPageId());
            preDetectionParam.setSendWebMsg(false);
            this.sendYtxSocketSimulate(preDetectionParam);
        }
        //组装源控制脚本
        PqScriptIssueParam issueParam = new PqScriptIssueParam();
        SourceInitialize sourceInitialize = pqSourceService.getSourceInitializeParam(param.getSourceId());
        issueParam.setSourceId(sourceInitialize.getSourceId());
        issueParam.setScriptId(param.getScriptId());
        issueParam.setType(1);
        issueParam.setIsPhaseSequence(SourceOperateCodeEnum.SIMULATE_TEST.getValue());

        List<SourceIssue> sourceIssues = pqScriptDtlsService.listSourceIssue(issueParam);
        sourceIssues = sourceIssues.stream()
                .filter(s -> s.getIndex().equals(param.getScriptIndex()))
                .collect(Collectors.toList());

        SourceIssue sourceIssue = sourceIssues.get(0);
        String type = sourceIssue.getType();
        if (StrUtil.isNotBlank(sourceIssue.getOtherType())) {
            sourceIssue.setType(PowerIndexEnum.LF.getKey().equals(sourceIssue.getOtherType()) ? PowerIndexEnum.F.getKey() : PowerIndexEnum.V.getKey());
            type = sourceIssue.getOtherType();
        }

        List<String> comm = sourceIssue.getDevValueTypeList();
        System.out.println("向装置下发的参数ddd>>>>>>>>" + comm);

        SocketMsg<String> socketMsg = new SocketMsg<>();
        socketMsg.setOperateCode(SourceOperateCodeEnum.OPER_GATHER.getValue());
        socketMsg.setRequestId(SourceOperateCodeEnum.FORMAL_REAL.getValue() + CnSocketUtil.STEP_TAG + type);
        socketMsg.setData(JSON.toJSONString(sourceIssues.get(0)));
        SocketManager.sendMsg(param.getUserPageId() + CnSocketUtil.SOURCE_TAG, JSON.toJSONString(socketMsg));
    }

    @Override
    public void closeTestSimulate(SimulateDetectionParam param) {
        Channel channel = SocketManager.getChannelByUserId(param.getUserPageId() + DetectionCommunicateConstant.SOURCE);
        if (Objects.isNull(channel) || !channel.isActive()) {
            throw new BusinessException(DetectionResponseEnum.SOURCE_NOT_CONNECT);
        }
        SourceInitialize sourceParam = pqSourceService.getSourceInitializeParam(param.getSourceId());
        PreDetectionParam preDetectionParam = new PreDetectionParam();
        preDetectionParam.setSourceId(sourceParam.getSourceId());
        preDetectionParam.setSourceName(sourceParam.getSourceId());
        preDetectionParam.setUserPageId(param.getUserPageId());
        CnSocketUtil.quitSendSource(preDetectionParam);
        WebServiceManager.removePreDetectionParam();
    }

    @Override
    public void startContrastTest(ContrastDetectionParam param) {
        // 参数校验，目前仅检查IP是否重复，后续可在里面扩展
        checkDevIp(param.getDevIds());
        socketContrastResponseService.init(param);

        // 和通信模块进行连接
        this.sendContrastSocket(param);
    }

    @Override
    public void exportAlignData() {
        String fileName = "对齐数据.xlsx";
        HttpServletResponse response = HttpServletUtil.getResponse();
        response.reset();
        response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
        response.setContentType("application/octet-stream;charset=UTF-8");
        try {
            InputStream inputStream = new FileInputStream(pathConfig.getDataPath() + File.separator + fileName);
            byte[] buffer = new byte[1024];
            int len = 0;
            ServletOutputStream outputStream = response.getOutputStream();
            while ((len = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, len);
            }
            outputStream.flush();
            outputStream.close();
            inputStream.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public boolean getCanCoefficient() {
        return CollUtil.isNotEmpty(DetectionServiceImpl.vAndIResultMap);
    }

    @Override
    public void startCoefficient() {
        if (CollUtil.isNotEmpty(DetectionServiceImpl.vAndIResultMap)) {
            // 是否进行系数校准
            //万一录波了这里就进不来了，但是在误差处理的时候DetectionServiceImpl.vAndIResultMap该集合会保留不符合的结果

            XiNumberManager.xiDevList.clear();
            XiNumberManager.devXiNumDataMap.clear();
            List<String> stdIpList = DetectionServiceImpl.vAndIResultMap.values().stream().map(DetectionServiceImpl.VAndIResult::getIp).collect(Collectors.toList());
            XiNumberManager.xiDevList = FormalTestManager.standardDevList.stream().filter(d -> stdIpList.contains(d.getDevIP())).collect(Collectors.toList());
            // 逐一设备向通讯模块要原始系数
            SocketMsg<String> socketMsg = new SocketMsg<>();
            socketMsg.setRequestId(SourceOperateCodeEnum.Coefficient_Check.getValue());
            socketMsg.setOperateCode(SourceOperateCodeEnum.DATA_CHNFACTOR$01.getValue());
            PreDetection preDetection = XiNumberManager.xiDevList.get(0);
            Map<String, Object> map = new HashMap<>();
            map.put("devIP", preDetection.getDevIP());
            map.put("chnNum", preDetection.getDevChns());
            socketMsg.setData(JSON.toJSONString(map));
            SocketManager.sendMsg(NettyContrastClientHandler.param.getUserPageId() + CnSocketUtil.CONTRAST_DEV_TAG, JSON.toJSONString(socketMsg));

            FormalTestManager.currentStep = SourceOperateCodeEnum.Coefficient_Check;
        }
    }

    /**
     * 比对式-与通信模块进行连接
     *
     * @param param
     */
    private void sendContrastSocket(ContrastDetectionParam param) {
        Map<String, List<PreDetection>> map = new HashMap<>(1);
        List<PreDetection> preDetections = new ArrayList<>();
        preDetections.addAll(FormalTestManager.devList);
        preDetections.addAll(FormalTestManager.standardDevList);

        preDetections.forEach(x -> {
            x.setDevType(x.getIcdType());
            x.getMonitorList().forEach(y -> {

                String ptStr = y.getPtStr();
                int i = ptStr.indexOf(":");
                y.setPt(BigDecimal.valueOf(Double.parseDouble(ptStr.substring(0, i))).divide(BigDecimal.valueOf(Double.parseDouble(ptStr.substring(i + 1))), 5, BigDecimal.ROUND_HALF_UP).doubleValue());

                String ctStr = y.getCtStr();
                i = ctStr.indexOf(":");
                y.setCt(BigDecimal.valueOf(Double.parseDouble(ctStr.substring(0, i))).divide(BigDecimal.valueOf(Double.parseDouble(ctStr.substring(i + 1))), 5, BigDecimal.ROUND_HALF_UP).doubleValue());
            });
        });
        map.put("deviceList", preDetections);
        String jsonString = JSON.toJSONString(map);
        SocketMsg<String> socketMsg = new SocketMsg<>();
        socketMsg.setRequestId(SourceOperateCodeEnum.YJC_SBTXJY.getValue());
        AdPlan currentTestPlan = FormalTestManager.currentTestPlan;
        String[] datasource = currentTestPlan.getDatasourceId().split(",");
        if (Arrays.stream(datasource).anyMatch(x -> x.equals(DataSourceEnum.REAL_DATA.getValue()))) {
            socketMsg.setOperateCode(SourceOperateCodeEnum.DEV_INIT_GATHER_02.getValue());
        } else if (Arrays.stream(datasource).anyMatch(x -> x.equals(DataSourceEnum.MINUTE_STATISTICS_AVG.getValue()) || x.equals(DataSourceEnum.MINUTE_STATISTICS_MAX.getValue()) || x.equals(DataSourceEnum.MINUTE_STATISTICS_MIN.getValue()) || x.equals(DataSourceEnum.MINUTE_STATISTICS_CP95.getValue()))) {
            socketMsg.setOperateCode(SourceOperateCodeEnum.DEV_INIT_GATHER_01.getValue());
        } else if (Arrays.stream(datasource).anyMatch(x -> x.equals(DataSourceEnum.WAVE_DATA.getValue()))) {
            socketMsg.setOperateCode(SourceOperateCodeEnum.DEV_INIT_GATHER_02.getValue());
        }
        socketMsg.setData(jsonString);
        PreDetectionParam preDetectionParam = new PreDetectionParam();
        preDetectionParam.setUserPageId(param.getLoginName());
        preDetectionParam.setTestItemList(param.getTestItemList());
        preDetectionParam.setDevIds(param.getDevIds());
        preDetectionParam.setUserId(param.getUserId());
        WebServiceManager.addPreDetectionParam(param.getLoginName(), preDetectionParam);
        socketManager.smartSendToContrast(param, JSON.toJSONString(socketMsg));
    }

    /**
     * 校验被检设备中是否存在IP重复的
     */
    private void checkDevIp(List<String> devIds) {
        List<PqDev> pqDevList = iPqDevService.listByIds(devIds);
        List<String> ipList = pqDevList.stream().map(PqDev::getIp).distinct().collect(Collectors.toList());
        if (ipList.size() != devIds.size()) {
            throw new BusinessException(DetectionResponseEnum.PLAN_DEV_IP_HAS);
        }
    }

}
