package com.njcn.csdevice.service.impl;

import cn.afterturn.easypoi.excel.ExcelExportUtil;
import cn.afterturn.easypoi.excel.ExcelImportUtil;
import cn.afterturn.easypoi.excel.entity.ExportParams;
import cn.afterturn.easypoi.excel.entity.ImportParams;
import cn.afterturn.easypoi.excel.entity.result.ExcelImportResult;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.text.StrPool;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.client.naming.utils.CollectionUtils;
import com.alibaba.nacos.shaded.com.google.gson.Gson;
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.tocrhz.mqtt.publisher.MqttPublisher;
import com.njcn.access.api.AskDeviceDataFeignClient;
import com.njcn.access.api.CsTopicFeignClient;
import com.njcn.access.enums.AccessEnum;
import com.njcn.access.enums.AccessResponseEnum;
import com.njcn.access.enums.TypeEnum;
import com.njcn.access.pojo.dto.ReqAndResDto;
import com.njcn.access.pojo.dto.UpgradeDevDto;
import com.njcn.access.pojo.dto.file.FileRedisDto;
import com.njcn.access.utils.MqttUtil;
import com.njcn.common.pojo.exception.BusinessException;
import com.njcn.csdevice.api.CsLedgerFeignClient;
import com.njcn.csdevice.constant.DataParam;
import com.njcn.csdevice.enums.AlgorithmResponseEnum;
import com.njcn.csdevice.mapper.CsEquipmentDeliveryMapper;
import com.njcn.csdevice.mapper.CsLedgerMapper;
import com.njcn.csdevice.mapper.CsSoftInfoMapper;
import com.njcn.csdevice.mapper.CsTerminalLogsMapper;
import com.njcn.csdevice.pojo.dto.DevDetailDTO;
import com.njcn.csdevice.pojo.dto.PqsCommunicateDto;
import com.njcn.csdevice.pojo.param.*;
import com.njcn.csdevice.pojo.po.*;
import com.njcn.csdevice.pojo.vo.*;
import com.njcn.csdevice.service.*;
import com.njcn.csdevice.util.QRCodeUtil;
import com.njcn.csdevice.utils.ExcelStyleUtil;
import com.njcn.csdevice.utils.StringUtil;
import com.njcn.csharmonic.api.CsHarmonicPlanLineFeignClient;
import com.njcn.csharmonic.api.EventUserFeignClient;
import com.njcn.csharmonic.param.CsEventUserQueryParam;
import com.njcn.csharmonic.pojo.po.CsEventPO;
import com.njcn.device.biz.mapper.OverLimitWlMapper;
import com.njcn.mq.message.CloudTopicMessage;
import com.njcn.mq.message.LogMessage;
import com.njcn.mq.template.CloudTopicMessageTemplate;
import com.njcn.mq.template.LogMessageTemplate;
import com.njcn.oss.constant.OssPath;
import com.njcn.oss.utils.FileStorageUtil;
import com.njcn.redis.pojo.enums.AppRedisKey;
import com.njcn.redis.utils.RedisUtil;
import com.njcn.system.api.DicDataFeignClient;
import com.njcn.system.api.DictTreeFeignClient;
import com.njcn.system.enums.DicDataEnum;
import com.njcn.system.enums.DicDataTypeEnum;
import com.njcn.system.pojo.po.SysDicTreePO;
import com.njcn.system.pojo.vo.DictTreeVO;
import com.njcn.user.api.UserFeignClient;
import com.njcn.user.enums.AppRoleEnum;
import com.njcn.user.pojo.po.User;
import com.njcn.web.factory.PageFactory;
import com.njcn.web.utils.RequestUtil;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang.StringUtils;
import org.apache.poi.ss.usermodel.Workbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StreamUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Description:
 * 接口文档访问地址：http://serverIP:port/swagger-ui.html
 * Date: 2023/3/30  16:23【需求编号】
 *
 * @author clam
 * @version V1.0.0
 */
@Service
@RequiredArgsConstructor
public class CsEquipmentDeliveryServiceImpl extends ServiceImpl<CsEquipmentDeliveryMapper, CsEquipmentDeliveryPO> implements CsEquipmentDeliveryService {
    private static final Logger logger = LoggerFactory.getLogger(CsEquipmentDeliveryServiceImpl.class);
    private final CsDevModelRelationService csDevModelRelationService;
    private final ICsDataSetService csDataSetService;
    private final ICsLedgerService csLedgerService;
    private final RoleEngineerDevService roleEngineerDevService;
    private final CsDeviceUserPOService csDeviceUserPOService;
    private final AppLineTopologyDiagramService appLineTopologyDiagramService;
    private final CsTouristDataPOService csTouristDataPOService;
    private final CsLinePOService csLinePOService;
    private final DicDataFeignClient dicDataFeignClient;
    private final FileStorageUtil fileStorageUtil;
    private final DictTreeFeignClient dictTreeFeignClient;
    private final CsEquipmentProcessPOService csEquipmentProcessPOService;
    private final AskDeviceDataFeignClient askDeviceDataFeignClient;
    private final RedisUtil redisUtil;
    private final CsSoftInfoMapper csSoftInfoMapper;
    private final MqttUtil mqttUtil;
    private final INodeService nodeService;
    private final CsDevModelService csDevModelService;
    private final CsLedgerMapper csLedgerMapper;
    private final CsTerminalLogsMapper csTerminalLogsMapper;
    private final ICsCommunicateService csCommunicateService;
    private final ICsUserPinsService csUserPinsService;
    private final CsEdDataService csEdDataService;
    private final ICsSoftInfoService csSoftInfoService;
    private final EventUserFeignClient eventUserFeignClient;
    private final UserFeignClient userFeignClient;
    private final OverLimitWlMapper overLimitWlMapper;
    private final CsLedgerFeignClient csLedgerFeignClient;
    private final CsHarmonicPlanLineFeignClient csHarmonicPlanLineFeignClient;
    private final ICsDeviceRegistryService csDeviceRegistryService;
    private final StringRedisTemplate stringRedisTemplate;
    private final CloudTopicMessageTemplate cloudTopicMessageTemplate;
    private final CsTopicFeignClient csTopicFeignClient;
    private final MqttPublisher publisher;
    private final LogMessageTemplate logMessageTemplate;
    private final CsUpgradeLogsService csUpgradeLogsService;

    @Override
    public void refreshDeviceDataCache() {
        LambdaQueryWrapper<CsEquipmentDeliveryPO> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.ne(CsEquipmentDeliveryPO::getRunStatus, 0);
        List<CsEquipmentDeliveryPO> deliveryPOS = this.list(queryWrapper);
        redisUtil.saveByKey(AppRedisKey.DEVICE_LIST, deliveryPOS);
    }

    @Override
    @Transactional(rollbackFor = {Exception.class})
    public CsEquipmentDeliveryPO save(CsEquipmentDeliveryAddParm csEquipmentDeliveryAddParm) {
        boolean result;
        List<CsEquipmentDeliveryPO> one = this.lambdaQuery().eq(CsEquipmentDeliveryPO::getName, csEquipmentDeliveryAddParm.getName()).ne(CsEquipmentDeliveryPO::getRunStatus, 0).eq(CsEquipmentDeliveryPO::getDevAccessMethod, "MQTT").list();
        if (CollUtil.isNotEmpty(one)) {
            throw new BusinessException("设备名称不能重复");
        }
        StringUtil.containsSpecialCharacters(csEquipmentDeliveryAddParm.getNdid());
        CsEquipmentDeliveryPO po = this.queryEquipmentPOByndid(csEquipmentDeliveryAddParm.getNdid());
        if (!Objects.isNull(po)) {
            throw new BusinessException(AlgorithmResponseEnum.NDID_ERROR);
        }
        CsEquipmentDeliveryPO csEquipmentDeliveryPo = new CsEquipmentDeliveryPO();
        BeanUtils.copyProperties(csEquipmentDeliveryAddParm, csEquipmentDeliveryPo);
        csEquipmentDeliveryPo.setStatus(1);
        csEquipmentDeliveryPo.setRunStatus(1);
        csEquipmentDeliveryPo.setUsageStatus(1);
//        String code = dictTreeFeignClient.queryById(csEquipmentDeliveryAddParm.getDevType()).getData().getCode();
//        if (Objects.equals(DicDataEnum.PORTABLE.getCode(),code)) {
//            csEquipmentDeliveryPo.setProcess(4);
//        } else if (Objects.equals(DicDataEnum.CONNECT_DEV.getCode(),code)) {
//            csEquipmentDeliveryPo.setProcess(2);
//        }
        csEquipmentDeliveryPo.setProcess(4);
        //生成二维码文件
        String qr = this.createQr(csEquipmentDeliveryAddParm.getNdid());
        csEquipmentDeliveryPo.setQrPath(qr);
        String path = this.createPath(csEquipmentDeliveryAddParm.getNdid());
        csEquipmentDeliveryPo.setMac(path);
        //PQCOM默认都支持升级
        csEquipmentDeliveryPo.setUpgrade(1);
        //记录设备调试日志表
        CsEquipmentProcessPO csEquipmentProcess = new CsEquipmentProcessPO();
        csEquipmentProcess.setDevId(csEquipmentDeliveryAddParm.getNdid());
        csEquipmentProcess.setOperator(RequestUtil.getUserIndex());
        csEquipmentProcess.setStartTime(LocalDateTime.now());
        csEquipmentProcess.setProcess(4);
        csEquipmentProcess.setStatus(1);
        csEquipmentProcessPOService.save(csEquipmentProcess);
        result = this.save(csEquipmentDeliveryPo);

        String code = dictTreeFeignClient.queryById(csEquipmentDeliveryAddParm.getDevType()).getData().getCode();
        if (Objects.equals(DicDataEnum.DEV_CLD.getCode(), code) && Objects.equals(csEquipmentDeliveryAddParm.getDevAccessMethod(), "CLD")) {
            //谁新建的设备，则认为是该设备的主用户，新建用户设备关系表数据
            boolean addUser = csDeviceUserPOService.add(csEquipmentDeliveryPo.getId());
            if (result && addUser) {
                refreshDeviceDataCache();
            }
        } else {
            if (result) {
                refreshDeviceDataCache();
            }
        }
        return csEquipmentDeliveryPo;
    }

    @Override
    @DSTransactional
    public Boolean AuditEquipmentDelivery(String id) {
        //物理删除
        boolean update = false;
        LambdaQueryWrapper<CsEquipmentDeliveryPO> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(CsEquipmentDeliveryPO::getId, id);
        CsEquipmentDeliveryPO po = this.getOne(wrapper);
        if (po != null) {
            update = this.remove(wrapper);
            redisUtil.deleteKeysByString(AppRedisKey.LINE_POSITION + po.getNdid());
        }
        //删除监测点表、监测点拓扑图关系、监测点限值
        List<CsLedger> list = csLedgerService.lambdaQuery().eq(CsLedger::getPid, id).list();
        if (!CollectionUtils.isEmpty(list)) {
            List<String> collect = list.stream().map(CsLedger::getId).collect(Collectors.toList());
            LambdaQueryWrapper<CsLinePO> csLinePOLambdaQueryWrapper = new LambdaQueryWrapper<>();
            csLinePOLambdaQueryWrapper.in(CsLinePO::getLineId, collect);
            csLinePOService.remove(csLinePOLambdaQueryWrapper);
            //删除监测点限值
            overLimitWlMapper.deleteBatchIds(collect);
            //删除配置
            csHarmonicPlanLineFeignClient.deleteByLineIds(collect);
            QueryWrapper<AppLineTopologyDiagramPO> appLineTopologyDiagramPOQueryWrapper = new QueryWrapper<>();
            appLineTopologyDiagramPOQueryWrapper.clear();
            appLineTopologyDiagramPOQueryWrapper.in("line_id", collect);
            appLineTopologyDiagramService.remove(appLineTopologyDiagramPOQueryWrapper);
        }
        //清空关系表
        LambdaQueryWrapper<CsLedger> csLedgerLambdaQueryWrapper = new LambdaQueryWrapper<>();
        csLedgerLambdaQueryWrapper.clear();
        csLedgerLambdaQueryWrapper.eq(CsLedger::getId, id);
        csLedgerService.remove(csLedgerLambdaQueryWrapper);
        csLedgerLambdaQueryWrapper.clear();
        csLedgerLambdaQueryWrapper.eq(CsLedger::getPid, id);
        csLedgerService.remove(csLedgerLambdaQueryWrapper);
        //删除设备和模板的关系
        LambdaQueryWrapper<CsDevModelRelationPO> csDevModelRelationPOQueryWrapper = new LambdaQueryWrapper<>();
        csDevModelRelationPOQueryWrapper.clear();
        csDevModelRelationPOQueryWrapper.eq(CsDevModelRelationPO::getDevId, id);
        csDevModelRelationService.remove(csDevModelRelationPOQueryWrapper);
        //删除设备/用户关系
        QueryWrapper<CsDeviceUserPO> csDeviceUserPOQueryWrapper = new QueryWrapper<>();
        csDeviceUserPOQueryWrapper.clear();
        csDeviceUserPOQueryWrapper.eq("device_id", id);
        csDeviceUserPOService.remove(csDeviceUserPOQueryWrapper);
        //删除游客
        QueryWrapper<CsTouristDataPO> queryWrap = new QueryWrapper<>();
        queryWrap.eq("device_id", id);
        csTouristDataPOService.getBaseMapper().delete(queryWrap);
        //删除设备注册表
        if (po != null) {
            csDeviceRegistryService.deleteByCurrentNdid(po.getNdid());
        }
        if (update) {
            refreshDeviceDataCache();
        }
        return update;
    }

    @Override
    public CsEquipmentDeliveryVO queryEquipmentByndid(String ndid) {
        CsEquipmentDeliveryVO result = new CsEquipmentDeliveryVO();
        CsEquipmentDeliveryPO csEquipmentDeliveryPO = queryEquipmentPOByndid(ndid);
        if (Objects.isNull(csEquipmentDeliveryPO)) {
            return result;
        }
        BeanUtils.copyProperties(csEquipmentDeliveryPO, result);
        if (!Objects.isNull(csEquipmentDeliveryPO.getAssociatedEngineering()) && !Objects.equals(csEquipmentDeliveryPO.getAssociatedEngineering(), "")) {
            CsLedger csLedger = csLedgerService.findDataById(csEquipmentDeliveryPO.getAssociatedEngineering());
            if (!Objects.isNull(csLedger)) {
                result.setAssociatedEngineeringName(csLedger.getName());
            }
        }
        if (!Objects.isNull(csEquipmentDeliveryPO.getAssociatedProject()) && !Objects.equals(csEquipmentDeliveryPO.getAssociatedProject(), "")) {
            CsLedger csLedger = csLedgerService.findDataById(csEquipmentDeliveryPO.getAssociatedProject());
            if (!Objects.isNull(csLedger)) {
                result.setAssociatedProjectName(csLedger.getName());
            }
        }
        return result;
    }

    public CsEquipmentDeliveryPO queryEquipmentPOByndid(String ndid) {
        QueryWrapper<CsEquipmentDeliveryPO> wrapper = new QueryWrapper();
        wrapper.eq("ndid", ndid);
        wrapper.ne("run_status", 0);
        CsEquipmentDeliveryPO csEquipmentDeliveryPO = this.baseMapper.selectOne(wrapper);
        return csEquipmentDeliveryPO;
    }

    @Override
    public IPage<ProjectEquipmentVO> queryEquipmentByProject(ProjectEquipmentQueryParm projectEquipmentQueryParm) {
        Page<ProjectEquipmentVO> returnpage = new Page<>(projectEquipmentQueryParm.getPageNum(), projectEquipmentQueryParm.getPageSize());
        List<String> device = roleEngineerDevService.getDevice();
        if (CollectionUtils.isEmpty(device)) {
            return returnpage;
        }
        Page<ProjectEquipmentVO> list = this.baseMapper.queryProjectEquipmentVO(returnpage, projectEquipmentQueryParm, device);
        //根据设备id获取监测点id集合
        List<CsLinePO> lineIds = csLinePOService.getLineByDev(device);
        //根据设备获取当日是否存在未读取的暂态事件
        CsEventUserQueryParam param = new CsEventUserQueryParam();
        param.setUserId(RequestUtil.getUserIndex());
        param.setStartTime(LocalDate.now().atStartOfDay().format(DatePattern.NORM_DATETIME_FORMATTER));
        param.setEndTime(LocalDateTime.now().format(DatePattern.NORM_DATETIME_FORMATTER));
        param.setEventIds(lineIds.stream().map(CsLinePO::getLineId).collect(Collectors.toList()));
        List<CsEventPO> eventList = eventUserFeignClient.queryTempEventDetail(param).getData();
        Map<String, List<CsEventPO>> eventMap = Optional.ofNullable(eventList).orElse(Collections.emptyList()).stream().collect(Collectors.groupingBy(CsEventPO::getDeviceId));

        List<ProjectEquipmentVO> recordList = new ArrayList<>();
        list.getRecords().forEach(temp -> {
            temp.setIsPrimaryUser(csDeviceUserPOService.isPrimaryUser(temp.getEquipmentId()));
            //获取设备类型
            SysDicTreePO po = dictTreeFeignClient.queryById(temp.getDevType()).getData();
            temp.setDevType(po.getCode());
            temp.setLineList(lineIds.stream().filter(line -> line.getDevId().equals(temp.getEquipmentId())).sorted(Comparator.comparing(CsLinePO::getLineId)).collect(Collectors.toList()));
            if (!Objects.equals(DicDataEnum.PORTABLE.getCode(), temp.getDevType())) {
                recordList.add(temp);
            }
            //判断设备是否存在告警
            temp.setIsAlarm(CollectionUtil.isNotEmpty(eventMap.get(temp.getEquipmentId())));
        });
        //获取用户置顶的设备
        List<CsUserPins> topList = csUserPinsService.getPinToTopList();
        List<String> targetIdList = topList.stream().filter(item -> Objects.equals(item.getTargetType(), 1)).map(CsUserPins::getTargetId).collect(Collectors.toList());
        if (CollectionUtil.isNotEmpty(targetIdList)) {
            List<ProjectEquipmentVO> matchedList = new ArrayList<>();
            List<ProjectEquipmentVO> unmatchedList = new ArrayList<>();
            for (ProjectEquipmentVO node : recordList) {
                if (targetIdList.contains(node.getEquipmentId())) {
                    node.setIsTop(1);
                    matchedList.add(node);
                } else {
                    unmatchedList.add(node);
                }
            }
            // 根据 targetIdList 的顺序对匹配的节点进行排序
            matchedList.sort(Comparator.comparingInt(node -> targetIdList.indexOf(node.getEquipmentId())));
            // 未匹配的节点按照原有的 sort 字段排序，处理 null 值情况
            unmatchedList.sort(Comparator.comparing(node -> node.getSort() != null ? node.getSort() : Integer.MAX_VALUE));
            // 清空原列表并添加排序后的数据
            recordList.clear();
            recordList.addAll(matchedList);
            recordList.addAll(unmatchedList);
            list.setRecords(recordList);
        }
        return list;
    }

    // ... existing code ...
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean updateEquipmentDelivery(CsEquipmentDeliveryAuditParm auditParm) {
        StringUtil.containsSpecialCharacters(auditParm.getNdid());
        // 校验ndid不重复
        long ndidCount = this.count(new LambdaQueryWrapper<CsEquipmentDeliveryPO>()
                .eq(CsEquipmentDeliveryPO::getNdid, auditParm.getNdid())
                .in(CsEquipmentDeliveryPO::getStatus, Arrays.asList(1, 2, 3))
                .ne(CsEquipmentDeliveryPO::getId, auditParm.getId()));
        if (ndidCount > 0) {
            throw new BusinessException(AlgorithmResponseEnum.NDID_ERROR);
        }
        // 查询原记录
        CsEquipmentDeliveryPO po = this.getById(auditParm.getId());
        if (Objects.isNull(po)) {
            throw new BusinessException("设备不存在");
        }
        // 校验设备名称不重复
        long nameCount = this.lambdaQuery()
                .ne(CsEquipmentDeliveryPO::getId, auditParm.getId())
                .ne(CsEquipmentDeliveryPO::getNdid, auditParm.getNdid())
                .eq(CsEquipmentDeliveryPO::getName, auditParm.getName())
                .ne(CsEquipmentDeliveryPO::getRunStatus, 0)
                .eq(CsEquipmentDeliveryPO::getDevAccessMethod, "MQTT")
                .count();
        if (nameCount > 0) {
            throw new BusinessException("设备名称不能重复");
        }
        CsEquipmentDeliveryPO updatePo = new CsEquipmentDeliveryPO();
        // 修改了ndid时的联动处理
        if (!Objects.equals(po.getNdid(), auditParm.getNdid())) {
            handleNdidChange(po, auditParm, updatePo);
        }
        BeanUtils.copyProperties(auditParm, updatePo);
        updatePo.setMac(createPath(auditParm.getNdid()));
        // 更新台账树中的设备名称及工程/项目
        CsLedger csLedger = csLedgerService.findDataById(auditParm.getId());
        if (ObjectUtil.isNotNull(csLedger)) {
            csLedger.setName(auditParm.getName());
            updateLedger(csLedger);
            handleEngineeringProjectChange(po, auditParm, csLedger);
        }
        return this.updateById(updatePo);
    }

    private void handleNdidChange(CsEquipmentDeliveryPO po, CsEquipmentDeliveryAuditParm auditParm, CsEquipmentDeliveryPO updatePo) {
        String oldNdid = po.getNdid();
        String newNdid = auditParm.getNdid();
        // 1、更新设备注册表信息记录
        List<CsDeviceRegistry> registryList = csDeviceRegistryService.queryByCurrentNdid(oldNdid);
        List<CsDeviceRegistry> updateList = registryList.stream().map(item -> {
            CsDeviceRegistry registry = new CsDeviceRegistry();
            registry.setId(item.getId());
            registry.setCurrentNdid(newNdid);
            registry.setOldNdid(oldNdid);
            registry.setClDid(item.getClDid());
            registry.setFirstSeenTime(item.getFirstSeenTime());
            registry.setIsAccess(0);
            return registry;
        }).collect(Collectors.toList());
        csDeviceRegistryService.updateBatch(updateList);
        // 2、迁移redis缓存
        transferRedisCache(AppRedisKey.MODEL, oldNdid, newNdid);
        transferRedisCache(AppRedisKey.LINE_POSITION, oldNdid, newNdid);
        // 3、修改设备状态为离线、已注册
        updatePo.setRunStatus(1);
        updatePo.setStatus(2);
        // 4、清空老mac的数据模板
        stringRedisTemplate.convertAndSend("model_cache_clear", "clear");
        // 5、修改二维码信息
        updatePo.setQrPath(this.createQr(newNdid));
    }

    private void transferRedisCache(String keyPrefix, String oldNdid, String newNdid) {
        Object data = redisUtil.getObjectByKey(keyPrefix + oldNdid);
        if (data != null) {
            redisUtil.delete(keyPrefix + oldNdid);
            redisUtil.saveByKey(keyPrefix + newNdid, data);
        }
    }

    private void handleEngineeringProjectChange(CsEquipmentDeliveryPO po, CsEquipmentDeliveryAuditParm auditParm, CsLedger csLedger) {
        boolean engineeringChanged = !Objects.equals(po.getAssociatedEngineering(), auditParm.getAssociatedEngineering());
        boolean projectChanged = !Objects.equals(po.getAssociatedProject(), auditParm.getAssociatedProject());
        if (!engineeringChanged && !projectChanged) {
            return;
        }
        // 已接入过的设备，工程和项目不能为空
        if (!Objects.equals(po.getStatus(), 1)) {
            if (StringUtils.isBlank(auditParm.getAssociatedEngineering()) || StringUtils.isBlank(auditParm.getAssociatedProject())) {
                throw new BusinessException("装置已接入过,工程项目不可设置为空");
            }
            // 修改设备台账的pid和pids
            csLedger.setPid(auditParm.getAssociatedProject());
            String[] pidsParts = csLedger.getPids().split(",");
            pidsParts[1] = auditParm.getAssociatedEngineering();
            pidsParts[2] = auditParm.getAssociatedProject();
            csLedger.setPids(String.join(",", pidsParts));
            updateLedger(csLedger);
            // 修改监测点的pids
            List<CsLedger> lineLedgers = csLedgerService.queryByPid(po.getId());
            lineLedgers.forEach(item -> {
                String[] linePidsParts = item.getPids().split(",");
                linePidsParts[1] = auditParm.getAssociatedEngineering();
                linePidsParts[2] = auditParm.getAssociatedProject();
                item.setPids(String.join(",", linePidsParts));
                updateLedger(item);
            });
        }
    }

    private void updateLedger(CsLedger csLedger) {
        CsLedgerParam.Update updateParam = new CsLedgerParam.Update();
        BeanUtils.copyProperties(csLedger, updateParam);
        csLedgerService.updateLedgerTree(updateParam);
    }

    // ... existing code ...


//    @Override
//    @Transactional(rollbackFor = Exception.class)
//    public Boolean updateEquipmentDelivery(CsEquipmentDeliveryAuditParm csEquipmentDeliveryAuditParm) {
//        StringUtil.containsSpecialCharacters(csEquipmentDeliveryAuditParm.getNdid());
//        boolean result;
//        LambdaQueryWrapper<CsEquipmentDeliveryPO> lambdaQueryWrapper = new LambdaQueryWrapper<>();
//        lambdaQueryWrapper.eq(CsEquipmentDeliveryPO::getNdid, csEquipmentDeliveryAuditParm.getNdid()).in(CsEquipmentDeliveryPO::getStatus, Arrays.asList(1, 2, 3)).ne(CsEquipmentDeliveryPO::getId, csEquipmentDeliveryAuditParm.getId());
//        int countByAccount = this.count(lambdaQueryWrapper);
//        //大于等于1个则表示重复
//        if (countByAccount >= 1) {
//            throw new BusinessException(AlgorithmResponseEnum.NDID_ERROR);
//        }
//        LambdaQueryWrapper<CsEquipmentDeliveryPO> lambdaQueryWrapper2 = new LambdaQueryWrapper<>();
//        lambdaQueryWrapper2.eq(CsEquipmentDeliveryPO::getId, csEquipmentDeliveryAuditParm.getId());
//        CsEquipmentDeliveryPO po = this.baseMapper.selectOne(lambdaQueryWrapper2);
//        CsEquipmentDeliveryPO csEquipmentDeliveryPo = new CsEquipmentDeliveryPO();
//        //修改了mac地址
//        if (!Objects.equals(po.getNdid(), csEquipmentDeliveryAuditParm.getNdid())) {
//            List<CsDeviceRegistry> updateList = new ArrayList<>();
//            //1、更新设备注册表信息记录
//            List<CsDeviceRegistry> list = csDeviceRegistryService.queryByCurrentNdid(po.getNdid());
//            list.forEach(item->{
//                CsDeviceRegistry csDeviceRegistry = new CsDeviceRegistry();
//                csDeviceRegistry.setId(item.getId());
//                csDeviceRegistry.setCurrentNdid(csEquipmentDeliveryAuditParm.getNdid());
//                csDeviceRegistry.setOldNdid(po.getNdid());
//                csDeviceRegistry.setClDid(item.getClDid());
//                csDeviceRegistry.setFirstSeenTime(item.getFirstSeenTime());
//                csDeviceRegistry.setIsAccess(0);
//                updateList.add(csDeviceRegistry);
//            });
//            csDeviceRegistryService.updateBatch(updateList);
//            //2、修改redis的缓存信息
//            Object data1 = redisUtil.getObjectByKey(AppRedisKey.MODEL + po.getNdid());
//            Object data2 = redisUtil.getObjectByKey(AppRedisKey.LINE_POSITION + po.getNdid());
//            if (data1 != null) {
//                redisUtil.delete(AppRedisKey.MODEL + po.getNdid());
//                redisUtil.saveByKey(AppRedisKey.MODEL + csEquipmentDeliveryAuditParm.getNdid(), data1);
//            }
//            if (data2 != null) {
//                redisUtil.delete(AppRedisKey.LINE_POSITION + po.getNdid());
//                redisUtil.saveByKey(AppRedisKey.LINE_POSITION + csEquipmentDeliveryAuditParm.getNdid(), data2);
//            }
//            //3、修改设备状态，设备应该是离线 已注册
//            csEquipmentDeliveryPo.setRunStatus(1);
//            csEquipmentDeliveryPo.setStatus(2);
//            //4.清空老mac的数据模板
//            stringRedisTemplate.convertAndSend("model_cache_clear", "clear");
//            //5.修改二维码信息
//            String qr = this.createQr(csEquipmentDeliveryAuditParm.getNdid());
//            csEquipmentDeliveryPo.setQrPath(qr);
//        }
//        List<CsEquipmentDeliveryPO> list = this.lambdaQuery().ne(CsEquipmentDeliveryPO::getId, csEquipmentDeliveryAuditParm.getId()).ne(CsEquipmentDeliveryPO::getNdid, csEquipmentDeliveryAuditParm.getNdid()).eq(CsEquipmentDeliveryPO::getName, csEquipmentDeliveryAuditParm.getName()).ne(CsEquipmentDeliveryPO::getRunStatus, 0).list();
//        if (!CollectionUtils.isEmpty(list)) {
//            throw new BusinessException("设备名称不能重复");
//        }
//        BeanUtils.copyProperties(csEquipmentDeliveryAuditParm, csEquipmentDeliveryPo);
//        csEquipmentDeliveryPo.setMac(csEquipmentDeliveryAuditParm.getNdid().replaceAll("(.{2})", "$1:").substring(0, 17));
//
//        //如果是已经接入的设备需要修改台账树中的设备名称
//        CsLedger csLedger = csLedgerService.findDataById(csEquipmentDeliveryAuditParm.getId());
//        if (ObjectUtil.isNotNull(csLedger)) {
//            CsLedgerParam.Update csLedgerParam = new CsLedgerParam.Update();
//            BeanUtils.copyProperties(csLedger, csLedgerParam);
//            csLedgerParam.setName(csEquipmentDeliveryAuditParm.getName());
//            csLedgerService.updateLedgerTree(csLedgerParam);
//        }
//        //修改预设的工程和项目
//        //判断设备状态，如果已经接入过，那么工程项目就不能设置为null；如果没有接入过，没有限制
//        if (!Objects.equals(po.getAssociatedEngineering(), csEquipmentDeliveryAuditParm.getAssociatedEngineering())
//                || !Objects.equals(po.getAssociatedProject(), csEquipmentDeliveryAuditParm.getAssociatedProject())) {
//            if (po.getStatus() != 1) {
//                if (Objects.isNull(csEquipmentDeliveryAuditParm.getAssociatedEngineering()) || Objects.isNull(csEquipmentDeliveryAuditParm.getAssociatedProject())) {
//                    throw new BusinessException("装置已接入过,工程项目不可设置为空");
//                } else {
//                    //修改设备
//                    CsLedger csLedger2 = csLedgerService.findDataById(csEquipmentDeliveryAuditParm.getId());
//                    csLedger2.setPid(csEquipmentDeliveryAuditParm.getAssociatedProject());
//                    String pidS = csLedger2.getPids();
//                    String[] parts = pidS.split(",");
//                    parts[1] = csEquipmentDeliveryAuditParm.getAssociatedEngineering();
//                    parts[2] = csEquipmentDeliveryAuditParm.getAssociatedProject();
//                    String newPidS = String.join(",", parts);
//                    csLedger2.setPids(newPidS);
//                    CsLedgerParam.Update csLedgerParam = new CsLedgerParam.Update();
//                    BeanUtils.copyProperties(csLedger2, csLedgerParam);
//                    csLedgerService.updateLedgerTree(csLedgerParam);
//                    csEquipmentDeliveryPo.setAssociatedEngineering(csEquipmentDeliveryAuditParm.getAssociatedEngineering());
//                    csEquipmentDeliveryPo.setAssociatedProject(csEquipmentDeliveryAuditParm.getAssociatedProject());
//                    //修改监测点
//                    List<CsLedger> csLedgers = csLedgerService.queryByPid(po.getId());
//                    csLedgers.forEach(item->{
//                        String linePidS = item.getPids();
//                        String[] lineParts = linePidS.split(",");
//                        lineParts[1] = csEquipmentDeliveryAuditParm.getAssociatedEngineering();
//                        lineParts[2] = csEquipmentDeliveryAuditParm.getAssociatedProject();
//                        String lineNewPidS = String.join(",", lineParts);
//                        item.setPids(lineNewPidS);
//                        CsLedgerParam.Update csLedgerParam2 = new CsLedgerParam.Update();
//                        BeanUtils.copyProperties(item, csLedgerParam2);
//                        csLedgerService.updateLedgerTree(csLedgerParam2);
//                    });
//                }
//            }
//        }
//        result = this.updateById(csEquipmentDeliveryPo);
//        return result;
//    }

    @Override
    public void updateStatusBynDid(String nDId, Integer status) {
        boolean result;
        LambdaUpdateWrapper<CsEquipmentDeliveryPO> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
        lambdaUpdateWrapper.set(CsEquipmentDeliveryPO::getStatus, status).eq(CsEquipmentDeliveryPO::getNdid, nDId);
        result = this.update(lambdaUpdateWrapper);
        if (result) {
            refreshDeviceDataCache();
        }
    }

    @Override
    public Page<CsEquipmentDeliveryVO> list(CsEquipmentDeliveryQueryParm queryParam) {
        Page<CsEquipmentDeliveryVO> page = new Page<>();
        page.setCurrent(queryParam.getPageNum());
        page.setSize(queryParam.getPageSize());
        queryParam.setPageNum((queryParam.getPageNum() - 1) * queryParam.getPageSize());
        int total = this.baseMapper.getCounts(queryParam);
        if (total > 0) {
            //获取设备注册信息表数据
            List<CsDeviceRegistry> list = csDeviceRegistryService.list();
            Map<String, List<CsDeviceRegistry>> groupedMap;
            if (list == null || list.isEmpty()) {
                groupedMap = Collections.emptyMap();
            } else {
                groupedMap = list.stream().collect(Collectors.groupingBy(CsDeviceRegistry::getCurrentNdid));
            }
            List<CsEquipmentDeliveryVO> recordList = this.baseMapper.getList(queryParam);
            //新增逻辑(针对便携式设备、监测设备):修改设备中的未注册状态(status = 1)改为5(前端定义的字典也即未接入)
            for (CsEquipmentDeliveryVO csEquipmentDeliveryVO : recordList) {
                String code = dictTreeFeignClient.queryById(csEquipmentDeliveryVO.getDevType()).getData().getCode();
                if ((Objects.equals(code, DicDataEnum.PORTABLE.getCode()) && csEquipmentDeliveryVO.getStatus() == 1)
                        || (Objects.equals(code, DicDataEnum.DEV_CLD.getCode()) && csEquipmentDeliveryVO.getStatus() == 1)) {
                    csEquipmentDeliveryVO.setStatus(5);
                } else if ((Objects.equals(code, DicDataEnum.PORTABLE.getCode()) && csEquipmentDeliveryVO.getStatus() == 2)
                        || (Objects.equals(code, DicDataEnum.DEV_CLD.getCode()) && csEquipmentDeliveryVO.getStatus() == 2)) {
                    csEquipmentDeliveryVO.setStatus(6);
                }
                //判断装置是否已经连接上mqtt服务器
                String clientName = "NJCN-" + csEquipmentDeliveryVO.getNdid().substring(csEquipmentDeliveryVO.getNdid().length() - 6);
                boolean mqttClient = mqttUtil.judgeClientOnline(clientName);
                if (mqttClient) {
                    csEquipmentDeliveryVO.setConnectStatus("已连接");
                } else {
                    csEquipmentDeliveryVO.setConnectStatus("未连接");
                }
                List<CsDeviceRegistry> data = groupedMap.get(csEquipmentDeliveryVO.getNdid());
                if (data != null && !data.isEmpty()) {
                    csEquipmentDeliveryVO.setIsAccess(data.get(0).getIsAccess());
                }
            }
            if (ObjectUtil.isNotNull(queryParam.getConnectStatus())) {
                recordList = recordList.stream().filter(item -> queryParam.getConnectStatus() == 0 ? "未连接".equals(item.getConnectStatus()) : "已连接".equals(item.getConnectStatus())).collect(Collectors.toList());
            }
            page.setTotal(CollectionUtil.isEmpty(recordList) ? 0 : recordList.size());
            page.setRecords(recordList);
        }
        return page;
    }

    @Override
    public DeviceManagerVO getDeviceData(String deviceId, String type, String lineId) {
        DeviceManagerVO deviceManagerVo = new DeviceManagerVO();
        List<DeviceManagerVO.DataSetVO> dataSetList = new ArrayList<>();
        CsEquipmentDeliveryPO csEquipmentDeliveryPo = this.lambdaQuery().eq(CsEquipmentDeliveryPO::getId, deviceId).one();
        //获取装置版本信息
        String softInfoId = csEquipmentDeliveryPo.getSoftinfoId();
        if (StringUtils.isNotBlank(softInfoId) && ObjectUtil.isNotNull(softInfoId)) {
            CsSoftInfoPO po = csSoftInfoMapper.selectById(softInfoId);
            deviceManagerVo.setAppVersion(po.getAppVersion());
            deviceManagerVo.setAppDate(po.getAppDate());
            deviceManagerVo.setAppCheck(po.getAppCheck());
        }
        if (lineId == null || lineId.isEmpty()) {
            List<CsDataSet> dataSet = new ArrayList<>();
            //如果没有传lineId(测点ID) 则根据设备ID获取对应的模板 select modelId from cs_dev_model_relation where dev_id = ?
            List<CsDevModelRelationPO> modelId = csDevModelRelationService.findModelByDevId(deviceId);
            if (CollUtil.isNotEmpty(modelId)) {
                for (CsDevModelRelationPO item : modelId) {
                    //再根据模板ID获取数据集 作为页面展示的Tab项 SELECT * FROM cs_data_set WHERE (pid = ? AND type IN (0,2))
                    dataSet.addAll(csDataSetService.findDataSetByModelId(item.getModelId()));
                }
            }
            setDataSetValues(csEquipmentDeliveryPo, dataSet, dataSetList, type, deviceManagerVo);
        } else {
            //治理设备 devTypeCode = Direct_Connected_Device 便携式设备 = Portable
            String devTypeCode = dictTreeFeignClient.queryById(csEquipmentDeliveryPo.getDevType()).getData().getCode();
            CsLinePO line = csLinePOService.getById(lineId);
            if (Objects.equals(devTypeCode, DicDataEnum.CONNECT_DEV.getCode())) {
                String code = dicDataFeignClient.getDicDataById(line.getPosition()).getData().getCode();
                String modelId = "";
                List<CsDataSet> dataSet = new ArrayList<>();
                //治理监测点
                if (Objects.equals(code, DicDataEnum.OUTPUT_SIDE.getCode())) {
                    modelId = csDevModelRelationService.getModelByType(deviceId, 0);
                    dataSet = csDataSetService.findDataSetByModelId(modelId, 0);
                }
                //电网侧监测点
                else if (Objects.equals(code, DicDataEnum.GRID_SIDE.getCode())) {
                    modelId = csDevModelRelationService.getModelByType(deviceId, 1);
                    dataSet = csDataSetService.findDataSetByModelId(modelId, 1);
                }
                //负载侧监测点
                else if (Objects.equals(code, DicDataEnum.LOAD_SIDE.getCode())) {
                    modelId = csDevModelRelationService.getModelByType(deviceId, 1);
                    dataSet = csDataSetService.findDataSetByModelId(modelId, 2);
                }
                setDataSetValues(csEquipmentDeliveryPo, dataSet, dataSetList, type, deviceManagerVo);
            } else if (Objects.equals(devTypeCode, DicDataEnum.PORTABLE.getCode())) {
                List<CsDataSet> dataSet = new ArrayList<>();
                List<CsDevModelRelationPO> modelId = csDevModelRelationService.findModelByDevId(deviceId);
                if (CollUtil.isNotEmpty(modelId)) {
                    for (CsDevModelRelationPO item : modelId) {
                        dataSet.addAll(csDataSetService.findDataSetByModelId(item.getModelId(), line.getClDid()));
                    }
                    setDataSetValues(csEquipmentDeliveryPo, dataSet, dataSetList, type, deviceManagerVo);
                }
            } else if (Objects.equals(devTypeCode, DicDataEnum.DEV_CLD.getCode())) {
                List<CsDataSet> dataSet = new ArrayList<>();
                List<CsDevModelRelationPO> modelId = csDevModelRelationService.findModelByDevId(deviceId);
                if (CollUtil.isNotEmpty(modelId)) {
                    for (CsDevModelRelationPO item : modelId) {
                        dataSet.addAll(csDataSetService.findDataSetByModelId(item.getModelId(), 1));
                    }
                    setDataSetValues(csEquipmentDeliveryPo, dataSet, dataSetList, type, deviceManagerVo);
                }
            }
        }
        return deviceManagerVo;
    }

    private void setDataSetValues(CsEquipmentDeliveryPO csEquipmentDeliveryPo, List<CsDataSet> dataSet, List<DeviceManagerVO.DataSetVO> dataSetList, String type, DeviceManagerVO deviceManagerVo) {
        BeanUtils.copyProperties(csEquipmentDeliveryPo, deviceManagerVo);
        if (CollUtil.isNotEmpty(dataSet)) {
            if (Objects.equals(type, "rt")) {
                for (CsDataSet item : dataSet) {
                    DeviceManagerVO.DataSetVO dataSetVO = new DeviceManagerVO.DataSetVO();
                    dataSetVO.setId(item.getId());
                    dataSetVO.setName(item.getAnotherName());
                    dataSetVO.setType("rt");
                    dataSetList.add(dataSetVO);
                    dataSetVO.setSort(item.getIdx());
                    deviceManagerVo.setDataLevel(item.getDataLevel());
                }
            } else {
                CsDataSet item = dataSet.get(0);
                boolean isPortableDevice = DataParam.portableDevType.equals(csEquipmentDeliveryPo.getDevType());
                boolean isCLdDevice = DicDataEnum.DEV_CLD.getCode().equals(dictTreeFeignClient.queryById(csEquipmentDeliveryPo.getDevType()).getData().getCode());

                addDataSet(dataSetList, item, "最新数据", "rt",0);
                if (item.getClDev() == 0 && item.getType() == 0) {
                    //限制角色展示
                    String role = RequestUtil.getUserRole();
                    List<String> strings = JSONArray.parseArray(role, String.class);
                    if (strings.contains(AppRoleEnum.ENGINEERING_USER.getCode()) || strings.contains(AppRoleEnum.OPERATION_MANAGER.getCode()) || strings.contains(AppRoleEnum.ROOT.getCode())) {
                        addDataSet(dataSetList, item, "模块数据", "moduleData",3);
                    }
                    addDataSet(dataSetList, item, "历史数据", "trenddata",2);
                    addDataSet(dataSetList, item, "暂态数据", "event",4);
                    addDataSet(dataSetList, item, "运行数据", "devRunTrend",5);
                } else {
                    addDataSet(dataSetList, item, "历史数据", "trenddata",2);
                    addDataSet(dataSetList, item, "暂态数据", "event",4);
                    addDataSet(dataSetList, item, "运行数据", "devRunTrend",5);
                    addDataSet(dataSetList, item, "电度数据", "kilowattHour",6);
                }
                if (isPortableDevice) {
                    addDataSet(dataSetList, item, "实时数据", "realtimedata",1);
                    // 便携式设备特有的数据集
                    addDataSet(dataSetList, item, "测试项数据", "items",7);
                }
                if (isCLdDevice) {
                    // 云前置数据集
                    addDataSet(dataSetList, item, "实时数据", "realtimedata",1);
                }

                deviceManagerVo.setDataLevel(item.getDataLevel());
            }
            if (CollUtil.isNotEmpty(dataSetList)) {
                dataSetList.sort(Comparator.comparing(DeviceManagerVO.DataSetVO::getSort));
            }
            deviceManagerVo.setDataSetList(dataSetList);
            List<CsLinePO> csLinePOS = csLinePOService.findByNdid(csEquipmentDeliveryPo.getNdid());
            if (!csLinePOS.isEmpty()) {
                Optional<CsLinePO> earliest = csLinePOS.stream().min(Comparator.comparing(CsLinePO::getCreateTime));
                deviceManagerVo.setTime(earliest.map(CsLinePO::getCreateTime).orElse(null));
            }
        }
    }

    private void addDataSet(List<DeviceManagerVO.DataSetVO> dataSetList, CsDataSet item, String name, String type, Integer sort) {
        DeviceManagerVO.DataSetVO dataSetVO = new DeviceManagerVO.DataSetVO();
        dataSetVO.setId(item.getId());
        dataSetVO.setName(name);
        dataSetVO.setType(type);
        dataSetVO.setSort(sort);
        dataSetList.add(dataSetVO);
    }

    @Override
    public void updateSoftInfoBynDid(String nDid, String id, Integer module) {
        boolean result;
        LambdaUpdateWrapper<CsEquipmentDeliveryPO> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
        lambdaUpdateWrapper.set(CsEquipmentDeliveryPO::getSoftinfoId, id).set(CsEquipmentDeliveryPO::getModuleNumber, module).eq(CsEquipmentDeliveryPO::getNdid, nDid);
        result = this.update(lambdaUpdateWrapper);
        if (result) {
            refreshDeviceDataCache();
        }
    }

    @Override
    public CsEquipmentDeliveryPO findDevByNDid(String nDid) {
        return this.lambdaQuery().eq(CsEquipmentDeliveryPO::getNdid, nDid).ne(CsEquipmentDeliveryPO::getRunStatus, 0).one();
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<CsEquipmentDeliveryPO> importEquipment(MultipartFile file, HttpServletResponse response) {
        ImportParams params = new ImportParams();
        params.setHeadRows(1);
        params.setTitleRows(1);
        //第一个sheet为台账信息
        params.setStartSheetIndex(0);
        params.setSheetNum(1);
        String regex = "^[A-Za-z0-9]{6,32}$";
        try {
            ExcelImportResult<DeviceExcelTemplete> terminalBaseList = ExcelImportUtil.importExcelMore(file.getInputStream(), DeviceExcelTemplete.class, params);
            //如果存在非法数据，将不合格的数据导出
            List<DeviceExcelTemplete> trueCollect = new ArrayList<>();
            List<DeviceExcelTemplete.IllegalityDeviceExcelTemplete> falseCollect = new ArrayList<>();
            for (int i = 0; i < terminalBaseList.getList().size(); i++) {
                DeviceExcelTemplete deviceExcelTemplete = terminalBaseList.getList().get(i);
                if (!deviceExcelTemplete.getNdid().matches(regex)) {
                    DeviceExcelTemplete.IllegalityDeviceExcelTemplete idlegalityDeviceException = new DeviceExcelTemplete.IllegalityDeviceExcelTemplete();
                    BeanUtils.copyProperties(deviceExcelTemplete, idlegalityDeviceException);
                    idlegalityDeviceException.setMsg("网络设备ID只可为数字或字母,长度至少为6位");
                    falseCollect.add(idlegalityDeviceException);
                    continue;
                }
                CsEquipmentDeliveryPO po = this.queryEquipmentPOByndid(deviceExcelTemplete.getNdid());
                if (!Objects.isNull(po)) {
                    DeviceExcelTemplete.IllegalityDeviceExcelTemplete idlegalityDeviceException = new DeviceExcelTemplete.IllegalityDeviceExcelTemplete();
                    BeanUtils.copyProperties(deviceExcelTemplete, idlegalityDeviceException);
                    idlegalityDeviceException.setMsg("NDID重复");
                    falseCollect.add(idlegalityDeviceException);
                    continue;
                }
                CsEquipmentDeliveryPO one = this.lambdaQuery().eq(CsEquipmentDeliveryPO::getName, deviceExcelTemplete.getName()).ne(CsEquipmentDeliveryPO::getRunStatus, 0).one();
                if (Objects.nonNull(one)) {
                    DeviceExcelTemplete.IllegalityDeviceExcelTemplete idlegalityDeviceException = new DeviceExcelTemplete.IllegalityDeviceExcelTemplete();
                    BeanUtils.copyProperties(deviceExcelTemplete, idlegalityDeviceException);
                    idlegalityDeviceException.setMsg("NDID重复");
                    falseCollect.add(idlegalityDeviceException);
                    continue;
                }
                List<SysDicTreePO> deviceType = dictTreeFeignClient.queryByCodeList(DicDataTypeEnum.DEVICE_TYPE.getCode()).getData();
                //设备类型
                List<SysDicTreePO> children = deviceType.get(0).getChildren();
                Map<String, SysDicTreePO> map = children.stream().collect(Collectors.toMap(SysDicTreePO::getName, dictTreeVO -> dictTreeVO));
                List<SysDicTreePO> collect = children.stream().filter(temp -> Objects.equals(temp.getName(), deviceExcelTemplete.getDevType())).collect(Collectors.toList());
                SysDicTreePO sysDicTreePO = map.get(deviceExcelTemplete.getDevType());
                if (CollectionUtils.isEmpty(collect)) {
                    DeviceExcelTemplete.IllegalityDeviceExcelTemplete idlegalityDeviceException = new DeviceExcelTemplete.IllegalityDeviceExcelTemplete();
                    BeanUtils.copyProperties(deviceExcelTemplete, idlegalityDeviceException);
                    idlegalityDeviceException.setMsg("设备类型不正确");
                    falseCollect.add(idlegalityDeviceException);
                    continue;
                } else {
                    deviceExcelTemplete.setDevType(map.get(deviceExcelTemplete.getDevType()).getId());
                }
                //设备型号
                List<SysDicTreePO> children1 = sysDicTreePO.getChildren();
                Map<String, SysDicTreePO> map2 = children1.stream().collect(Collectors.toMap(SysDicTreePO::getName, dictTreeVO -> dictTreeVO));
                List<SysDicTreePO> collect2 = children1.stream().filter(temp -> Objects.equals(temp.getName(), deviceExcelTemplete.getDevModel())).collect(Collectors.toList());
                if (CollectionUtils.isEmpty(collect2)) {
                    DeviceExcelTemplete.IllegalityDeviceExcelTemplete idlegalityDeviceException = new DeviceExcelTemplete.IllegalityDeviceExcelTemplete();
                    BeanUtils.copyProperties(deviceExcelTemplete, idlegalityDeviceException);
                    idlegalityDeviceException.setMsg("设备类型与设备型号不匹配");
                    falseCollect.add(idlegalityDeviceException);
                    continue;
                } else {
                    deviceExcelTemplete.setDevModel(map2.get(deviceExcelTemplete.getDevModel()).getId());
                    trueCollect.add(deviceExcelTemplete);
                }
            }
            if (!CollectionUtils.isEmpty(trueCollect)) {
                List<CsEquipmentProcessPO> collect1 = new ArrayList<>();
                List<CsEquipmentDeliveryPO> collect = trueCollect.stream().map(temp -> {
                    CsEquipmentDeliveryPO csEquipmentDeliveryPO = new CsEquipmentDeliveryPO();
                    BeanUtils.copyProperties(temp, csEquipmentDeliveryPO);
                    String qr = this.createQr(temp.getNdid());
                    csEquipmentDeliveryPO.setQrPath(qr);
                    String path = this.createPath(temp.getNdid());
                    csEquipmentDeliveryPO.setMac(path);
                    csEquipmentDeliveryPO.setRunStatus(1);
                    csEquipmentDeliveryPO.setStatus(1);
//                    csEquipmentDeliveryPO.setProcess(2);
                    csEquipmentDeliveryPO.setProcess(4);
                    csEquipmentDeliveryPO.setSort(100);
                    CsEquipmentProcessPO csEquipmentProcess = new CsEquipmentProcessPO();
                    csEquipmentProcess.setDevId(csEquipmentDeliveryPO.getNdid());
                    csEquipmentProcess.setOperator(RequestUtil.getUserIndex());
                    csEquipmentProcess.setStartTime(LocalDateTime.now());
//                    csEquipmentProcess.setProcess(1);
                    csEquipmentProcess.setProcess(4);
                    csEquipmentProcess.setStatus(1);
                    collect1.add(csEquipmentProcess);
                    return csEquipmentDeliveryPO;
                }).collect(Collectors.toList());
                csEquipmentProcessPOService.saveBatch(collect1, 500);
                this.saveOrUpdateBatch(collect, 500);
                return collect;
            }
            if (!CollectionUtils.isEmpty(falseCollect)) {
                ExportParams exportExcel = new ExportParams("批量导入模板（请严格按照模板标准填入数据）", "非法设备信息");
                Workbook workbook = ExcelExportUtil.exportExcel(exportExcel, DeviceExcelTemplete.IllegalityDeviceExcelTemplete.class, falseCollect);
                ExcelStyleUtil.exportFileByWorkbook(workbook, "非法设备信息.xlsx", response);
                return null;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /*
     * 物理删除设备相关数据
     *
     * 2.删除cs_ledger
     * 3.删除cs_dev_model_relation
     * 4.删除cs_line
     * 5.删除cs_line_topology_diagram
     * 6.删除cs_device_user
     * */
    @Override
    @DSTransactional
    public void delete(String devId) {
        QueryWrapper<CsLedger> csLedgerQueryWrapper = new QueryWrapper<>();
        /**
         * 删除设备
         */
        csLedgerQueryWrapper.clear();
        csLedgerQueryWrapper.eq("id", devId);
        csLedgerService.remove(csLedgerQueryWrapper);
        csLedgerQueryWrapper.clear();
        csLedgerQueryWrapper.eq("pid", devId);
        List<CsLedger> list = csLedgerService.list(csLedgerQueryWrapper);
        List<String> collect = list.stream().map(CsLedger::getId).collect(Collectors.toList());
        csLedgerService.remove(csLedgerQueryWrapper);
        QueryWrapper<CsDevModelRelationPO> csDevModelRelationPOQueryWrapper = new QueryWrapper<>();
        csDevModelRelationPOQueryWrapper.clear();
        csDevModelRelationPOQueryWrapper.eq("dev_id", devId);
        csDevModelRelationService.remove(csDevModelRelationPOQueryWrapper);
        QueryWrapper<CsDeviceUserPO> csDeviceUserPOQueryWrapper = new QueryWrapper<>();
        csDeviceUserPOQueryWrapper.clear();
        csDeviceUserPOQueryWrapper.eq("device_id", devId);
        csDeviceUserPOService.remove(csDeviceUserPOQueryWrapper);
        if (!CollectionUtils.isEmpty(collect)) {
            QueryWrapper<CsLinePO> csLinePOQueryWrapper = new QueryWrapper<>();
            csLinePOQueryWrapper.clear();
            csLinePOQueryWrapper.in("line_id", collect);
            csLinePOService.remove(csLinePOQueryWrapper);
            //删除监测点限值
            overLimitWlMapper.deleteBatchIds(collect);

            QueryWrapper<AppLineTopologyDiagramPO> appLineTopologyDiagramPOQueryWrapper = new QueryWrapper<>();
            appLineTopologyDiagramPOQueryWrapper.clear();
            appLineTopologyDiagramPOQueryWrapper.in("line_id", collect);
            appLineTopologyDiagramService.remove(appLineTopologyDiagramPOQueryWrapper);
        }
        refreshDeviceDataCache();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void testCompletion(String deviceId, Integer type, String remark) {
        CsEquipmentDeliveryPO one = this.lambdaQuery().eq(CsEquipmentDeliveryPO::getId, deviceId).one();
        this.lambdaUpdate().eq(CsEquipmentDeliveryPO::getId, deviceId).set(CsEquipmentDeliveryPO::getStatus, 1).set(CsEquipmentDeliveryPO::getRunStatus, 1).set(CsEquipmentDeliveryPO::getProcess, type + 1).update();
        this.delete(deviceId);
        List<CsEquipmentProcessPO> list = csEquipmentProcessPOService.lambdaQuery().eq(CsEquipmentProcessPO::getDevId, one.getNdid()).eq(CsEquipmentProcessPO::getProcess, type).orderByDesc(CsEquipmentProcessPO::getStartTime).list();
        //获取最新一条数据；
        CsEquipmentProcessPO csEquipmentProcessPO = list.get(0);
        csEquipmentProcessPOService.lambdaUpdate().eq(CsEquipmentProcessPO::getId, csEquipmentProcessPO.getId()).set(CsEquipmentProcessPO::getStatus, 1).set(CsEquipmentProcessPO::getEndTime, LocalDateTime.now()).set(CsEquipmentProcessPO::getRemark, remark).update();
    }

    @Override
    public void deleteTest(String deviceId, Integer type, String remark) {
        CsEquipmentDeliveryPO one = this.lambdaQuery().eq(CsEquipmentDeliveryPO::getId, deviceId).one();
        this.lambdaUpdate().eq(CsEquipmentDeliveryPO::getId, deviceId).
//                set(CsEquipmentDeliveryPO::getStatus,1).
//                set(CsEquipmentDeliveryPO::getRunStatus,1).
        set(CsEquipmentDeliveryPO::getProcess, type).update();
        this.delete(deviceId);
        List<CsEquipmentProcessPO> list = csEquipmentProcessPOService.lambdaQuery().eq(CsEquipmentProcessPO::getDevId, one.getNdid()).eq(CsEquipmentProcessPO::getProcess, type).orderByDesc(CsEquipmentProcessPO::getStartTime).list();
        //获取最新一条数据；
        CsEquipmentProcessPO csEquipmentProcessPO = list.get(0);
        csEquipmentProcessPOService.lambdaUpdate().eq(CsEquipmentProcessPO::getId, csEquipmentProcessPO.getId()).set(CsEquipmentProcessPO::getStatus, 0).set(CsEquipmentProcessPO::getEndTime, LocalDateTime.now()).set(CsEquipmentProcessPO::getRemark, remark).update();
    }

    @Override
    public void updateSoftInfo(String nDid, String id) {
        boolean result;
        LambdaUpdateWrapper<CsEquipmentDeliveryPO> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
        lambdaUpdateWrapper.eq(CsEquipmentDeliveryPO::getNdid, nDid).ne(CsEquipmentDeliveryPO::getRunStatus, 0).set(CsEquipmentDeliveryPO::getSoftinfoId, id);
        result = this.update(lambdaUpdateWrapper);
        if (result) {
            refreshDeviceDataCache();
        }
    }

    @Override
    public void updateModuleNumber(String nDid, Integer number) {
        boolean result;
        LambdaUpdateWrapper<CsEquipmentDeliveryPO> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
        lambdaUpdateWrapper.eq(CsEquipmentDeliveryPO::getNdid, nDid).ne(CsEquipmentDeliveryPO::getRunStatus, 0).set(CsEquipmentDeliveryPO::getModuleNumber, number);
        result = this.update(lambdaUpdateWrapper);
        if (result) {
            refreshDeviceDataCache();
        }
    }

    @Override
    public boolean rebootDevice(String nDid) {
        boolean result = false;
        try {
            askDeviceDataFeignClient.rebootDevice(nDid);
            Thread.sleep(3000);
            String key = AppRedisKey.CONTROL + nDid;
            Object object = redisUtil.getObjectByKey(key);
            if (Objects.nonNull(object)) {
                String value = object.toString();
                if (Objects.equals(value, "success")) {
                    result = true;
                }
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return result;
    }

    @Override
    public List<CsEquipmentDeliveryPO> getAll() {
        return this.lambdaQuery().ne(CsEquipmentDeliveryPO::getRunStatus, 0).list();
    }

    @Override
    public List<CsEquipmentDeliveryPO> getAllOnline() {
        return this.lambdaQuery().ne(CsEquipmentDeliveryPO::getRunStatus, 0).eq(CsEquipmentDeliveryPO::getUsageStatus, 1).isNotNull(CsEquipmentDeliveryPO::getNodeId).list();
    }

    @Override
    public boolean judgeDevModel(String nDid) {
        boolean result = false;
        //获取装置id
        CsEquipmentDeliveryPO po = findDevByNDid(nDid);
        //设备类型
        String code = dictTreeFeignClient.queryById(po.getDevType()).getData().getCode();
        if (Objects.equals(DicDataEnum.DEV_CLD.getCode(), code) || Objects.equals(DicDataEnum.CONNECT_DEV.getCode(), code)) {
            result = true;
        }
        return result;
    }

    @Override
    public CsEquipmentDeliveryPO getDevByLineId(String lineId) {
        CsLinePO linePO = csLinePOService.getById(lineId);
        return this.lambdaQuery().eq(CsEquipmentDeliveryPO::getId, linePO.getDeviceId()).ne(CsEquipmentDeliveryPO::getRunStatus, 0).one();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public CsEquipmentDeliveryPO saveCld(CsEquipmentDeliveryAddParm param) {
        boolean result;
        StringUtil.containsSpecialCharacters(param.getNdid());
        CsEquipmentDeliveryPO po = this.queryEquipmentPOByndid(param.getNdid());
        if (!Objects.isNull(po)) {
            throw new BusinessException(AlgorithmResponseEnum.NDID_ERROR);
        }
        CsEquipmentDeliveryPO csEquipmentDeliveryPo = new CsEquipmentDeliveryPO();
        BeanUtils.copyProperties(param, csEquipmentDeliveryPo);
        csEquipmentDeliveryPo.setStatus(3);
        csEquipmentDeliveryPo.setRunStatus(1);
        csEquipmentDeliveryPo.setUsageStatus(1);
        csEquipmentDeliveryPo.setProcess(4);
        csEquipmentDeliveryPo.setMac(param.getMac());

        //这边自动分配前置机进程号，需要做判断 1.新增装置时，判断是否已经是最大装置数量；2.根据进程号，选择最少的分配
        csEquipmentDeliveryPo.setNodeId(param.getNodeId());
        //判断1
        Node node = nodeService.getNodeById(param.getNodeId());
        List<CsEquipmentDeliveryPO> devList = getListByNodeId(param.getNodeId());
        if (ObjectUtil.isNotEmpty(devList) && devList.size() >= node.getNodeDevNum()) {
            throw new BusinessException(AlgorithmResponseEnum.OVER_MAX_DEV_COUNT);
        }
        //判断2
        int process = findLeastFrequentProcess(devList, node.getMaxProcessNum());
        csEquipmentDeliveryPo.setNodeProcess(process);
        result = this.save(csEquipmentDeliveryPo);

        //新增设备与模板之间的关系 获取云前置模板
        CsDevModelPO csDevModelPO = csDevModelService.getCldModel();
        CsDevModelRelationAddParm relationAddParam = new CsDevModelRelationAddParm();
        relationAddParam.setDevId(csEquipmentDeliveryPo.getId());
        relationAddParam.setModelId(csDevModelPO.getId());
        relationAddParam.setDid(1);
        CsDevModelRelationPO relation = csDevModelRelationService.addDevModelRelation(relationAddParam);

        //新增台账数据
        CsLedger csLedger = new CsLedger();
        csLedger.setId(csEquipmentDeliveryPo.getId());
        csLedger.setPid(param.getProjectId());
        csLedger.setPids("0," + param.getEngineeringId() + "," + param.getProjectId());
        csLedger.setLevel(2);
        csLedger.setName(param.getName());
        csLedger.setState(1);
        csLedger.setSort(0);
        int addLedger = csLedgerMapper.insert(csLedger);

        //1.谁新建的设备，则认为是该设备的主用户，新建用户设备关系表数据
        //2.获取当前工程已经关注的用户列表，新增用户和设备的关系
        boolean addUser = false;
        List<DevDetailDTO> devDetailList = csLedgerFeignClient.getDevInfoByEngineerIds(Collections.singletonList(param.getEngineeringId())).getData();
        if (!CollectionUtils.isEmpty(devDetailList)) {
            List<String> devIds = devDetailList.stream().map(DevDetailDTO::getEquipmentId).collect(Collectors.toList());
            LambdaQueryWrapper<CsDeviceUserPO> wrapper = new LambdaQueryWrapper<>();
            wrapper.in(CsDeviceUserPO::getDeviceId, devIds);
            List<CsDeviceUserPO> csDeviceUserPOList = csDeviceUserPOService.list(wrapper);
            if (!CollectionUtils.isEmpty(csDeviceUserPOList)) {
                List<String> userIds = csDeviceUserPOList.stream().map(CsDeviceUserPO::getSubUserId).distinct().collect(Collectors.toList());
                UserDevParam param1 = new UserDevParam();
                param1.setUserId(csEquipmentDeliveryPo.getId());
                param1.setList(userIds);
                addUser = csDeviceUserPOService.addRelation(param1);
            }
        } else {
            UserDevParam param1 = new UserDevParam();
            param1.setUserId(csEquipmentDeliveryPo.getId());
            param1.setList(null);
            addUser = csDeviceUserPOService.addRelation(param1);
        }

        if (result && ObjectUtil.isNotNull(relation) && addLedger > 0 && addUser) {
            refreshDeviceDataCache();
        }
        return csEquipmentDeliveryPo;
    }

    @Override
    @DSTransactional
    public Boolean delCldDev(String id) {
        CsEquipmentDeliveryPO one = this.lambdaQuery().eq(CsEquipmentDeliveryPO::getId, id).one();
        LambdaQueryWrapper<CsEquipmentDeliveryPO> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(CsEquipmentDeliveryPO::getId, id);
        boolean update = this.remove(lambdaQueryWrapper);
        List<CsLedger> list = csLedgerService.lambdaQuery().eq(CsLedger::getPid, id).list();
        if (!CollectionUtils.isEmpty(list)) {
            List<String> collect = list.stream().map(CsLedger::getId).collect(Collectors.toList());
            LambdaQueryWrapper<CsLinePO> csLinePOLambdaQueryWrapper = new LambdaQueryWrapper<>();
            csLinePOLambdaQueryWrapper.in(CsLinePO::getLineId, collect);
            csLinePOService.remove(csLinePOLambdaQueryWrapper);
            //删除监测点限值
            overLimitWlMapper.deleteBatchIds(collect);
        }
        LambdaQueryWrapper<CsLedger> csLedgerLambdaQueryWrapper = new LambdaQueryWrapper<>();
        csLedgerLambdaQueryWrapper.clear();
        csLedgerLambdaQueryWrapper.eq(CsLedger::getId, id);
        csLedgerService.remove(csLedgerLambdaQueryWrapper);
        csLedgerLambdaQueryWrapper.clear();
        csLedgerLambdaQueryWrapper.eq(CsLedger::getPid, id);
        csLedgerService.remove(csLedgerLambdaQueryWrapper);
        csDevModelRelationService.lambdaUpdate().eq(CsDevModelRelationPO::getDevId, id).set(CsDevModelRelationPO::getStatus, 0).update();
        if (update) {
            refreshDeviceDataCache();
        }
        //删除用户设备关系表数据
        LambdaQueryWrapper<CsDeviceUserPO> csDeviceUserPOLambdaQueryWrapper = new LambdaQueryWrapper<>();
        csDeviceUserPOLambdaQueryWrapper.eq(CsDeviceUserPO::getDeviceId, id);
        csDeviceUserPOService.remove(csDeviceUserPOLambdaQueryWrapper);
        //新增操作日志
        CsTerminalLogs csTerminalLogs = new CsTerminalLogs();
        csTerminalLogs.setDeviceId(id);
        csTerminalLogs.setOperateType(2);
        csTerminalLogs.setIsPush(0);
        csTerminalLogs.setNodeId(one.getNodeId());
        csTerminalLogs.setNodeProcess(one.getNodeProcess());
        csTerminalLogs.setDeviceName(one.getName());
        csTerminalLogsMapper.insert(csTerminalLogs);
        //删除设备注册表
        csDeviceRegistryService.deleteByCurrentNdid(one.getNdid());
        return true;
    }

    @Override
    public Boolean updateCldDev(CsEquipmentDeliveryAuditParm param) {
        StringUtil.containsSpecialCharacters(param.getNdid());
        boolean result;
        LambdaQueryWrapper<CsEquipmentDeliveryPO> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(CsEquipmentDeliveryPO::getNdid, param.getNdid()).in(CsEquipmentDeliveryPO::getStatus, Arrays.asList(1, 2, 3)).ne(CsEquipmentDeliveryPO::getId, param.getId());
        int countByAccount = this.count(lambdaQueryWrapper);
        //大于等于1个则表示重复
        if (countByAccount >= 1) {
            throw new BusinessException(AlgorithmResponseEnum.NDID_ERROR);
        }
        LambdaQueryWrapper<CsEquipmentDeliveryPO> lambdaQueryWrapper2 = new LambdaQueryWrapper<>();
        lambdaQueryWrapper2.eq(CsEquipmentDeliveryPO::getId, param.getId());
        CsEquipmentDeliveryPO po = this.baseMapper.selectOne(lambdaQueryWrapper2);
        if (!Objects.equals(po.getNdid(), param.getNdid())) {
            List<CsDeviceRegistry> updateList = new ArrayList<>();
            //修改了mac地址
            List<CsDeviceRegistry> list = csDeviceRegistryService.queryByCurrentNdid(po.getNdid());
            list.forEach(item->{
                CsDeviceRegistry csDeviceRegistry = new CsDeviceRegistry();
                csDeviceRegistry.setId(item.getId());
                csDeviceRegistry.setCurrentNdid(param.getNdid());
                csDeviceRegistry.setOldNdid(po.getNdid());
                csDeviceRegistry.setClDid(item.getClDid());
                csDeviceRegistry.setFirstSeenTime(item.getFirstSeenTime());
                updateList.add(csDeviceRegistry);
            });
            csDeviceRegistryService.updateBatch(updateList);
        }
        List<CsEquipmentDeliveryPO> list = this.lambdaQuery().ne(CsEquipmentDeliveryPO::getId, param.getId()).ne(CsEquipmentDeliveryPO::getNdid, param.getNdid()).eq(CsEquipmentDeliveryPO::getName, param.getName()).ne(CsEquipmentDeliveryPO::getRunStatus, 0).list();
        if (!CollectionUtils.isEmpty(list)) {
            throw new BusinessException("设备名称不能重复");
        }
        CsEquipmentDeliveryPO csEquipmentDeliveryPo = new CsEquipmentDeliveryPO();
        BeanUtils.copyProperties(param, csEquipmentDeliveryPo);
        csEquipmentDeliveryPo.setMac(param.getNdid().replaceAll("(.{2})", "$1:").substring(0, 17));
        result = this.updateById(csEquipmentDeliveryPo);
        //修改台账树中的设备名称
        CsLedger csLedger = csLedgerService.findDataById(param.getId());
        if (!Objects.isNull(csLedger)) {
            CsLedgerParam.Update csLedgerParam = new CsLedgerParam.Update();
            BeanUtils.copyProperties(csLedger, csLedgerParam);
            csLedgerParam.setName(param.getName());
            csLedgerParam.setSort(param.getSort());
            csLedgerService.updateLedgerTree(csLedgerParam);
        }
        //新增台账日志
        CsTerminalLogs csTerminalLogs = new CsTerminalLogs();
        csTerminalLogs.setDeviceId(csEquipmentDeliveryPo.getId());
        csTerminalLogs.setOperateType(1);
        csTerminalLogs.setIsPush(0);
        csTerminalLogsMapper.insert(csTerminalLogs);
        return result;
    }

    @Override
    public List<CsEquipmentDeliveryPO> getCldDevByIp(String id, List<Integer> runFlag) {
        //运行状态转换
        List<Integer> state = new ArrayList<>();
        if (CollectionUtil.isNotEmpty(runFlag)) {
            if (runFlag.contains(0)) {
                state.add(1);
            }
            if (runFlag.contains(2)) {
                state.add(0);
            }
            //fixme 目前治理设备只有启用和停用，没有那么多状态，如果前置传递其他状态，先设置一个不存在的状态
            state.add(9);
        }
        LambdaQueryWrapper<CsEquipmentDeliveryPO> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(CsEquipmentDeliveryPO::getNodeId, id).in(ObjectUtil.isNotEmpty(state), CsEquipmentDeliveryPO::getUsageStatus, state).eq(CsEquipmentDeliveryPO::getDevAccessMethod, "CLD");
        return this.list(lambdaQueryWrapper);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateCldDevStatus(String nodeId, Integer processNo) {
        //获取需要修改的设备信息
        LambdaQueryWrapper<CsEquipmentDeliveryPO> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(CsEquipmentDeliveryPO::getNodeId, nodeId).eq(CsEquipmentDeliveryPO::getNodeProcess, processNo).eq(CsEquipmentDeliveryPO::getUsageStatus, 1).eq(CsEquipmentDeliveryPO::getRunStatus, 2);
        List<CsEquipmentDeliveryPO> devList = this.list(lambdaQueryWrapper);
        if (CollectionUtil.isNotEmpty(devList)) {
            //修改设备运行状态
            devList.forEach(item -> {
                item.setRunStatus(1);
                insertPqsCommunicateRecord(LocalDateTime.now().format(DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN)), item.getNdid(), 0);
            });
            this.updateBatchById(devList);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void flipCldDevStatus(String time, String devId, Integer status) {
        LambdaQueryWrapper<CsEquipmentDeliveryPO> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(CsEquipmentDeliveryPO::getId, devId).eq(CsEquipmentDeliveryPO::getUsageStatus, 1).ne(CsEquipmentDeliveryPO::getUsageStatus, 0);
        CsEquipmentDeliveryPO po = this.getOne(lambdaQueryWrapper);
        if (ObjectUtil.isNotNull(po)) {
            po.setRunStatus(status);
            this.updateById(po);
            //修改设备的运行状态，则录入influxdb表数据
            int communicateType = status == 2 ? 1 : 0;
            insertPqsCommunicateRecord(time, po.getNdid(), communicateType);
            //如果是设备上线，则询问设备的版本信息
            CloudTopicMessage message = new CloudTopicMessage();
            message.setGuid(IdUtil.simpleUUID());
            message.setDevId(po.getId());
            message.setFrontId(po.getNodeId());
            message.setNode(po.getNodeProcess());
            CloudTopicMessage.Detail detail = new CloudTopicMessage.Detail();
            detail.setType(1112);
            detail.setMsg(new ArrayList<>());
            message.setDetail(detail);
            cloudTopicMessageTemplate.sendMember(message, po.getNodeId());
        }
    }

    // 时序数据库写入数据
    private void insertPqsCommunicateRecord(String time, String devId, Integer status) {
        PqsCommunicateDto pqsCommunicateDto = new PqsCommunicateDto();
        pqsCommunicateDto.setTime(time);
        pqsCommunicateDto.setDevId(devId);
        pqsCommunicateDto.setType(status);
        pqsCommunicateDto.setDescription(status == 0 ? "通讯中断" : "通讯正常");
        csCommunicateService.insertion(pqsCommunicateDto);
    }

    @Override
    public List<CsEquipmentDeliveryPO> getDevListByProjectId(String projectId) {
        QueryWrapper<CsLedger> csLedgerQueryWrapper = new QueryWrapper();
        csLedgerQueryWrapper.lambda().eq(CsLedger::getPid, projectId).eq(CsLedger::getState, 1);
        List<CsLedger> csLedgers = csLedgerMapper.selectList(csLedgerQueryWrapper);
        if (CollectionUtil.isEmpty(csLedgers)) {
            return new ArrayList<>();
        }
        List<String> collect = csLedgers.stream().map(CsLedger::getId).collect(Collectors.toList());
        return this.lambdaQuery().in(CsEquipmentDeliveryPO::getId, collect).list();
    }

    @Override
    public List<CsEquipmentDeliveryPO> getDataByNodeIdAndProcessNo(String nodeId, Integer processNo) {
        LambdaQueryWrapper<CsEquipmentDeliveryPO> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(CsEquipmentDeliveryPO::getNodeId, nodeId).ne(CsEquipmentDeliveryPO::getRunStatus, 0).eq(CsEquipmentDeliveryPO::getDevAccessMethod, "CLD");
        if (ObjectUtil.isNotNull(processNo)) {
            lambdaQueryWrapper.eq(CsEquipmentDeliveryPO::getNodeProcess, processNo);
        }
        return this.list(lambdaQueryWrapper);
    }

    @Override
    public List<CsEquipmentDeliveryPO> getListByIds(List<String> devList) {
        return this.lambdaQuery().in(CsEquipmentDeliveryPO::getId, devList).list();
    }

    @Override
    public IPage<DevVersionVO> versionPage(CsEquipmentDeliveryQueryParm baseParam) {
        //获取台账信息
        List<CsLedgerVO> allList = csLedgerMapper.getAll();
        Map<String, CsLedgerVO> csLedgerMap = CollUtil.isEmpty(allList) ? Collections.emptyMap() : allList.stream().collect(Collectors.toMap(CsLedgerVO::getId, item -> item));

        LambdaQueryWrapper<CsEquipmentDeliveryPO> wrapper = new LambdaQueryWrapper<>();
        wrapper.ne(CsEquipmentDeliveryPO::getRunStatus, 0)
                .eq(StrUtil.isNotBlank(baseParam.getDevModel()), CsEquipmentDeliveryPO::getDevModel, baseParam.getDevModel())
                .eq(StrUtil.isNotBlank(baseParam.getIcd()), CsEquipmentDeliveryPO::getIcd, baseParam.getIcd())
                .eq(ObjectUtil.isNotNull(baseParam.getUpgrade()), CsEquipmentDeliveryPO::getUpgrade, baseParam.getUpgrade())
                .like(StrUtil.isNotBlank(baseParam.getSearchValue()), CsEquipmentDeliveryPO::getName, baseParam.getSearchValue());
        Page<CsEquipmentDeliveryPO> page = this.page(new Page<>(PageFactory.getPageNum(baseParam), PageFactory.getPageSize(baseParam)), wrapper);

        List<DevVersionVO> records = new ArrayList<>();
        List<DevVersionVO> finalRecords = records;
        page.getRecords().forEach(item -> {
            DevVersionVO devVersionVO = BeanUtil.copyProperties(item, DevVersionVO.class);
            List<User> userData = userFeignClient.getUserByIdList(Collections.singletonList(item.getUpdateBy())).getData();
            if (CollectionUtil.isNotEmpty(userData)) {
                devVersionVO.setUpdateByName(userData.get(0).getName());
            }
            if (StrUtil.isNotBlank(item.getSoftinfoId())) {
                CsSoftInfoPO softInfoPO = csSoftInfoService.getById(item.getSoftinfoId());
                if (ObjectUtil.isNotNull(softInfoPO)) {
                    devVersionVO.setVersion(softInfoPO.getAppVersion());
                    devVersionVO.setVersionDate(softInfoPO.getAppDate());
                    devVersionVO.setCrc(softInfoPO.getAppCheck());
                    devVersionVO.setAccessMethod(item.getDevAccessMethod());
                    devVersionVO.setNDid(item.getNdid());
                    CsEdDataPO csEdDataPO = csEdDataService.findByDevTypeIdAndVersionNo(item.getDevModel(), softInfoPO.getAppVersion());
                    if (ObjectUtil.isNotNull(csEdDataPO)) {
                        devVersionVO.setProtocolVersion(csEdDataPO.getVersionAgreement());
                    }
                }
            }
            devVersionVO.setStatus(item.getUsageStatus());
            devVersionVO.setIcd(item.getIcd());
            if (StrUtil.isNotBlank(item.getDevModel())) {
                SysDicTreePO sysDicTreePO = dictTreeFeignClient.queryById(item.getDevModel()).getData();
                if (ObjectUtil.isNotNull(sysDicTreePO)) {
                    devVersionVO.setDevModelName(sysDicTreePO.getName());
                }
            }
            //获取工程名称和项目名称
            CsLedgerVO devVo = csLedgerMap.get(item.getId());
            if (!Objects.isNull(devVo)) {
                //项目
                CsLedgerVO proVo = csLedgerMap.get(devVo.getPid());
                devVersionVO.setAssociatedProject(proVo.getName());
                //工程
                CsLedgerVO engineeringVo = csLedgerMap.get(proVo.getPid());
                devVersionVO.setAssociatedEngineering(engineeringVo.getName());
            }
            //设备通讯状态
            Integer runStatus = item.getRunStatus();
            if (!Objects.isNull(runStatus) && runStatus == 2) {
                devVersionVO.setConnectStatus(1);
            } else {
                devVersionVO.setConnectStatus(0);
            }
            finalRecords.add(devVersionVO);
        });

        Page<DevVersionVO> resultPage = new Page<>(PageFactory.getPageNum(baseParam), PageFactory.getPageSize(baseParam));
        if (ObjectUtil.isNotNull(baseParam.getConnectStatus())) {
            records = records.stream().filter(item -> baseParam.getConnectStatus().equals(item.getConnectStatus())).collect(Collectors.toList());
        }
        resultPage.setTotal(CollectionUtil.isEmpty(records) ? 0 : records.size());
        resultPage.setRecords(records);
        resultPage.setTotal(page.getTotal());
        resultPage.setMaxLimit(page.getMaxLimit());
        resultPage.setPages(page.getPages());

        return resultPage;
    }

    @Override
    public void uploadUpgradeFile(String id, String nDid) {
        CsEdDataPO po = csEdDataService.getById(id);
        if (Objects.isNull(po)) {
            throw new BusinessException("系统中未找到对应的版本信息");
        }
        //判断是否正在上传
        Object object = redisUtil.getObjectByKey("uploading:" + nDid);
        if (Objects.nonNull(object)) {
            throw new BusinessException(AlgorithmResponseEnum.FILE_UPLOADING);
        }
        Object object2 = redisUtil.getObjectByKey("fileDowning:" + nDid);
        if (Objects.nonNull(object2)) {
            throw new BusinessException(AlgorithmResponseEnum.FILE_BUSY);
        }
        try {
            //记录升级日志
            CsEquipmentDeliveryVO vo = queryEquipmentByndid(nDid);
            CsUpgradeLogs csUpgradeLogs = new CsUpgradeLogs();
            csUpgradeLogs.setDevId(vo.getId());
            csUpgradeLogs.setVersionNo(po.getVersionNo());
            csUpgradeLogs.setResult(3);
            csUpgradeLogs.setUserName(RequestUtil.getUsername());
            csUpgradeLogs.setLoginName(RequestUtil.getLoginName());
            csUpgradeLogsService.save(csUpgradeLogs);
            //根据路径获取文件
            String path = po.getFilePath();
            InputStream inputStream = fileStorageUtil.getFileStream(path);
            byte[] bytes = StreamUtils.copyToByteArray(inputStream);
            if (bytes.length == 0) {
                throw new BusinessException("文件流转成数组，数据为空");
            }
            int length = bytes.length;

            String version = csTopicFeignClient.find(nDid).getData();
            if (StrUtil.isNotBlank(version) || Objects.isNull(version)) {
                version = "V1";
            }
            int cap = 50 * 1024;
            //需要分片处理 一帧按50k大小传递
            if (length > cap){
                //需要循环的次数
                int times = bytes.length / cap + 1;
                for (int i = 1; i <= times; i++) {
                    //发送数据给前端
                    String json = "{fileName:"+po.getFilePath()+",allStep:"+times+",nowStep:"+i+"}";
                    publisher.send("/Web/Progress/UpgradeFile/" + nDid, new Gson().toJson(json), 1, false);
                    LogMessage logDto = new LogMessage();
                    byte[] lsBytes;
                    if (length > 50*1024) {
                        lsBytes = Arrays.copyOfRange(bytes, (i - 1) * cap, i * cap);
                        ReqAndResDto.Req req = getPojo(i,path,bytes.length,lsBytes,(i-1)*cap);
                        publisher.send("/Pfm/DevFileCmd/" + version + "/" + nDid, new Gson().toJson(req), 1, false);
                        logDto.setOperate(nDid + "设备上送文件,这是第" + i + "帧");
                        logDto.setResult(1);
                        length = length - cap;
                        //判断是否重发
                        sendNextStep(logDto,path,bytes.length,lsBytes,(i-1)*cap,version,nDid,i,false);
                        FileRedisDto fileRedisDto = (FileRedisDto) redisUtil.getObjectByKey(AppRedisKey.UPLOAD.concat(nDid).concat(String.valueOf(i)));
                        //重发之后判断继续循环还是跳出循环
                        if (!Objects.isNull(fileRedisDto) && !Objects.equals(fileRedisDto.getCode(),200)) {
                            redisUtil.delete("uploading:" + nDid);
                            break;
                        }
                    } else {
                        lsBytes = Arrays.copyOfRange(bytes, (i - 1) * cap, bytes.length);
                        ReqAndResDto.Req req = getPojo(i,path,bytes.length,lsBytes,(i-1)*cap);
                        publisher.send("/Pfm/DevFileCmd/" + version + "/" + nDid, new Gson().toJson(req), 1, false);
                        logDto.setOperate(id + "设备上送文件，这是最后一帧，为第" + i + "帧");
                        logDto.setResult(1);
                        //判断是否重发
                        sendNextStep(logDto,path,bytes.length,lsBytes,(i-1)*cap,version,nDid,i,true);
                    }
                    logMessageTemplate.sendMember(logDto);
                }
            } else {
                String json = "{fileName:"+path+",allStep:\""+1+"\",nowStep:"+1+"}";
                publisher.send("/Web/Progress/UpgradeFile/" + nDid, new Gson().toJson(json), 1, false);
                ReqAndResDto.Req req = getPojo(1,path,length,bytes,0);
                publisher.send("/Pfm/DevFileCmd/" + version + "/" + nDid, new Gson().toJson(req), 1, false);
                LogMessage logDto = new LogMessage();
                logDto.setOperate(nDid + "系统上送文件,当前文件只有1帧");
                logDto.setResult(1);
                logMessageTemplate.sendMember(logDto);
                //判断是否重发
                sendNextStep(logDto,path,length,bytes,0,version,nDid,1,false);
            }
        } catch (IOException e) {
            redisUtil.delete("uploading:" + nDid);
            redisUtil.delete("fileDowning:" + nDid);
            LogMessage logDto = new LogMessage();
            logDto.setResult(0);
            logDto.setOperate("系统上传升级文件，获取文件流");
            logDto.setFailReason(e.getMessage());
            logMessageTemplate.sendMember(logDto);
            throw new BusinessException("获取文件流失败");
        } catch (Exception e) {
            redisUtil.delete("uploading:" + nDid);
            redisUtil.delete("fileDowning:" + nDid);
            LogMessage logDto = new LogMessage();
            logDto.setResult(0);
            logDto.setOperate("系统上传升级文件");
            logDto.setFailReason(e.getMessage());
            logMessageTemplate.sendMember(logDto);
            throw new BusinessException(AccessResponseEnum.UPLOAD_ERROR);
        }
    }

    @Override
    public void commitUpgradeFile(Integer mid, String nDid, String version) {
        //修改升级日志
        CsEquipmentDeliveryVO vo = queryEquipmentByndid(nDid);
        csUpgradeLogsService.updateLogs(vo.getId(), null, 2);
        //组装报文
        ReqAndResDto.Req reqAndResParam = new ReqAndResDto.Req();
        reqAndResParam.setMid(mid);
        reqAndResParam.setDid(0);
        reqAndResParam.setPri(AccessEnum.FIRST_CHANNEL.getCode());
        reqAndResParam.setType(Integer.parseInt(TypeEnum.TYPE_32.getCode()));
        reqAndResParam.setExpire(-1);
        UpgradeDevDto upgradeDevDto = new UpgradeDevDto();
        upgradeDevDto.setChk(1);
        reqAndResParam.setMsg(upgradeDevDto);
        redisUtil.saveByKeyWithExpire("chk:" + nDid, 1, 10L);
        publisher.send("/Pfm/DevFileCmd/" + version + "/" + nDid, new Gson().toJson(reqAndResParam), 1, false);
    }

    /**
     * 上送文件至装置
     */
    public ReqAndResDto.Req getPojo(Integer mid, String fileName, Integer allLength, byte[] bytes, Integer offset) {
        //组装报文
        ReqAndResDto.Req reqAndResParam = new ReqAndResDto.Req();
        reqAndResParam.setMid(mid);
        reqAndResParam.setDid(0);
        reqAndResParam.setPri(AccessEnum.FIRST_CHANNEL.getCode());
        reqAndResParam.setType(Integer.parseInt(TypeEnum.TYPE_32.getCode()));
        reqAndResParam.setExpire(-1);

        UpgradeDevDto upgradeDevDto = new UpgradeDevDto();
        int cmpAlg;
        String ext = fileName.substring(fileName.lastIndexOf('.') + 1);
        if (Objects.equals(ext.toLowerCase(), "zip")) {
            cmpAlg = 1;
        } else if (Objects.equals(ext.toLowerCase(), "tar")) {
            cmpAlg = 2;
        } else {
            cmpAlg = 0;
        }
        upgradeDevDto.setCmpAlg(cmpAlg);
        upgradeDevDto.setName(fileName);
        upgradeDevDto.setFileSize(allLength);
        upgradeDevDto.setOffset(offset);
        upgradeDevDto.setLen(bytes.length);
        upgradeDevDto.setData(Base64.getEncoder().encodeToString(bytes));
        reqAndResParam.setMsg(upgradeDevDto);
        return reqAndResParam;
    }

    /**
     * 根据装置响应来判断发送的内容
     */
    public void sendNextStep(LogMessage logDto, String path, int length, byte[] bytes, Integer offset, String version, String id, int mid, boolean result) {
        try {
            for (int i = 0; i < 30; i++) {
                if (result) {
                    Thread.sleep(10000);
                } else {
                    Thread.sleep(1000);
                }
                FileRedisDto fileRedisDto = (FileRedisDto) redisUtil.getObjectByKey(AppRedisKey.UPLOAD.concat(id).concat(String.valueOf(mid)));
                if (Objects.isNull(fileRedisDto)) {
                    FileRedisDto fileRedis = new FileRedisDto();
                    fileRedis.setCode(400);
                    redisUtil.saveByKeyWithExpire(AppRedisKey.UPLOAD.concat(id).concat(String.valueOf(mid)),fileRedis,10L);
                } else {
                    if (Objects.equals(fileRedisDto.getCode(),200)) {
                        break;
                    } else {
                        FileRedisDto fileRedis = new FileRedisDto();
                        fileRedis.setCode(400);
                        redisUtil.saveByKeyWithExpire(AppRedisKey.UPLOAD.concat(id).concat(String.valueOf(mid)),fileRedis,10L);
                        ReqAndResDto.Req req = getPojo(mid,path,length,bytes,offset);
                        publisher.send("/Pfm/DevFileCmd/" + version + "/" + id, new Gson().toJson(req), 1, false);
                        logDto.setOperate(id + "系统上送文件,装置响应失败，重新发送，这是第" + (i+1) + "次");
                        logDto.setResult(1);
                        logMessageTemplate.sendMember(logDto);
                    }
                }
            }
        } catch (InterruptedException e) {
            assert logDto != null;
            logDto.setResult(0);
            logDto.setOperate("平台重新上送文件");
            logDto.setFailReason(e.getMessage());
            logMessageTemplate.sendMember(logDto);
            throw new BusinessException(AccessResponseEnum.RELOAD_UPLOAD_ERROR);
        }
    }

    @Override
    public List<CsEquipmentDeliveryPO> getRunPortableDev(String userId) {
        List<CsEquipmentDeliveryPO> result = new ArrayList<>();
        DictTreeVO vo = dictTreeFeignClient.queryByCode(DicDataEnum.PORTABLE.getCode()).getData();
        if (ObjectUtil.isNull(vo)) {
            throw new BusinessException("便携式设备类型字典缺失");
        }
        LambdaQueryWrapper<CsEquipmentDeliveryPO> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(CsEquipmentDeliveryPO::getDevType, vo.getId()).ne(CsEquipmentDeliveryPO::getRunStatus, 0).eq(CsEquipmentDeliveryPO::getUsageStatus, 1);
        //获取用户已经绑定的便携式设备
        List<String> devList = csDeviceUserPOService.findDevByUserId(userId);
        if (CollectionUtil.isNotEmpty(devList)) {
            lambdaQueryWrapper.notIn(CsEquipmentDeliveryPO::getId, devList);
        }
        result = this.list(lambdaQueryWrapper);
        if (CollectionUtil.isNotEmpty(result)) {
            //获取已经接入过系统的设备，未接入的不给分配
            LambdaQueryWrapper<CsLedger> queryWrapper = new LambdaQueryWrapper<>();
            List<String> ids = result.stream().map(CsEquipmentDeliveryPO::getId).collect(Collectors.toList());
            queryWrapper.in(CsLedger::getId, ids);
            List<CsLedger> list = csLedgerService.list(queryWrapper);
            if (CollectionUtil.isNotEmpty(list)) {
                Set<String> idSet = list.stream().map(CsLedger::getId).collect(Collectors.toSet());
                return result.stream().filter(data -> idSet.contains(data.getId())).collect(Collectors.toList());
            } else {
                result.clear();
                return result;
            }
        }
        return result;
    }

    //根据前置机id获取装置数量
    public List<CsEquipmentDeliveryPO> getListByNodeId(String nodeId) {
        LambdaQueryWrapper<CsEquipmentDeliveryPO> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(CsEquipmentDeliveryPO::getNodeId, nodeId).in(CsEquipmentDeliveryPO::getRunStatus, Arrays.asList(1, 2));
        return this.list(lambdaQueryWrapper);
    }

    public int findLeastFrequentProcess(List<CsEquipmentDeliveryPO> items, Integer process) {
        Map<Integer, Long> processCounts = items.stream().collect(Collectors.groupingBy(CsEquipmentDeliveryPO::getNodeProcess, Collectors.counting()));
        for (int i = 1; i <= process; i++) {
            processCounts.putIfAbsent(i, 0L);
        }
        //如果列表为空，默认返回1
        return processCounts.entrySet().stream().min(Comparator.comparingLong(Map.Entry::getValue)).map(Map.Entry::getKey).orElse(1);
    }

    /**
     * 根据ndid生成二维码
     *
     * @param ndid
     * @return
     */
    private String createQr(String ndid) {
        String filePath = "";
        //生成二维码文件
        try {
            QrParam qrParam = new QrParam();
            qrParam.setData(ndid);
            BufferedImage bufferedImage = QRCodeUtil.getBufferedImage(JSONObject.toJSONString(qrParam));
            InputStream inputStream = bufferedImageToInputStream(bufferedImage);
            String fileName = ndid + ".png";
            filePath = fileStorageUtil.uploadStream(inputStream, OssPath.APP_DEVICE_QR, fileName);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return filePath;
    }

    public InputStream bufferedImageToInputStream(BufferedImage image) {
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        try {
            ImageIO.write(image, "png", os);
            InputStream input = new ByteArrayInputStream(os.toByteArray());
            return input;
        } catch (IOException e) {
            log.error("提示:", e);
        }
        return null;
    }

    /*将ndid转成mac地址*/
    public String createPath(String nDid) {
        // 使用StringBuilder来构建新的字符串
        StringBuilder output = new StringBuilder();
        // 遍历输入字符串，每两个字符分割并添加冒号
        for (int i = 0; i < nDid.length(); i += 2) {
            if (i > 0) {
                //添加冒号分隔符
                output.append(StrPool.COLON);
            }
            if (i + 1 < nDid.length()) {
                // 检查是否有足够的字符来分割成两组
                // 每两个字符添加到新字符串
                output.append(nDid.substring(i, i + 2));
            } else {
                // 如果只剩一个字符，可以根据需要添加处理逻辑
                output.append(nDid.charAt(i));
            }
        }
        return output.toString();
    }
}