package com.tykj.dev.device.confirmcheck.controller;

import com.alibaba.fastjson.JSON;
import com.github.wenhao.jpa.Specifications;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.tykj.dev.config.GlobalMap;
import com.tykj.dev.config.swagger.AutoDocument;
import com.tykj.dev.device.confirmcheck.common.CcNodeType;
import com.tykj.dev.device.confirmcheck.common.CheckChart;
import com.tykj.dev.device.confirmcheck.common.CheckType;
import com.tykj.dev.device.confirmcheck.common.TaskPeriod;
import com.tykj.dev.device.confirmcheck.entity.domain.DeviceCheckDetail;
import com.tykj.dev.device.confirmcheck.entity.domain.DeviceCheckLink;
import com.tykj.dev.device.confirmcheck.entity.domain.DeviceCheckStat;
import com.tykj.dev.device.confirmcheck.entity.vo.*;
import com.tykj.dev.device.confirmcheck.repository.DeviceCheckBillDao;
import com.tykj.dev.device.confirmcheck.repository.DeviceCheckDetailDao;
import com.tykj.dev.device.confirmcheck.repository.DeviceCheckLinkDao;
import com.tykj.dev.device.confirmcheck.repository.DeviceCheckStatDao;
import com.tykj.dev.device.confirmcheck.service.ConfirmCheckService;
import com.tykj.dev.device.confirmcheck.utils.ObjTransUtil;
import com.tykj.dev.device.file.entity.FileRet;
import com.tykj.dev.device.file.service.FilesUtil;
import com.tykj.dev.device.library.repository.DeviceLibraryDao;
import com.tykj.dev.device.library.service.DeviceLibraryCacheService;
import com.tykj.dev.device.library.subject.domin.DeviceLibrary;
import com.tykj.dev.device.selfcheck.controller.SelfCheckController;
import com.tykj.dev.device.task.repository.TaskDao;
import com.tykj.dev.device.task.service.TaskLogService;
import com.tykj.dev.device.task.service.TaskService;
import com.tykj.dev.device.task.subject.bto.TaskBto;
import com.tykj.dev.device.task.subject.bto.TaskLogBto;
import com.tykj.dev.device.task.subject.domin.Task;
import com.tykj.dev.device.user.base.enums.AuExample;
import com.tykj.dev.device.user.base.ret.UserShenRe;
import com.tykj.dev.device.user.cache.AreaCache;
import com.tykj.dev.device.user.read.service.MessageService;
import com.tykj.dev.device.user.read.subject.bto.MessageBto;
import com.tykj.dev.device.user.subject.dao.AreaDao;
import com.tykj.dev.device.user.subject.dao.UnitsDao;
import com.tykj.dev.device.user.subject.entity.Area;
import com.tykj.dev.device.user.subject.entity.Units;
import com.tykj.dev.device.user.subject.entity.User;
import com.tykj.dev.device.user.subject.entity.bto.AreaUnit;
import com.tykj.dev.device.user.subject.service.AuService;
import com.tykj.dev.device.user.subject.service.UnitsService;
import com.tykj.dev.device.user.subject.service.UserService;
import com.tykj.dev.device.user.util.AuthenticationUtils;
import com.tykj.dev.misc.base.ResultObj;
import com.tykj.dev.misc.base.StatusEnum;
import com.tykj.dev.misc.exception.ApiException;
import com.tykj.dev.misc.utils.JacksonUtil;
import com.tykj.dev.socket.MyWebSocket;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.function.Function;

import static com.tykj.dev.misc.base.BusinessEnum.CONFIRM_CHECK_DETAIL;
import static com.tykj.dev.misc.base.BusinessEnum.CONFIRM_CHECK_STAT;
import static com.tykj.dev.misc.base.StatusEnum.*;
import static java.util.stream.Collectors.*;

/**
 * @author dengdiyi
 */
@SuppressWarnings("ALL")
@RestController
@RequestMapping(value = "/check/confirm")
@AutoDocument
//@Transactional(rollbackFor = Exception.class)
@Slf4j
@Api(tags = "核查模块", description = "核查模块", position = 1)
public class DeviceCheckController {

    final String CHECK_RESULT_WAIT = "等待省查阅";
    final String CHECK_RESULT_DONE = "已查阅";
    Map<Integer, String> desMap = new HashMap<>();
    Map<Integer, String> desBillMap = new HashMap<>();
    private int specialDetailId = 0;
    private List<String> specialUnits = new ArrayList<>();

    @Autowired
    private DeviceCheckLinkDao linkRepo;
    @Autowired
    private DeviceCheckStatDao statRepo;
    @Autowired
    private DeviceCheckBillDao billRepo;
    @Autowired
    private AreaDao areaRepo;
    @Autowired
    private UnitsDao unitsRepo;
    @Autowired
    private DeviceLibraryDao deviceRepo;
    @Autowired
    private DeviceCheckDetailDao detailRepo;
    @Autowired
    private DeviceLibraryCacheService dcService;
    @Autowired
    private ObjTransUtil transUtil;
    @Autowired
    private TaskDao taskRepo;
    @Autowired
    private TaskService taskService;
    @Autowired
    private AuthenticationUtils authenticationUtils;
    @Autowired
    private AreaCache areaCache;
    @Autowired
    private AuService auService;
    @Autowired
    private ConfirmCheckService ccService;
    @Autowired
    private UserService userService;
    @Autowired
    private MyWebSocket myWebSocket;
    @Autowired
    private SelfCheckController selfCheckController;
    @Autowired
    private MessageService messageService;
    @Autowired
    private TaskLogService taskLogService;
    @Autowired
    private UnitsService unitsService;

    @GetMapping("/area/{fatherId}")
    @ApiOperation(value = "查询指定区域下的所有区域信息")
    public ResponseEntity<ResultObj<List<Area>>> findAreaUnderId(@PathVariable Integer fatherId) {
        return ResponseEntity.ok(new ResultObj<>(areaRepo.findByFatherId(fatherId)));
    }

    /**
     * 查询默认的可以被核查单位的清单(省本直,省直属,市局),同时过滤掉那些已经被代管的单位
     *
     * @return type = 2 and level in (1,2) ,filter escrow = 1 的单位
     */
    @GetMapping("/unit")
    @ApiOperation(value = "查询默认的可以被核查单位的清单(省本直,省直属,市局),同时过滤掉那些已经被代管的单位")
    public ResponseEntity findDefaultUnits() {
        List<Units> units = unitsRepo.findAllByTypeInOrLevelIn(Lists.newArrayList(2), Lists.newArrayList(1, 2))
                .stream().filter(Units::isNotEscrow)
                .collect(toList());
        // 需要去除掉被托管的单位
        return ResponseEntity.ok(new ResultObj<>(units));

    }

    /**
     * @param id
     * @return
     */
    @GetMapping("/handleUsers/{id}")
    @ApiOperation(value = "查询该detail经手的相关人员")
    public ResponseEntity getDetailHanderUsers(@PathVariable Integer id) {
        DeviceCheckDetail detail = detailRepo.findById(id).orElseThrow(() -> new ApiException("没有找到id = " + id + "的detail自查单！"));
        Map<String, String> handUsers = new HashMap<>();

        Integer startUserId = detail.getCreateUserId();
        Integer userAId = detail.getUserAId();
        Integer userBId = detail.getUserBId();
        Integer userCId = detail.getUserCId();

        if (Objects.nonNull(startUserId) && startUserId != 0) {
            handUsers.put("startUserId", userService.findByUser(startUserId).getName());
        } else {
            handUsers.put("startUserId", "NoBody");
        }

        if (Objects.nonNull(userAId) && userAId != 0) {
            handUsers.put("userA", userService.findByUser(userAId).getName());
        } else {
            handUsers.put("userA", "NoBody");
        }

        if (Objects.nonNull(userBId) && userBId != 0) {
            handUsers.put("userBId", userService.findByUser(userBId).getName());
        } else {
            handUsers.put("userBId", "NoBody");
        }

        if (Objects.nonNull(userCId) && userCId != 0) {
            handUsers.put("userCId", userService.findByUser(userCId).getName());
        } else {
            handUsers.put("userCId", "NoBody");
        }

        return ResponseEntity.ok(handUsers);
    }

    @GetMapping("/judge/{taskId}")
    @ApiOperation(value = "根据taskId判断该task任务是由核查发起的还是由检查发起的")
    public ResponseEntity judgeExamDetail(@PathVariable Integer taskId) {
        Task task = taskRepo.findById(taskId).orElseThrow(() -> new ApiException("没有找到该Task任务，你提供的taskId = " + taskId));

        Task finalTaks;
        if (task.getParentTaskId() != null && task.getParentTaskId() != 0) {
            Integer pId = task.getParentTaskId();
            Task parentTask = taskRepo.findById(pId).get();
            // 存在父任务,找到父任务，二次判断
            if (parentTask.getParentTaskId() != null && parentTask.getParentTaskId() != 0) {
                Integer ppId = parentTask.getParentTaskId();
                Task doubleParentTask = taskRepo.findById(ppId).get();
                finalTaks = doubleParentTask;
            } else {
                // 不存在二级父任务，那么头节点就是父任务
                finalTaks = parentTask;
            }
        } else {
            // 不存在父任务，那么头节点就是自己
            finalTaks = task;
        }

        return ResponseEntity.ok(finalTaks.getCustomInfo());

    }

    @ApiOperation(value = "根据id查询核查统计数据", notes = "可以通过这个接口查询核查统计数据")
    @GetMapping("/stat/{id}")
    public CheckStatVo findStatById(@PathVariable Integer id) {
        //还要查询出所有的
        CheckStatVo statVoList = statRepo.findById(id)
                .map(transUtil::checkStatDo2Vo)
                .orElse(CheckStatVo.empty());
        return statVoList;
    }

    @ApiOperation(value = "根据关键字分页查询核查统计数据")
    @PostMapping("/stat")
    public Page<CheckStatTableVo> findStatByKeyword(
            @RequestBody CheckBillSelectVo checkBillSelectVo
    ) {
        return ccService.findAllStatTable(checkBillSelectVo);
    }

    @ApiOperation(value = "根据billId查询核查详情数据", notes = "可以通过这个接口查询核查详情数据")
    @GetMapping("/detail/{id}")
    public ResponseEntity<ResultObj<CheckDetailVo>> findDetail(@PathVariable Integer id) {
        CheckDetailVo detailVoList = detailRepo.findById(id)
                .map(transUtil::CheckDetailDo2Vo)
                .orElse(null);
        return ResponseEntity.ok(new ResultObj<>(detailVoList));
    }

    @ApiOperation(value = "提供taskId,该接口返回可以跳转到详情页面的数据", notes = "提供taskId,该接口返回可以跳转到详情页面的数据")
    @GetMapping("/msgLink")
    public ResponseEntity msgLink(@RequestParam Integer taskId) {
        // 如果是businessType = 7 走unionLink 否则走detail接口
        Task task = taskRepo.findById(taskId).orElseThrow(() -> new ApiException(String.format("没有找到taskId = %d 的任务", taskId)));
        if (task.getBusinessType().equals(CONFIRM_CHECK_STAT.id)) {
            return unionLink(7, task.getBillId());
        } else if (task.getBusinessType().equals(CONFIRM_CHECK_DETAIL.id)) {
            return findDetail(task.getBillId());
        }

        return ResponseEntity.status(400).body("该businessType不在处理范围内!本接口处理的type = 7 ,8");
    }

    @ApiOperation(value = "根据id查询核查详情数据", notes = "可以通过这个接口查询核查详情数据")
    @GetMapping("/detail/unit/{unitId}")
    public ResponseEntity<ResultObj<List<CheckDetailVo>>> findDetailsByUnitId(@PathVariable Integer unitId) {
        Units unit = unitsRepo.findById(unitId).get();
        Specification<DeviceCheckDetail> pred = Specifications.<DeviceCheckDetail>and()
                .eq("checkUnit", unit.getName())
                .build();
        List<CheckDetailVo> detailVoList = detailRepo.findAll(pred).stream()
                .map(transUtil::CheckDetailDo2Vo)
                .collect(toList());
        return ResponseEntity.ok(new ResultObj<>(detailVoList));
    }

    @GetMapping("/detail/refresh/remove/{checkId}/{detailId}")
    public ResponseEntity<String> removeUnitTask(@PathVariable Integer checkId, @PathVariable Integer detailId) {
        log.info("[核查模块] 移除billId = {} 中  detailId = {}的数据 ", checkId, detailId);
        DeviceCheckDetail detail = detailRepo.findById(detailId).get();
        DeviceCheckStat dcs = statRepo.findById(checkId).get();
        removeDetailFromDcs(detail, dcs);
        log.info("[核查模块] 从stat id = {} 中删除detail id = {}的数据", checkId, detailId);
        // 删除task
        Task task = taskRepo.findByBillIdAndBusinessType(detailId, CONFIRM_CHECK_DETAIL.id).get();
        taskRepo.deleteById(task.getId());
        log.info("[核查模块] 删除task id = {} 的任务", task.getId());
        return ResponseEntity.ok("成功删除数据");
    }

    @GetMapping("/detail/refresh/active/{id}")
    public ResponseEntity<String> activeSpecialData(@PathVariable Integer id, @RequestParam Integer unitId1, @RequestParam(defaultValue = "0") Integer unitId2) {

        String unitName1 = "";
        String unitName2 = "";
        unitName1 = unitsRepo.findById(unitId1).get().getName();
        unitName2 = unitsRepo.findById(unitId2).get().getName();

        if (!StringUtils.isEmpty(unitName1)) {
            specialUnits.add(unitName1);
        }

        if (!StringUtils.isEmpty(unitName2)) {
            specialUnits.add(unitName2);
        }

        log.info("[核查模块] 特殊数据处理,设置specialDetailId = {},specialUnitsId = {} {} ", id, unitId1, unitId2);
        specialDetailId = id;

        return ResponseEntity.ok("设置特殊数据处理成功");
    }

    @GetMapping("/detail/refresh/reset")
    public ResponseEntity<String> resetSpecialData() {
        log.info("[核查模块] 重置特殊数据! ");
        specialDetailId = 0;
        specialUnits = new ArrayList<>();

        return ResponseEntity.ok("设置特殊数据处理成功");
    }

    @ApiOperation(value = "根据id 刷新 核查详情数据", notes = "可以通过这个接口 刷新 核查详情数据")
    @GetMapping("/detail/refresh/{id}")
    public ResponseEntity<ResultObj<CheckDetailVo>> refreshDetail(@PathVariable Integer id) {
        DeviceCheckDetail detail = detailRepo.findById(id)
                .orElseThrow(() -> new ApiException("没有找到指定的detail数据，你提供的是 id = " + id));
        String checkUnit = unitsRepo.findById(detail.getCheckUnitId()).get().getName();
        //查出所有装备 分为4类 A 所在是本单位 B 所属是本单位 其中
        List<DeviceLibrary> allDevices = dcService.getAllDeviceLibraryList();
        Map<Boolean, List<DeviceLibrary>> devLib = getDevLibMap(checkUnit, allDevices);

        // 更新detail
        List<DeviceLibrary> devInLib = devLib.get(true);
        List<DeviceLibrary> devNotInLib = devLib.get(false);

        // 特殊单位处理 if DetailId = x , 则补充添加 name=[],[]单位的装备进来
        if (specialDetailId != 0 && specialUnits.size() != 0 && id == specialDetailId) {
            String unitsString = specialUnits.stream().collect(joining(",", "[", "]"));
            log.info("[核查模块] 特殊数据处理，要补充特殊数据的省本级detail Id ={},要补充的单位名 = {}", id, unitsString);

            for (String specialUnit : specialUnits) {
                log.info("[核查模块] 正在补充 {} 的数据", specialUnit);
                Map<Boolean, List<DeviceLibrary>> specialLib = getDevLibMap(specialUnit, allDevices);
                devInLib.addAll(specialLib.get(true));
                devNotInLib.addAll(specialLib.get(false));
            }
        }

        // 代管 - 如果当前自查为省本级自查，则需要将被代管的装备加入到省本级
        Units unit = unitsRepo.findByName(checkUnit);
        boolean isProv = unit.getLevel() == 1 && unit.getType() == 1;
        if (isProv) {
            //获得所有的需要被代管的单位的在库与非在库装备，添加到省本级的在库与非在库中去
            List<Units> escrowUnits = unitsRepo.findByTypeAndEscrow(2, 1);
            for (Units eu : escrowUnits) {
                log.info("[核查模块] 正在将 {} 单位的装备托管代理到 {} 单位", checkUnit, eu.getName());
                Map<Boolean, List<DeviceLibrary>> libMap = getDevLibMap(eu.getName(), allDevices);
                devInLib.addAll(libMap.get(true));
                devNotInLib.addAll(libMap.get(false));
            }

        }

        detail.updateDevice(devInLib, devNotInLib);

        //更新应查装备数量
        detail.setCheckingCount(devInLib.size());
        detail = detailRepo.save(detail);

        CheckDetailVo cdVo = transUtil.CheckDetailDo2Vo(detail);

        return ResponseEntity.ok(new ResultObj<>(cdVo));
    }

    /**
     * 该接口负责处理以下跳转情况
     * 1 - 统计跳转
     * 1-1 省核查页面 - 市的检查以及省直属的自查任务
     * 2 - 详情跳转
     *
     * @param type   7  统计
     * @param billId 业务id
     * @return
     */
    @ApiOperation(value = "统一跳转接口", notes = "可以通过这个接口进行跳转")
    @GetMapping("/link")
    public ResponseEntity<LinkVo> unionLink(@RequestParam Integer type, @RequestParam Integer billId) {

        if (type != CONFIRM_CHECK_STAT.id) {
            throw new ApiException("link跳转接口提供的type应当为7,你提供的是 " + type);
        }

        LinkVo linkVo = new LinkVo();
        // 默认的shutDown状态为false
        linkVo.setShutDown(false);

        //type = 7 统计
        DeviceCheckStat ct = statRepo.findById(billId).get();
        CheckStatVo ctVo = transUtil.checkStatDo2Vo(ct);
        linkVo.setTitle(ctVo.getTitle());
        LocalDateTime endTime = ctVo.getEndTime();
        endTime = LocalDateTime.of(ctVo.getEndTime().toLocalDate(), LocalTime.MAX);
        //新增修改时间
        LocalDateTime updateTime = ctVo.getUpdateTime();
        linkVo.setEndTime(LocalDateTime.of(ctVo.getEndTime().toLocalDate(), LocalTime.MAX).toLocalDate());
        List<LinkCheckDetail> lcdList = new ArrayList<>();
        List<LinkExamDetail> ledList = new ArrayList<>();

        //是否要走缓存，如果该任务taskId = 20000，则调用上次保存的结果(取另外一张表)，否则即时计算并保存结果
        // 获取核查/检查的任务节点
        Task rootTask = taskRepo.findAllByBillIdAndBusinessType(billId, type).stream()
                .filter(task -> Objects.nonNull(task.getCustomInfo()) && ("exam".equals(task.getCustomInfo()) || "check".equals(task.getCustomInfo())))
                .findFirst()
                .orElseThrow(() -> new ApiException("[核查模块]没有找到对应billId的统计任务，您给的billId = " + billId));

        // key = stat id ,value = LinkVo
        if (rootTask.getBillStatus().equals(CHECK_SHUT_DOWN.id)) {
            // 拿之前保存的缓存
            linkVo = linkRepo.findByStatId(billId)
                    .orElseThrow(() -> new ApiException(String.format("没有找到stat id = %d 的统计缓存,请检查数据", billId)))
                    .toVo()
                    .getLinkVo();
            linkVo.setShutDown(true);
        } else {
            // 正常处理，即时计算
            // checkType = 0 核查，checkType = 1 检查
            if (ctVo.getCheckType() == 0) {
                linkVo.setType(1);
                // 根据tpye和billId找到父级节点 - 默认了最顶级的省核查节点应当为一个

                // 先检出所有类型为7或者8的Task集合 方便后续操作
                Specification<Task> pred = Specifications.<Task>and()
                        .in("businessType", 7, 8)
                        .build();

                List<Task> cacheTask = taskRepo.findAll(pred)
                        .stream()
                        .filter(task -> Objects.nonNull(task.getParentTaskId()))
                        .collect(toList());
                //key = parentId , value childList
                Map<Integer, List<Task>> parentTaskMap = cacheTask.stream()
                        .collect(groupingBy(Task::getParentTaskId));

                // 找到所有的子节点
                List<Task> childTask = parentTaskMap.get(rootTask.getId());

                long startTime = System.currentTimeMillis();
                long finalTime = System.currentTimeMillis();


                for (Task child : childTask) {
                    // 将子节点任务中的 统计数据确认任务 给过滤掉
                    if (child.getTitle().contains("统计数据确认任务")) {
                        continue;
                    }

                    Integer childBusType = child.getBusinessType();
                    Integer childBusId = child.getBillId();

                    // 处理子节点，子节点有两种类型 - 检查类统计与自查类数据
                    if (childBusType.equals(CONFIRM_CHECK_STAT.id)) {
                        log.info("[处理子节点] 子节点为检查类子节点");
                        startTime = System.currentTimeMillis();
                        //市检查
                        Integer unitId = child.getOwnUnit();
                        String unitName = unitsRepo.findById(unitId).get().getName();
                        DeviceCheckStat cdc = statRepo.findById(child.getBillId()).get();
                        CheckStatVo cdcVo = transUtil.checkStatDo2Vo(cdc);
                        //todo 这里更改从detail中去查看并求和
                        //找到chilid的子child detail 并求和 ccTask即为所有的自查任务集合

                        List<Task> cctask = parentTaskMap.get(child.getId());
                        if (Objects.isNull(cctask)) {
                            cctask = new ArrayList<>();
                        }

                        List<CheckAreaStatVo> totalList = new ArrayList<>();

                        for (Task cct : cctask) {
                            long start1 = System.currentTimeMillis();
                            Integer detailId = cct.getBillId();
                            DeviceCheckDetail childDetail = detailRepo.findById(detailId).get();

                            List<CheckAreaStatVo> casList = parseStatString2Vo(child.parse2Bto(), childDetail).stream()
                                    .map(CheckDeviceStatVo::getAreaStatList)
                                    .flatMap(checkAreaStatVos -> checkAreaStatVos.stream())
                                    .collect(toList());
                            totalList.addAll(casList);
                            long end1 = System.currentTimeMillis();
                            log.info("[性能分析] task id = {} cost {} ms", cct.getId(), end1 - start1);
                        }
//
                        String areaName = auService.findOne(AuExample.UnitId, child.getOwnUnit()).getName();
//
                        LinkCheckDetail lcd = cas2lcd(totalList, child, cctask, areaName);

                        lcd.setCheckUnit(unitName);
                        if (child.getTitle().contains("统计确认待办任务")) {
                            lcd.setCheckSituation("统计确认待办任务");
                        }

                        lcdList.add(lcd);
                        finalTime = System.currentTimeMillis();
                        log.info("[TEST] child-BILL id = {}的检查任务 , COST {} MS", child.getBillId(), finalTime - startTime);
                    } else {
                        log.info("[处理子节点] 子节点为自查类子节点");
                        startTime = System.currentTimeMillis();

                        // 省直属 ，省本级自查
                        // 直属自查 -> detail里面找
                        DeviceCheckDetail childDetail = detailRepo.findById(childBusId).get();
                        String unitName = unitsRepo.findById(childDetail.getCheckUnitId()).get().getName();

                        List<CheckDeviceStatVo> list = parseStatString2Vo(child.parse2Bto(), childDetail);
                        finalTime = System.currentTimeMillis();
                        log.info("[test] 自查装备数据转换消耗了{}ms", finalTime - startTime);
                        List<CheckAreaStatVo> casList = list.stream()
                                .map(CheckDeviceStatVo::getAreaStatList)
                                .flatMap(checkAreaStatVos -> checkAreaStatVos.stream())
                                .collect(toList());
                        //自查的areaName要从detail里找
                        CheckAreaStatVo cas;
                        if (casList.isEmpty()) {
                            cas = new CheckAreaStatVo("默认地区", 0, 0, 0, 0, 0, 0);
                        } else {
//                            cas = combineCaList(casList, unitName);
                            cas = casList.get(0);
                        }

//                        LinkExamDetail led = rev2led(child, endTime, updateTime, cas.reverse());
                        LinkExamDetail led = cas2led(cas, child, endTime, updateTime);

                        led.setCheckUnit(unitName);
                        ledList.add(led);

                        finalTime = System.currentTimeMillis();
                        log.info("[TEST] 子节点账单 ID = {} 的自查任务消耗了 {} MS", child.getBillId(), finalTime - startTime);
                    }
                }
                linkVo.setLcDetail(lcdList);
                linkVo.setLeDetail(ledList);
                finalTime = System.currentTimeMillis();
                log.info("[TEST] COST {} MS", finalTime - startTime);
            }
            // check type = 1 检查页面 看见的是自查 1=省核查,2=省检查,3=3级结构下的市检查,4=2级结构下的市检查
            if (ctVo.getCheckType() == 1) {
                Integer createUserId = ctVo.getCreateUserId();
                Integer ownUnit = rootTask.getOwnUnit();
                if (unitsService.isProvUnit(ownUnit)) {
                    linkVo.setType(2);
                } else if (rootTask.getParentTaskId() != 0) {
                    linkVo.setType(3);
                } else {
                    linkVo.setType(4);
                }

                // 找到所有的子节点
                List<Task> childTask = taskRepo.findAllByParentTaskId(rootTask.getId());

                //核查组成员和名称 找到父亲级节点 然后找到父节点的统计节点
                String[] checkArray = ctVo.getRemark().split("\\|");// x,a |x ,b
                List<String> groupNames = new ArrayList<>();
                List<String> userNames = new ArrayList<>();

                for (String ca : checkArray) {
                    if (!ca.contains(",")) {
                        continue;
                    }
                    String[] carry = ca.split(",");
                    groupNames.add(carry[0]);
                    String uname = "";
                    for (int i = 1; i < carry.length; i++) {
                        uname += carry[i] + " ";
                    }
                    userNames.add(uname);
                }

                // 检查的节点都是自查
                int i = 0;
                for (Task child : childTask) {

                    LinkExamDetail led = getLed(endTime, updateTime, child);

                    //设置名称
                    led.setExamName(groupNames.get(i));
                    led.setExamUser(userNames.get(i));

                    ledList.add(led);
                    i++;
                }
                i = 0;
                linkVo.setLcDetail(lcdList);
                linkVo.setLeDetail(ledList);

            }
        }

        //将此次使用的linkVo更新
        DeviceCheckLink updateLink = linkRepo.findByStatId(billId).orElse(new DeviceCheckLink());
        updateLink.setLinkText(JSON.toJSONStringWithDateFormat(linkVo, "yyyy-MM-dd"));
        updateLink.setStatId(billId);
        linkRepo.save(updateLink);

        return ResponseEntity.ok(linkVo);

    }

    /**
     * @param periodId 1-月度 2-季度 3-年度
     * @return
     */
    @ApiOperation(value = "更新自动核查周期", notes = "更新自动核查周期")
    @PutMapping("/task/{periodId}")
    public ResponseEntity updateTaskPeriod(
            @PathVariable @ApiParam(value = "核查周期,1-月度 2-季度 3-年度", example = "1") Integer periodId) {
        if (periodId < 1 || periodId > 3) {
            return ResponseEntity.status(400).body(new ResultObj<>("提供了错误的周期参数!应该是 1/2/3 , 您提供的是" + periodId));
        }
        TaskPeriod period = TaskPeriod.values()[periodId - 1];

        // 更新最新的周期
        ccService.updateTaskPeriod(period);
        // 结束当前任务，开启下轮任务
        ccService.stopAutoCheckCron();
        boolean startSuccess = ccService.startAutoCheckCron();

        if (startSuccess) {
            String nextTime = ccService.getNextTaskDate().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
            return ResponseEntity.ok(new ResultObj<>(String.format("更新自动核查周期成功，下一次任务的执行时间为%s", nextTime)));
        } else {
            return ResponseEntity.status(400).body("自动核查更新周期失败了!");
        }
    }

    @ApiOperation(value = "获取下一次自动核查任务执行的具体时间", notes = "获取下一次自动核查任务执行的具体时间")
    @GetMapping("/task/next")
    public Map<String, LocalDate> getNextTaskTime() {
        return ImmutableMap.of("nextTaskTime", ccService.getNextTaskDate());
    }

    @ApiOperation(value = "获取当前自动核查的核查周期", notes = "monthly-月度,quarterly-季度,yearly-年度")
    @GetMapping("/task/current")
    public Map<String, String> getCurrentTaskPeriod() {
        String periodName = ccService.getCurrentTaskPeriod().getCronExpression().name();
        return ImmutableMap.of("currentTaskPeriod", periodName);
    }

    /**
     * 发起检查时获取核查的标题
     *
     * @param examJobId
     * @return
     */
    @GetMapping("/title/{examJobId}")
    public ResponseEntity getNames(@PathVariable Integer examJobId) {
        // 根据检查的主键id 查询到prov city Stat的title
        Integer provId = taskService.get(examJobId).getParentTaskId();
        // 检查的job id 找到 father 进而找到 father的billid 进而找到title Id
        Integer statId = taskService.get(provId).getBillId();
        DeviceCheckStat deviceCheckStat = statRepo.findById(statId).get();
        String title = deviceCheckStat.getTitle();
        String remark = deviceCheckStat.getRemark();
        return ResponseEntity.ok(new CheckTitleAndTimeVo(title, deviceCheckStat.getEndTime(), remark));
    }

    @ApiOperation(value = "发起自动核查", notes = "发起自动核查")
    @PostMapping("/auto")
    public ResultObj<Map<String, List<Integer>>> startAutoCheck() {
        Map<String, List<Integer>> resultIds = ccService.autoCheck();
        return new ResultObj<>(resultIds, "自动核查任务发起成功");
    }

    @ApiOperation(value = "根据地区ID获取下级的单位", notes = "根据地区ID获取下级的单位")
    @GetMapping("/under/{areaId}")
    public ResponseEntity getUnitsUnderAreaId(@PathVariable Integer areaId, @RequestParam(defaultValue = "true") boolean filter) {
        //1.获取child AreaId List
        List<Integer> areaIds = areaRepo.findAllByFatherId(areaId).stream()
                .map(Area::getId)
                .collect(toList());

        //2. 根据childId 获得对应的unit
        List<Units> unitsList = unitsRepo.findAllByAreaIdIn(areaIds);

        // 根据filter 决定是否将没有数据的装备给过滤掉
        if (filter) {
            unitsList = unitsList.stream()
                    .filter(unit -> deviceRepo.findAllByOwnUnit(unit.getName()).size() != 0)
                    .collect(toList());
        }

        return ResponseEntity.ok(unitsList);
    }

    @ApiOperation(value = "检查地区是否可以发起核查", notes = "检查地区是否可以发起核查")
    @PostMapping("/checkPossible")
    public ResponseEntity checkPossible(@RequestBody UnitIds unitIds) {
        List<String> unitNames = unitIds.getIds().stream()
                .map(id -> auService.findOne(AuExample.UnitId, id))
                .map(AreaUnit::getUnitName)
                .collect(toList());

        boolean findEmpty = false;
        String alertString = "[";

        for (String unitName : unitNames) {
            List<DeviceLibrary> devices = deviceRepo.findAllByOwnUnit(unitName);
            if (devices.size() == 0) {
                findEmpty = true;
                alertString += unitName + " ";
            }
        }

        alertString += "]";
        alertString += "单位没有装备数据，请重新勾选！";

        return ResponseEntity.ok(ImmutableMap.of("empty", findEmpty, "msg", alertString));
    }

    @ApiOperation(value = "发起核查", notes = "对指定单位发起核查任务")
    @PostMapping("/startCheck")
    public ResponseEntity startCheck(@RequestBody CheckCheckVo ccVO) {
        // 构建省的统计账单
        Integer startUnitId = ccVO.getUnitId();
        Units startUnit = unitsRepo.findById(startUnitId).get();
        List<Units> checkedUnits = unitsRepo.findAllById(ccVO.getUnitRange());
        // 1.发起自己的自查 (市,tpye=2,省 level = 0,1,2)
        // 2.发起自己的检查(只是市级别的 level=2)
        List<String> checkedUnitNames = checkedUnits.stream().map(Units::getName).collect(toList());

        log.info("[核查模块]发起核查，发起单位为{},被查单位为{}", startUnit.getName(), checkedUnitNames);

        // 构建省的统计账单,所有的checkedUnits都要被包含到统计中去
        DeviceCheckStat provinceCheckStat = initStatData(ccVO.getTitle(), ccVO.getRemark(), 0, 0, startUnit.getName(), checkedUnits, ccVO.getEndTime().atStartOfDay());
        provinceCheckStat.setCheckType(CheckType.CT_CHECK);
        Integer statId = statRepo.save(provinceCheckStat).getId();
        List<CheckDeviceStatVo> deviceStatVos = Arrays.stream(
                Objects.requireNonNull(JacksonUtil.readValue(provinceCheckStat.getStatInfo(), CheckDeviceStatVo[].class)))
                .collect(toList());

        // 构建省的统计任务
        TaskBto provStatTask = new Task(CHECK_STAT_1.id, getUnitDateString(startUnit, ccVO.getTitle()), 0, ".0.", CONFIRM_CHECK_STAT.id, statId, startUnitId)
                .parse2Bto();
        provStatTask.getInvolveUserIdList().add(authenticationUtils.getAuthentication().getCurrentUserInfo().getUserId());
        provStatTask.getInvolveUserIdList().add(0);
        provStatTask.setCurrentPoint(1);
        provStatTask.setCustomInfo("check");
        provStatTask = taskService.start(provStatTask);

        desMap.put(provStatTask.getId(), ccVO.getRemark());
        //构建市的检查任务(level=2)
        List<Units> examCheckUnits = checkedUnits.stream()
                .filter(units -> Arrays.asList(2).contains(units.getLevel()))
                .collect(toList());

        Map<Integer, Integer> cityExamIdMap = new HashMap<>();

        for (Units unit : examCheckUnits) {
            //构建市的统计账单
            String cityTitle = unit.getName() + "检查统计";
            DeviceCheckStat cityCheckStat = initStatData(cityTitle, ccVO.getRemark(), 0, 0, unit.getName(), Collections.emptyList(), ccVO.getEndTime().atStartOfDay());
            cityCheckStat.setCheckType(CheckType.CT_EXAM);
            Integer cityStatId = statRepo.save(cityCheckStat).getId();
            log.info("[核查模块] {} 检查统计账单构建完毕,id 为 {}", unit.getName(), cityStatId);

            // 构建市的统计任务
            TaskBto cityStatTask = new TaskBto(CHECK_EXAM_STAT_0.id, getUnitDateString(unit, ccVO.getTitle()), provStatTask.getId(), addNode(provStatTask.getNodeIdDetail(), provStatTask.getId()), CONFIRM_CHECK_STAT.id, cityStatId, unit.getUnitId(), 0);
            cityStatTask.getInvolveUserIdList().add(0);
            cityStatTask.setCurrentPoint(1);
            cityStatTask.setCustomInfo("exam");
            cityStatTask = taskService.start(cityStatTask);

            //将市的检查id记录下，以便设置市的自查任务的父级节点
            cityExamIdMap.put(unit.getUnitId(), cityStatTask.getId());
        }

        // 构建自查任务(levl = 0,1) tpye = 2
        List<Units> selfCheckUnits = checkedUnits.stream()
                .filter(units -> Arrays.asList(0, 1).contains(units.getLevel()) || units.getType() == 2)
                .collect(toList());

        // 获取所有在库装备 lifeStatus ==2 or 14
        Map<String, List<DeviceLibrary>> devInLib = dcService.getAllDeviceLibraryList().stream()
//                .filter(device -> device.getOwnUnit().equals(device.getLocationUnit()))
                .filter(d -> d.getLifeStatus() == 2 || d.getLifeStatus() == 14)
                .collect(groupingBy(DeviceLibrary::getOwnUnit));

        // 获取所有非在库装备 ls != 2 and != 14
        Map<String, List<DeviceLibrary>> devNotInLib = dcService.getAllDeviceLibraryList().stream()
//                .filter(device -> !device.getOwnUnit().equals(device.getLocationUnit()))
                .filter(d -> d.getLifeStatus() != 2 && d.getLifeStatus() != 14)
                .collect(groupingBy(DeviceLibrary::getOwnUnit));


        User loginUser = authenticationUtils.getAuthentication().getCurrentUserInfo();

        for (Units unit : selfCheckUnits) {
            //核查组成员名称用当前登录用户
            String names = loginUser.getName();

            // 如果是市level=2 的话,father Id为市的检查id
            Integer fatherId = 0;
            Integer initStatusId = 0;

            // level = 1 或者2 市省级 否则 就是区级 ，市省级的自查初始状态160，区级的自查初始状态140
            // type = 2 160 ，tpye = 1 && level = 1 || level = 2 160
            boolean directProv = unit.getType() == 2;
            boolean normalProv = unit.getType() == 1 && (unit.getLevel() == 1 || unit.getLevel() == 2);

            if (directProv || normalProv) {
                fatherId = cityExamIdMap.get(unit.getUnitId());
                initStatusId = CHECK_DETAIL_CITY_0.id;
                if (fatherId == null) {
                    fatherId = provStatTask.getId();
                }
            } else {
                fatherId = provStatTask.getId();
                initStatusId = CHECK_DETAIL_REGION_0.id;
            }

            // 3-1  构建被查单位的 自查账单
            DeviceCheckDetail unitDetailDoc = DeviceCheckDetail.EmptyWithChecker(names + "|" + "默认备注", ccVO.getRemark(), 0, 0, 0, 0, unit.getName(),
                    // todo 后期续检查获取UnitI的是否可行
                    unitsRepo.findByName(unit.getName()).getUnitId(),
                    devInLib.getOrDefault(unit.getName(), new ArrayList<>()), devNotInLib.getOrDefault(unit.getName(), new ArrayList<>()));
            unitDetailDoc.setVar2(String.valueOf(initStatusId));
            DeviceCheckDetail detail = detailRepo.save(unitDetailDoc);
            // 将id放入统计中去 model ->  areaName -> detailId
            String areaName = auService.findOne(AuExample.UnitId, unit.getUnitId()).getName();
            for (CheckDeviceStatVo statVo : deviceStatVos) {
                for (CheckAreaStatVo asv : statVo.getAreaStatList()) {
                    if (asv.getAreaName().equals(areaName)) {
                        asv.setAreaStatId(statId);
                        asv.setAreaDetailId(detail.getId());
                    }
                }
            }

            // 3-2  构建被查单位的 自查任务 (根据被查单位的级别来区分是县级状态是市级状态) 这里的父级任务应该是省统计

            //处理ownUnit的代码 - 如果省直属有账号发给省直属的，没有账号就发给省做 用于处理没有账号的单位的可见性
            Integer ownUnitId = 0;
            List<User> userList = userService.findAllByUnite(unit.getUnitId());
            boolean notHasAccount = userList.size() == 0;

            if (unit.getType() == 2 && notHasAccount) {
                ownUnitId = 1;
            } else {
                ownUnitId = unit.getUnitId();
            }

            TaskBto checkedTask = new TaskBto(initStatusId, getUnitDateString(unit, ccVO.getTitle()), fatherId, addNode(provStatTask.getNodeIdDetail(), provStatTask.getId()), CONFIRM_CHECK_DETAIL.id, detail.getId(), ownUnitId, 0);
            checkedTask.setCustomInfo("manual");
            taskService.start(checkedTask);
        }


        return ResponseEntity.ok(ImmutableMap.of("msg", "发起核查成功"));
    }

    /**
     * 发起检查
     * <li>1. 添加发起核查bill记录</>
     * <li>2. 构建发起单位的统计账单与Task</>
     * <li>3. 构建被核查单位的详情账单与Task</li>
     *
     * @param ceVo 核查发起对象
     */
    @ApiOperation(value = "发起检查", notes = "手动发起核查，需要指定参数")
    @PostMapping("/startExam")
    public ResponseEntity<ResultObj> startExam(@RequestBody CheckExamVo ceVo) {
        //设置为
        ceVo.setEndTime(LocalDateTime.of(ceVo.getEndTime(), LocalTime.MAX).toLocalDate());
        //初始化数据结构
        List<Integer> detailIds = new ArrayList<>();
        Integer startUnitId = ceVo.getUnitId();
        Units startUnit = unitsRepo.findById(startUnitId).get();
        List<CheckExamDetailVo> examDetailVos = ceVo.getDetail();
        //如果是create检查,那么不需要添加自己单位的自查,如果是update检查，那么需要添加单位的自查
        if (ceVo.getExamStatId() != 0) {
            //update
            log.info("[核查模块] update核查,自动添加id = {}市的自核查任务", startUnitId);
            examDetailVos.add(examDetailVos.get(0).copy(startUnitId, "由省核查发起的自检查"));
        }

        List<String> tmpString = new ArrayList<>();
        // 拼接检查组和检查组成员
        for (CheckExamDetailVo vo : examDetailVos) {
            for (Integer u : vo.getUnitIds()) {
                tmpString.add(vo.getGroupName() + "," + vo.getUserNames().stream().collect(joining(",")));
            }
        }
        String groupUserString = tmpString.stream().collect(joining("|"));

        List<Integer> unitIds = examDetailVos.stream().flatMap(cv -> cv.getUnitIds().stream()).collect(toList());
        List<Units> checkedUnits = unitsRepo.findAllById(unitIds);
        List<String> checkedUnitNames = checkedUnits.stream().map(Units::getName).collect(toList());

        // 2-1 构建发起单位的 统计账单
        DeviceCheckStat provinceCheckStat;
        //根据examStatId来判断是update还是create 此时初始化的为指定检查区域的数据
        DeviceCheckStat initCheckStat = initStatData(checkedUnitNames, ceVo.getTitle(), groupUserString, 0, 0, startUnit.getName(), checkedUnits, ceVo.getEndTime().atStartOfDay());
        if (ceVo.getExamStatId() != 0) {
            DeviceCheckStat oriCheckStat = statRepo.findById(ceVo.getExamStatId()).get();
            oriCheckStat.setRemark(initCheckStat.getRemark());
            oriCheckStat.setStatInfo(initCheckStat.getStatInfo());
            provinceCheckStat = oriCheckStat;
        } else {
            provinceCheckStat = initCheckStat;
        }


        log.info("[核查模块]发起手动检查，发起单位为{},被查单位为{}", startUnit.getName(), checkedUnitNames);

        provinceCheckStat.setCheckType(CheckType.CT_EXAM);
        Integer statId = statRepo.save(provinceCheckStat).getId();
        List<CheckDeviceStatVo> deviceStatVos = Arrays.stream(
                Objects.requireNonNull(JacksonUtil.readValue(provinceCheckStat.getStatInfo(), CheckDeviceStatVo[].class)))
                .collect(toList());

        // 2-2 构建发起单位的 统计任务
        // 根据examStatId来判断要不要重新创建任务
        Integer currentUserId = authenticationUtils.getAuthentication().getCurrentUserInfo().getUserId();
        TaskBto cityStatTask;
        if (ceVo.getExamStatId() == 0) {
            cityStatTask = new Task(CHECK_EXAM_STAT_1.id, getUnitDateString(startUnit, ceVo.getTitle()), 0, ".0.", CONFIRM_CHECK_STAT.id, statId, startUnitId)
                    .parse2Bto();
//            cityStatTask.setRemark(String.valueOf(CHECK_EXAM_STAT_1.id));
            cityStatTask.setCustomInfo("exam");
            cityStatTask.getInvolveUserIdList().add(currentUserId);
            // 检查变为待办
            cityStatTask.getInvolveUserIdList().add(0);
            cityStatTask.setCurrentPoint(1);
            cityStatTask = taskService.start(cityStatTask);
        } else {
            // 将原来的待办改为跟踪
            cityStatTask = taskRepo.findByBillIdAndBusinessType(ceVo.getExamStatId(), CONFIRM_CHECK_STAT.id).get().parse2Bto();
            //检查变为待办
            cityStatTask.getInvolveUserIdList().add(0);
//            cityStatTask.setRemark(String.valueOf(CHECK_EXAM_STAT_1.id));
            cityStatTask.setCustomInfo("exam");
            cityStatTask.setCurrentPoint(cityStatTask.getCurrentPoint() + 1);
            cityStatTask.setCreateUserId(currentUserId);
            cityStatTask.setBillStatus(CHECK_EXAM_STAT_1.id);
            taskService.update(cityStatTask);
        }

        // 3 构建被查单位的 自查账单 与 自查任务
        // 获取所有在库装备 ls == 2 or ls == 14
        Map<String, List<DeviceLibrary>> devInLib = dcService.getAllDeviceLibraryList().stream()
                .filter(d -> d.getLifeStatus() == 2 || d.getLifeStatus() == 14)
//                .filter(device -> device.getOwnUnit().equals(device.getLocationUnit()))
                .collect(groupingBy(DeviceLibrary::getOwnUnit));

        // 非在库装备 ls !=2 and ls !=14
        Map<String, List<DeviceLibrary>> devNotInLib = dcService.getAllDeviceLibraryList().stream()
                .filter(d -> d.getLifeStatus() != 2 && d.getLifeStatus() != 14)
//                .filter(device -> !device.getOwnUnit().equals(device.getLocationUnit()))
                .collect(groupingBy(DeviceLibrary::getOwnUnit));

        // 3. 构建被核查单位的详情账单与Task
        // 对每个需要核查的单位构建其detail账单与task
        boolean isProvUser = authenticationUtils.getAuthentication()
                .getCurrentUserInfo().getUnitsId() == 1;

        for (CheckExamDetailVo ed : examDetailVos) {
            List<Integer> uid = ed.getUnitIds();
            for (Integer u : uid) {

                Units unit = unitsRepo.findById(u).get();
                String names = ed.getUserNames().stream().collect(joining(","));

                // 根据发起人决定初始状态id 省发起人- 160 其他发起人 被查的市160 被查的是区140
                int initTaskStatusId = 0;
                if (isProvUser) {
                    initTaskStatusId = CHECK_DETAIL_CITY_0.id;
                } else {
                    //再根据被查单位的级别 level = 3 区140 level =2市 160
                    if (unit.getLevel() == 3) {
                        initTaskStatusId = CHECK_DETAIL_REGION_0.id;
                    } else {
                        initTaskStatusId = CHECK_DETAIL_CITY_0.id;
                    }
                }
                ;

                // 3-1  构建被查单位的 自查账单
                DeviceCheckDetail unitDetailDoc = DeviceCheckDetail.EmptyWithChecker(names + "|" + ed.getRemark(), ceVo.getTitle() + "%^&" + ed.getRemark(), 0, 0, 0, 0, unit.getName(),
                        // todo 后期需要检查是否可以根据单位名称得到unitId，检查逻辑
                        unitsRepo.findByName(unit.getName()).getUnitId(),
                        devInLib.getOrDefault(unit.getName(), new ArrayList<>()), devNotInLib.getOrDefault(unit.getName(), new ArrayList<>()));
                unitDetailDoc.setVar2(String.valueOf(initTaskStatusId));
                DeviceCheckDetail detail = detailRepo.save(unitDetailDoc);
                detailIds.add(detail.getId());
                // 将id放入统计中去 model ->  areaName -> detailId
                String areaName = auService.findOne(AuExample.UnitId, unit.getUnitId()).getName();
                for (CheckDeviceStatVo statVo : deviceStatVos) {
                    for (CheckAreaStatVo asv : statVo.getAreaStatList()) {
                        if (asv.getAreaName().equals(areaName)) {
                            asv.setAreaStatId(statId);
                            asv.setAreaDetailId(detail.getId());
                        }
                    }
                }

                // 3-2   构建被查单位的 自查任务 (根据被查单位的级别来区分是县级状态是市级状态)

                TaskBto checkedTask = new TaskBto(initTaskStatusId, getUnitDateString(unit, ceVo.getTitle()), cityStatTask.getId(), addNode(cityStatTask.getNodeIdDetail(), cityStatTask.getId()), CONFIRM_CHECK_DETAIL.id, detail.getId(), unit.getUnitId(), 0);
//                checkedTask.setRemark(String.valueOf(CHECK_EXAM_DETAIL_0.id));
                checkedTask.setCustomInfo("manual");
                checkedTask = taskService.start(checkedTask);
                desBillMap.put(detail.getId(), ed.getRemark());
                desMap.put(checkedTask.getId(), ed.getRemark());
            }


        }

        // 4. 重新设置并保存统计账单
        provinceCheckStat.setStatInfo(JacksonUtil.toJSon(deviceStatVos));
        statRepo.save(provinceCheckStat);
        log.info("[核查模块] {}单位成功发起对 {} 单位的检查任务分发", startUnit.getName(), checkedUnitNames);
        return ResponseEntity.ok(new ResultObj<>(
                ImmutableMap.of("statIds", statId, "detailIds", detailIds),
                String.format("[核查模块] [%s]单位成功发起对 [%s] 单位的检查任务分发", startUnit.getName(), checkedUnitNames)
        ));
    }

    /**
     * 根据taskId查询Remark
     */
    @ApiOperation(value = "根据taskId查询Remark", notes = "根据taskId查询Remark")
    @GetMapping("/selectRemark/{taskId}")
    public ResponseEntity selectRemark(@PathVariable Integer taskId) {
        TaskBto taskBto = selectProvTask(taskId);
        Optional<DeviceCheckStat> optional = statRepo.findById(taskBto.getBillId());
        if (optional.isPresent()) {
            DeviceCheckStat deviceCheckStat = optional.get();
            if (deviceCheckStat.getCheckType().getId() == 0) {
                return ResponseEntity.ok(desMap.get(taskBto.getId()));
            } else {
                return ResponseEntity.ok(desMap.get(taskId));
            }
        } else {
            throw new ApiException(ResponseEntity.status(500).body("没有找到id"));
        }

    }

    /**
     * 根据taskId查询Remark
     */
    @ApiOperation(value = "根据BillId查询Remark", notes = "根据taskId查询Remark")
    @GetMapping("/selectRemarkBill/{billId}")
    public ResponseEntity selectRemarkBill(@PathVariable Integer billId) {

        return ResponseEntity.ok(desBillMap.get(billId));
    }

    /**
     * 对于专员A来说的逻辑
     * 1. 修改detailString
     * 2. 更新task状态
     * 3. 更新job状态(完成A的job,同时发起b的job)
     *
     * @param assignUserId 指定专管员B来接受这件事
     */
    @ApiOperation(value = "专管员A核查详情单")
    @PutMapping("/detail/A/{id}")
    public ResponseEntity<ResultObj> checkUserA(@PathVariable Integer id,
                                                @RequestParam int assignUserId,
                                                @RequestParam String checkResult,
                                                @RequestParam String terminalInfo,
                                                @RequestBody DetailVo detailVo) {
        // 获取参数
        DevLibVo devLibVo = detailVo.getDevLibVo();
        List<FileRet> checkFiles = detailVo.getCheckFiles();

        //是否上传检查单
        if (checkFiles != null && checkFiles.size() > 0) {
            DeviceCheckDetail deviceCheckDetail = detailRepo.findById(id).get();
            deviceCheckDetail.setCheckFiles(FilesUtil.stringFileToList(checkFiles));
            detailRepo.save(deviceCheckDetail);
        }

        //1. 更新checkDetail
        log.info("[核查模块] 专管员A正在进行详情账单核查,且指定下一个审核人B id 为 {}", assignUserId);
        String detailString = transUtil.devLib2String(devLibVo.getDevInLibrary(), devLibVo.getDevNotInLibrary());
        User currentUser = Objects.requireNonNull(authenticationUtils.getAuthentication()).getCurrentUserInfo();
        long count = devLibVo.getDevInLibrary().stream()
                .filter(deviceInLibVo -> deviceInLibVo.getProofResult() % 10 != 9)
                .count();
        detailRepo.updateCheckDetailWithT(id, detailString, checkResult, currentUser.getUserId(), assignUserId, (int) count, terminalInfo);
        //2. 推进TASK 状态
        TaskBto currentTask = taskService.get(id, CONFIRM_CHECK_DETAIL.id);
        currentTask.getInvolveUserIdList().set(0, authenticationUtils.getAuthentication().getCurrentUserInfo().getUserId());
        taskService.moveToNext(currentTask, assignUserId);
        log.info("[核查模块] A岗核查操作成功");

        //3.将父级以及父级的父级的状态更改为正在进行 找到父级的stat对象
        Integer fatherId = currentTask.getParentTaskId();
        Task cityTask = taskRepo.findById(fatherId).get();
        Integer cityStatId = cityTask.getBillId();

        //这里是为了修正特殊的直属单位改为从detail里拿单位(城市)名
//        String unitName = unitsRepo.findById(
//                detailRepo.findById(id).get().getCheckUnitId()).get().getName();

        AreaUnit areaUnit = auService.findOne(AuExample.UnitId, detailRepo.findById(id).get().getCheckUnitId());
        String cityName = areaUnit.getName();

        DeviceCheckStat cityStat = statRepo.findById(cityStatId).get();
        CheckStatVo cityVo = transUtil.checkStatDo2Vo(cityStat);
        //这里需要只对本城市的area开启start
        String finalCityName = cityName;
        cityVo.getDeviceStatVoList()
                .forEach(c -> c.getAreaStatList().forEach(v -> {
                    if (v.getAreaName().equals(finalCityName)) {
                        v.start();
                    }
                }));

        List<String> modelNames = devLibVo.getDevInLibrary().stream()
                .map(DeviceInLibVo::getModel)
                .collect(toList());

        statRepo.save(cityVo.toDo());

        //尝试寻找父级的父级
        Integer taskProvTaskId = cityTask.getParentTaskId();
        if (taskProvTaskId != 0) {
            if (areaUnit.getType() == 3) {
                //如果是县级城市，那么名字要替换成上级城市
                cityName = auService.findOne(AuExample.AreaId, areaUnit.getFatherId()).getName();
            }

            Integer provStatId = taskRepo.findById(taskProvTaskId).get().getBillId();
            DeviceCheckStat provStat = statRepo.findById(provStatId).get();
            CheckStatVo provVo = transUtil.checkStatDo2Vo(provStat);
            String finalName = cityName;
            provVo.getDeviceStatVoList()
                    .forEach(c -> {
                        if (modelNames.contains(c.getDeviceModel())) {
                            c.getAreaStatList().forEach(vo -> {
                                if (vo.getAreaName().equals(finalName)) {
                                    vo.start();
                                }
                            });
                        }
                    });
            statRepo.save(provVo.toDo());
            log.info("[核查模块] 检测到是自动核查任务，将省级统计 id = {} 认为开启", provStat.getId());
        }
        return ResponseEntity.ok(new ResultObj<>("专管员A操作成功"));
    }

    /**
     * 对于专管员B来说的逻辑
     */
    @ApiOperation(value = "专管员B核查详情单")
    @PutMapping("/detail/B/{id}")
    public ResponseEntity<ResultObj> checkUserB(@PathVariable Integer id,
                                                @RequestParam int checkStatus,
                                                @RequestParam(required = false, defaultValue = "0") int checkUserAId,
                                                @RequestParam(required = false, defaultValue = "0") int checkUserBId,
                                                @RequestParam(required = false, defaultValue = "0") int isUpdate,
                                                @RequestParam String checkResult,
                                                @RequestBody DetailVo detailVo
    ) {

        List<FileRet> checkFiles = detailVo.getCheckFiles();
        DevLibVo devLibVo = detailVo.getDevLibVo();
        log.info("[核查模块] 专管员B正在进行核查操作，核查账单id为 {} ,且审核状态为 : {}", id, checkStatus == 1 ? "通过" : "不通过");
        if (checkStatus == 0) {
            return ResponseEntity.status(400).body(new ResultObj<>("checkStatus不应该为0!"));
        }

        //先更新checkUser
        if (checkUserAId > 0 && checkUserBId > 0) {
            detailRepo.updateCheckUser(id, checkUserAId, checkUserBId);
        }

        // 审核通过与不通过的逻辑不同
        TaskBto currentTask = taskService.get(id, CONFIRM_CHECK_DETAIL.id);
        boolean CEDStep1 = currentTask.getBillStatus().equals(CHECK_DETAIL_REGION_1.id) || currentTask.getBillStatus().equals(CHECK_DETAIL_CITY_1.id);
        if (!CEDStep1) {
            return ResponseEntity.status(400).body(new ResultObj<>(String.format("当前任务的状态异常！当前任务状态为 %s , 任务状态应该为 %s", GlobalMap.getStatusEnumMap().get(currentTask.getBillStatus()), CHECK_DETAIL_REGION_1.name)));
        }

        DeviceCheckDetail currentDetail = detailRepo.findById(id).get();
        if (checkStatus == 1) {
            // 只要通过了，且更新了详情结果，那么这里就进行一次更新
            if (isUpdate == 1) {
                String detailString = transUtil.devLib2String(devLibVo.getDevInLibrary(), devLibVo.getDevNotInLibrary());
                User currentUser = Objects.requireNonNull(authenticationUtils.getAuthentication()).getCurrentUserInfo();
                long count = devLibVo.getDevInLibrary().stream()
                        .filter(deviceInLibVo -> deviceInLibVo.getProofResult() % 10 != 9)
                        .count();
                log.info("[核查模块] 检测到专管员B人工核查修改了核查结果，实查装备数量为 : {}", count);
                detailRepo.updateCheckDetail4Check(id, detailString, checkResult, (int) count);
            }

            //依据detail账单对应的checkUserId来判断是2流程还是多流程的
            Integer userAId = currentDetail.getCheckUserAId();
            Integer userBId = currentDetail.getCheckUserBId();

            // 如果是4流程的，则需要指定核查组成员A接任务
            detailRepo.updateCheckStatus(id, checkStatus);
            //是否上传检查单
            if (checkFiles != null && checkFiles.size() > 0) {
                DeviceCheckDetail deviceCheckDetail = detailRepo.findById(id).get();
                deviceCheckDetail.setCheckFiles(FilesUtil.stringFileToList(checkFiles));
                detailRepo.save(deviceCheckDetail);
            }
            if (userAId > 0 && userBId > 0) {
                log.info("[核查模块] 该详情任务是一个4流程任务，推送到C状态...");
                taskService.moveToNext(currentTask, userAId);
            } else {
                // 如果是2流程的，则直接结束该任务
                log.info("[核查模块] 详情任务推进到下个阶段...");
                taskService.moveToNext(currentTask);
                summaryDetail(currentTask, currentDetail);
                // 检查该exam任务是否可以完结推进了
                advanceExamTask(currentTask.getParentTaskId(), true);
                // 如果是省本级的自查任务，则自动触发省审核通过
                if (unitsService.isProvUnit(currentTask.getOwnUnit())) {
                    provAudit(currentDetail.getId(), true);
                }

            }
        } else {
            //不通过则回到第一阶段
            log.info("[核查模块] 核查员B退回自查任务...");
            StatusEnum firstStatus = getFirstStatus(currentTask.getBillStatus(), Integer.valueOf(currentDetail.getVar2()));
            currentDetail.setCheckFiles("");
            currentDetail.setCheckFileList(Lists.newArrayList());
            detailRepo.save(currentDetail);
            taskService.moveToSpecial(currentTask, firstStatus, 0);
        }
        log.info("[核查模块] 专管员B操作成功");
        //完结系统自查业务
        findBySystem();
        return ResponseEntity.ok(new ResultObj<>("专管B操作成功"));
    }

    /**
     * 市人员审核区自查数据的接口
     *
     * @param detailId
     * @param pass     是否通过 true - 通过 ，false - 未通过
     * @return
     */
    @PutMapping("/city/audit/{id}")
    @ApiOperation(value = "市专管人员审核区自查数据的接口")
    public ResponseEntity cityAudit(
            @PathVariable(name = "id") Integer detailId,
            @RequestParam boolean pass) {

        TaskBto currentTask = taskService.get(detailId, CONFIRM_CHECK_DETAIL.id);
        DeviceCheckDetail currentDetail = detailRepo.findById(detailId).get();

        if (pass) {
            // 通过的话就推进任务进度，合并数据
            log.info("[核查模块] 市专管员审核通过，结束 {} 区的自查任务并汇总数据", unitsRepo.findById(currentDetail.getCheckUnitId()).get().getName());
            taskService.moveToNext(currentTask);
            Integer userId = authenticationUtils.getAuthentication().getCurrentUserInfo().getUserId();
            currentDetail.setUserCId(userId);
            currentDetail = detailRepo.save(currentDetail);
            summaryDetail(currentTask, currentDetail);
//            // 检查该exam任务是否可以完结推进了
            advanceExamTask(currentTask.getParentTaskId(), false);

            return ResponseEntity.ok("市专管员审核通过，结束" + unitsRepo.findById(currentDetail.getCheckUnitId()).get().getName() + " 区的自查任务并汇总数据");
        } else {
            log.info("[核查模块] 市专管员审核未通过，回滚该任务到初始状态，任务id = {}", currentTask.getId());
            // 重置Task任务本身的任务状态，使其回滚到等待专管员A处理时的状态,并在Task里添加特殊的回滚标记，用来鉴别这是一个回滚任务
            StatusEnum firstStatus = getFirstStatus(currentTask.getBillStatus(), Integer.valueOf(currentDetail.getVar2()));
            currentTask.setRemark("ROLLBACK-0");
            taskService.moveToSpecial(currentTask, firstStatus, 0);
            //重置该自查详情里的各个装备的自查详情
            currentDetail = setDetailCheckNumber(currentDetail, 119);
            currentDetail.setCheckFiles("");
            currentDetail.setCheckFileList(Lists.newArrayList());
            Integer userId = authenticationUtils.getAuthentication().getCurrentUserInfo().getUserId();
            currentDetail.setUserCId(0);
            currentDetail = detailRepo.save(currentDetail);
            //该detail对应的stat数据里相应的数据剔除(写一个通用的剔除方法), 否则在第二次审核通过的时候会重复计算一次数据
            TaskBto fatherTask = taskService.get(currentTask.getParentTaskId());
            DeviceCheckStat dcs = statRepo.findById(fatherTask.getBillId()).get();
            removeDetailFromDcs(currentDetail, dcs);

            //撤销时恢复市统计任务;
            if(END.id.equals(fatherTask.getBillStatus())) {
                fatherTask.setBillStatus(StatusEnum.CHECK_EXAM_STAT_1.id);
                taskService.update(fatherTask);
            }

            return ResponseEntity.ok("回退成功!回滚该任务到初始状态,id = " + currentTask.getId());
        }

    }

    /**
     * 省专管员审核自查数据的方法
     *
     * @param detailId 自查主键id
     * @param pass     是否通过 true - 通过 ，false - 未通过
     * @return
     */
    @PutMapping("/prov/audit/{id}")
    @ApiOperation(value = "省专管人员审核区自查数据的接口")
    public ResponseEntity provAudit(
            @PathVariable(name = "id") Integer detailId,
            @RequestParam boolean pass) {

        if (pass) {
            log.info("[核查模块] 省专管员审查detailId = {} 的自查数据-审核通过", detailId);
            return provAuditPass(detailId);
        } else {
            log.info("[核查模块] 省专管员审查detailId = {} 的自查数据-审核未通过", detailId);
            return provAuitNotPassed(detailId);
        }
    }

    @ApiOperation(value = "查询还有哪些单位的自查没有完成，给出单位名称")
    @GetMapping("/stat/shutdown/{statId}")
    public ResponseEntity queryNotFinish(@PathVariable Integer statId){
        Task checkTask = taskRepo.findByBillIdAndBusinessType(statId, CONFIRM_CHECK_STAT.id).get();

        List<Task> l1Childs = taskRepo.findAllByParentTaskId(checkTask.getId());
        Set<String> unitNames = new HashSet<>();

        for (Task c1 : l1Childs) {
            if (predNodeType(c1).equals(CcNodeType.EXAM)) {
                List<Task> detailTaskList = taskRepo.findAllByParentTaskId(c1.getId());
                if(detailTaskList.isEmpty()) {
                    unitNames.add(unitsRepo.findById(c1.getOwnUnit()).get().getName());
                } else {
                    List<String> notEndUnitNames = detailTaskList
                            .stream().filter(task -> !task.getBillStatus().equals(END.id))
                            .map(task -> unitsRepo.findById(task.getOwnUnit()).get().getName())
                            .collect(toList());
                    unitNames.addAll(notEndUnitNames);
                }
            }else if (predNodeType(c1).equals(CcNodeType.MANUAL)&& !c1.getBillStatus().equals(END.id)){
                String unitName = unitsRepo.findById(c1.getOwnUnit()).get().getName();
                unitNames.add(unitName);
            }
        }

        return ResponseEntity.ok(unitNames);
    }

    @ApiOperation(value = "终结stat任务,多指核查任务")
    @PutMapping("/stat/shutdown/{statId}")
    public ResponseEntity shutDown(@PathVariable Integer statId) {
        log.info("[核查模块] 终止任务 - 正在终止statId = {} 的统计任务", statId);
        Task checkTask = taskRepo.findByBillIdAndBusinessType(statId, CONFIRM_CHECK_STAT.id).get();
        // 将该stat任务置为20000,即终止
        unionLink(7, statId);
        taskService.moveToSpecial(checkTask.parse2Bto(), CHECK_SHUT_DOWN);
        supplyLogMsg(checkTask);

        // 检验所有还没有end的节点
        // 1级节点分为两类 自查类节点&统计数据确认节点 以及 检查类节点
        List<Task> l1Childs = taskRepo.findAllByParentTaskId(checkTask.getId());
        for (Task c1 : l1Childs) {
            if (predNodeType(c1).equals(CcNodeType.EXAM)) {
                // c1里的检查节点也shutDown掉 并保存掉
                unionLink(7, c1.getBillId());//save
                taskService.moveToSpecial(c1.parse2Bto(), CHECK_SHUT_DOWN);
                supplyLogMsg(c1);
                List<Task> c2Childs = taskRepo.findAllByParentTaskId(c1.getId());
                c2Childs.forEach(c2 -> shutDownNode(c2));
            } else {
                shutDownNode(c1);
            }
        }

        return ResponseEntity.ok(String.format("任务节点task id = %d 的任务已经成功关闭", checkTask.getId()));
    }

    /**
     * @param taskId 待办任务id
     * @param statId 市级统计的STAT ID
     * @return
     */
    @ApiOperation(value = "跟踪任务待办办结确认")
    @PostMapping("/stat/done")
    public ResponseEntity confirmDone(@RequestParam int taskId, @RequestParam int statId) {
        log.info("[核查模块] 正在进行办结任务确认，任务id为 : {}", taskId);
        TaskBto cityDoneTask = taskService.get(taskId);

        //市的task 且statId保持一致  省待办 需要的是 市的统计taskId
        Specification<Task> pred = Specifications.<Task>and()
                .eq("billId", statId)
                .eq("businessType", CONFIRM_CHECK_STAT.id)
                .build();

        // 找到child任务 -> 市级任务 如果是2级的则直接找自己
        List<Task> tasks = taskRepo.findAll(pred);
        Task cityTask = tasks.stream()
                .filter(task -> task.getParentTaskId() != 0 && task.getParentTaskId() != null)
                .findFirst()
                .orElse(tasks.get(0));

        // 如果没有父统计TASK 则证明是两级
        boolean doubleLevel = cityTask.getParentTaskId() == 0 || cityTask.getParentTaskId() == null;
        if (doubleLevel) {
            log.info("[核查模块] 办结确认两级检查任务");
        } else {
            log.info("[核查模块] 办结确认三级检查任务");
            //1.累加数据 累加市级数据数据到省级
            CheckStatVo cityStat = transUtil.checkStatDo2Vo(statRepo.findById(statId).get());
            Integer provStatId = taskService.get(cityTask.getParentTaskId()).getBillId();
            CheckStatVo provinceStat = transUtil.checkStatDo2Vo(statRepo.findById(provStatId).get());
            String cityName = areaCache.findById(unitsRepo.findAreaId(cityTask.getOwnUnit())).getName();
            // 将区级信息合并到市中
            List<CheckDeviceStatVo> mergedVo = cityStat.getDeviceStatVoList().stream()
                    .map(vo -> vo.combine(cityName, cityStat.getId()))
                    .collect(toList());
            // 如果是第一个市，则替换，否则累加
            boolean firstCity = taskService.TaskTreeIsStart(cityTask.getParentTaskId(), true);
            if (firstCity) {
                provinceStat.setDeviceStatVoList(mergedVo);
                log.info("[核查模块] 检测到该任务的合并状态为第一个市统计任务，因此直接替换省级统计数据");
            } else {
                provinceStat.cleanReduce(mergedVo);
                log.info("[核查模块] 数据累加成功");
            }
            DeviceCheckStat dcs = provinceStat.toDo();
            statRepo.save(dcs);
        }

        //2.办结待办
        taskService.moveToEnd(cityDoneTask);

        //3.如果所有省级的子任务都完成了
        boolean over = taskService.TaskTreeIsOver(cityTask.getParentTaskId());
        if (over) {
            log.info("[核查模块] 所有的省级子任务都完成了，将省统计任务变为待办");
            TaskBto provTask = taskService.get(cityTask.getParentTaskId());
            provTask.getInvolveUserIdList().add(0);
            provTask.setCurrentPoint(provTask.getCurrentPoint() + 1);
            taskService.update(provTask);
        }

        log.info("[核查模块] 办结统计待办操作成功");
        return ResponseEntity.ok(new ResultObj<>("办结统计待办确认完毕"));
    }

    /**
     * @param statId 统计账单主键id
     */
    @ApiOperation(value = "统计数据确认")
    @PostMapping("/stat/verify")
    public ResponseEntity<ResultObj> statConfirm(@RequestParam int statId) {
        log.info("[核查模块] 正在进行统计数据确认，统计账单id为 : {}", statId);
        //将当前的统计task办结
        TaskBto currentTask = taskService.get(statId, CONFIRM_CHECK_STAT.id);
        currentTask = taskService.moveToEnd(currentTask);
        Integer parentTaskId = currentTask.getParentTaskId();
        boolean hasParent = parentTaskId != 0;

        //尝试寻找老的市办结任务，如果有的话就删除加END
        //5.需要将上一次市的确认任务给清理掉 统计确认任务关联的是statId 找到billId -> exam Task ->
        // 父级的父级，就是省的任务，然后子child里名字包含 统计数据确认任务 的任务给 END 吧
        Map<Integer, List<Task>> doneTaskList = taskRepo.findAllByParentTaskId(parentTaskId)
                .stream()
                .filter(task -> task.getTitle().contains("统计数据确认任务"))
                .collect(groupingBy(Task::getBillId));

        List<Task> existsDone = doneTaskList.get(statId);

        if (Objects.nonNull(existsDone)) {
            for (Task d : existsDone) {
                log.info("[核查模块] 发现了id = {}市的重复的统计确认任务,将其完结并剔除整个树节点", d.getId());
                d.setParentTaskId(0);
                taskService.moveToEnd(d.parse2Bto());
            }
        }

        log.info("[核查模块] 统计数据确认操作成功");
        return ResponseEntity.ok(new ResultObj<>("统计数据确认完毕"));
    }

    /**
     * 获得指定单位的在库装备与非在库装备
     * lifeStatus  10 = 已销毁 11 = 丢失
     *
     * @param checkUnit  单位名
     * @param allDevices 装备列表
     * @return true -> 在库装备,false -> 非在库装备
     * @see DeviceLibrary#lifeStatus
     */
    @NotNull
    private Map<Boolean, List<DeviceLibrary>> getDevLibMap(String checkUnit, List<DeviceLibrary> allDevices) {
        // 在库装备 = 所在在本单位 (过滤掉生命状态为10(已销毁)的装备)
        // 非在库装备 = 所属是本单位，但是所在不在本单位里 + 原本是在库装备但是生命状态是11（丢失）的装备
        Map<Boolean, List<DeviceLibrary>> locationMap = allDevices.stream()
                .collect(partitioningBy(d -> d.getLocationUnit().equals(checkUnit)));

        List<DeviceLibrary> devInLib = locationMap.get(true);

        // filter ls == 11 join to 非在库
        List<DeviceLibrary> ls11Lib = devInLib.stream().filter(d -> d.getLifeStatus() == 11).collect(toList());

        // filter ls == 10
        devInLib = devInLib.stream()
                .filter(d -> d.getLifeStatus() != 10 && d.getLifeStatus() != 11)
                .collect(toList());

        // 非在库 = not A and in B
        List<DeviceLibrary> devNotInLib = locationMap.get(false)
                .stream().filter(d -> d.getOwnUnit().equals(checkUnit))
                .collect(toList());

        devNotInLib.addAll(ls11Lib);

        Map<Boolean, List<DeviceLibrary>> devLib = new HashMap<>();
        devLib.put(true, devInLib);
        devLib.put(false, devNotInLib);
        return devLib;
    }

    @NotNull
    private LinkExamDetail getLed(LocalDateTime endTime, LocalDateTime updateTime, Task child) {
        Integer childBusType = child.getBusinessType();
        Integer childBusId = child.getBillId();

        DeviceCheckDetail childDetail = detailRepo.findById(childBusId).get();
        String unitName = unitsRepo.findById(childDetail.getCheckUnitId()).get().getName();

        List<CheckAreaStatVo> casList = parseStatString2Vo(child.parse2Bto(), childDetail).stream()
                .map(CheckDeviceStatVo::getAreaStatList)
                .flatMap(checkAreaStatVos -> checkAreaStatVos.stream())
                .collect(toList());
        //自查的areaName要从detail里找
        String areaName = unitsRepo.findById(childDetail.getCheckUnitId()).get().getName();
        CheckAreaStatVo cas;

        if (casList.isEmpty()) {
            cas = new CheckAreaStatVo("默认地区", 0, 0, 0, 0, 0, 0);
        } else {
            cas = casList.get(0);
        }

        LinkExamDetail led = cas2led(cas, child, endTime, updateTime);


        led.setCheckUnit(unitName);
        return led;
    }

    /**
     * lcd 里的列表都为检查，因此是集合对
     *
     * @return
     */
    private LinkCheckDetail cas2lcd(List<CheckAreaStatVo> casList, Task task, List<Task> detailTasks, String finalCityName) {
        LinkCheckDetail lcd = new LinkCheckDetail();
        lcd.setId(task.getBillId());
        String finalSituation = "";

        // 核查结果的判断具体细节可以查看 docs/confirmCheck.md文档
        List<String> situationList = detailTasks.stream()
                .map(this::getDetailSituation)
                .collect(toList());

        // 如果 size = 0 或全都是 无 ，则无
        if (situationList.size() == 0) {
            finalSituation = "无";
        } else {
            boolean allIsNothing = situationList.stream().allMatch(s -> s.equals("无"));
            if (allIsNothing) {
                finalSituation = "无";
            }

            //如果 有任意一个区状态是待审核
            boolean anyIsAudit = situationList.stream().anyMatch(s -> s.equals("等待市审核"));
            if (anyIsAudit) {
                finalSituation = "等待市审核";
            }

            //如果 全都是 完成，那么是完成
            boolean allIsFinish = situationList.stream().allMatch(s -> s.equals("完成"));
            if (allIsFinish) {
                finalSituation = "完成";
            }

            // 其他情况，则是进行中
            if (StringUtils.isEmpty(finalSituation)) {
                finalSituation = "进行中";
            }
        }

        lcd.setCheckSituation(finalSituation);

        // 核查情况  所有子任务都是初始状态 = 无 10=未
        String checkReuslt = "";
        // Task 先将历史数据给过滤掉 - 方法是按照billId分组排序,跳出主键id最大的即最新的一条
        detailTasks = detailTasks.stream()
                .collect(groupingBy(Task::getTitle,
                        collectingAndThen(maxBy(Comparator.comparing(Task::getId)), Optional::get)))
                .values().stream()
                .collect(toList());

        // 不能存在有9999的任务
        boolean notExistsEnd = detailTasks.stream()
                .allMatch(t -> !t.getBillStatus().equals(END.id));

        if (notExistsEnd) {
            checkReuslt = "无";
        } else {
            // 任意一个是10则是待审核
            boolean waitAudit = casList.stream()
                    .anyMatch(cas -> cas.getComSituation() == 10);

            boolean notPassed = casList.stream()
                    .anyMatch(cas -> cas.getComSituation() == 13);

            // 需要每一个城市都有无误(即12) 才算无误
            Map<String, List<CheckAreaStatVo>> map = casList.stream()
                    .collect(groupingBy(CheckAreaStatVo::getAreaName));

            long okCount = 0;

            for (List<CheckAreaStatVo> vos : map.values()) {
                boolean containsOk = vos.stream()
                        .anyMatch(checkAreaStatVo -> checkAreaStatVo.getComSituation() == 12);
                if (containsOk) {
                    okCount++;
                }
            }

            boolean allOk = okCount >= map.size();

            if (waitAudit) {
                checkReuslt = CHECK_RESULT_WAIT;
            } else if (allOk) {
                checkReuslt = CHECK_RESULT_DONE;
            } else if (notPassed) {
                checkReuslt = "未通过";
            } else {
                checkReuslt = "进行中";
            }
        }
        lcd.setCheckResult(checkReuslt);

        lcd.setCheckUnit(finalCityName + "局");

        return lcd;

    }

    private TaskBto selectProvTask(Integer taskId) {
        TaskBto taskBto = taskService.get(taskId);
        if (taskBto.getParentTaskId() == 0) {
            return taskBto;
        }
        return selectProvTask(taskBto.getParentTaskId());
    }

    /**
     * 获得自查的核查情况
     */
    private String getDetailSituation(Task task) {
        Integer taskStatus = task.getBillStatus();
        String situation = CheckChart.taskStatus2Situation.get(taskStatus);
        // 无的情况要分两种 一种是初始的无 一种是重做导致的无 通过判断task的remark
        String remark = task.getRemark();
        if (situation.equals("无")) {
            if (Objects.nonNull(remark) && remark.contains("ROLLBACK")) {
                situation = "无";
            }
        }

        return situation;
    }

    /**
     * led 里的列表都为自查，因此都是单个对象
     *
     * @return
     */
    private LinkExamDetail cas2led(CheckAreaStatVo cas, Task task, LocalDateTime endTime, LocalDateTime updateTime) {
        LinkExamDetail led = new LinkExamDetail();
        led.setId(task.getBillId());
        int comProgress = cas.getComProgress();
        int comSituation = cas.getComSituation();

        // 核查情况依照 新的与task status来对应 详情可以查看docs/confirmcheck.md里的对应表 对照表

        String situation = getDetailSituation(task);
        // 逾期的处理，只在完成的那一步才判断
        if (situation.equals("完成") && updateTime.isAfter(endTime)) {
            situation = "逾期完成";
        }
        led.setCheckSituation(situation);

        String checkResult = "";
        String remark = task.getRemark();
        // 核查结果 - 如果省的自查任务，核查情况是完成了之后，核查结果就是无误
        String unitName = unitsRepo.findById(detailRepo.findById(task.getBillId()).get().getCheckUnitId()).get().getName();
        Integer areaType = auService.findOne(AuExample.UnitId, detailRepo.findById(task.getBillId()).get().getCheckUnitId()).getType();

        if (!situation.contains("完成")) {
            checkResult = "无";
        } else if (comSituation == 10) {
            // 再判断一下 是2级结构(检查统计)还是3级结构(核查统计)
            Integer rootId = taskService.findByTaskId(task.getParentTaskId()).getParentTaskId();
            boolean isTwoLevel = rootId == null || rootId == 0;
            // 2级结构 - 检查统计 发起人单位是省，结果为等待省审核，发起人是市则是无
            if (isTwoLevel) {
                Integer startUnitId = userService.findById(task.getCreateUserId()).getUnitsId();
                if (startUnitId != 1) {
                    checkResult = "无";
                } else {
                    //根据detail userC为省且任务状态是完结状态时 变为无误 否则是等待省审核
                    Integer detailId = task.getBillId();
                    Integer userCId = detailRepo.findById(detailId).get().getUserCId();
                    if (task.getBillStatus().equals(END.id) && userIsProv(userCId)) {
                        checkResult = CHECK_RESULT_DONE;
                    } else {
                        checkResult = CHECK_RESULT_WAIT;
                    }
                }
            } else {
                checkResult = CHECK_RESULT_WAIT;
            }
        } else if (comSituation == 12) {
            checkResult = CHECK_RESULT_DONE;
        } else if (comSituation == 13) {
            int redoTime = 1;
            if (Objects.nonNull(remark) && remark.contains("ROLLBACK")) {
                redoTime = Integer.valueOf(remark.split("-")[1]);
            }
            checkResult = redoTime + "次未通过";
        } else if (comSituation == 0) {
            if (task.getBillStatus().equals(END.id)) {
                int redoTime = 1;
                if (Objects.nonNull(remark) && remark.contains("ROLLBACK")) {
                    redoTime = Integer.valueOf(remark.split("-")[1]);
                }
                checkResult = redoTime + "次未通过";
            } else {
                checkResult = CHECK_RESULT_WAIT;
            }
        } else {
            checkResult = "状态异常";
        }
        led.setCheckResult(checkResult);

        led.setCheckUnit(cas.getAreaName() + "局");

        return led;
    }

    /**
     * 判断用户是否是省级用户
     *
     * @return
     */
    private boolean userIsProv(Integer userId) {
        if (Objects.isNull(userId) || userId == 0) {
            return false;
        }
        Integer unitsId = userService.findById(userId).getUnitsId();
        Integer level = unitsRepo.findById(unitsId).get().getLevel();
        return level == 1;
    }

    // todo
    private String getUnitDateString(Units units, String title) {
        return "[" + units.getUnitDesc() + "]" + title;

    }

    /**
     * 尝试自动完结检查任务(市/省的)
     * 1. 当且仅当该检查任务的状态等于@link{CHECK_EXAM_STAT_1}时 并且该检查是三级结构里的核查(即父Id !=0 )且其下的所有子任务均为完结状态，自动推进
     *
     * @param exam的taskId
     */
    private void advanceExamTask(Integer examTaskId, boolean exec) {
        TaskBto parentTask = taskService.findByTaskId(examTaskId);
        if (exec) {
            exec = parentTask.getParentTaskId() != 0;
        } else {
            exec = true;
        }

        //  && parentTask.getParentTaskId() != 0
        if (parentTask.getBillStatus().equals(CHECK_EXAM_STAT_1.id) && exec && taskService.TaskTreeIsOver(examTaskId)) {
            log.info("[核查模块] 检测到task id = {}的检查任务已经可以自动推进了，自动推进至下一步", examTaskId);
            statConfirm(parentTask.getBillId());
        }
    }

    /**
     * 关闭某个节点，该节点下必须没有子节点树了
     * <p>1.将该任务进度推进至9999</>
     * <p>2.手动补充终结日志</>
     * <p>3.将终结信息添加阅知列表里面</>
     *
     * @param node
     */
    private void shutDownNode(Task node) {
        log.info("[核查模块] 终止任务-正在关闭 task id = {} 的任务节点");
        // 如果是核查类捡点，抛出异常
        if (node.getCustomInfo().equals("check") || node.getCustomInfo().equals("exam")) {
            throw new ApiException("关闭节点里不处理核查/核查节点...,你提供的任务节点id = " + node.getId());
        }
        //  其余节点,执行关闭策略
        // 1.将该任务进度推进至9999
        TaskBto nodeBto = node.parse2Bto();
        taskService.moveToEnd(nodeBto);
//        supplyLogMsg(node);
        log.info("[核查模块] 终止任务-task id = {} 成功关闭");
    }

    /**
     * 补充任务的终结日志与阅知信息
     *
     * @param node
     */
    private void supplyLogMsg(Task node) {
        // 手动补充终结日志
        String adminUser = authenticationUtils.getAuthentication().getCurrentUserInfo().getName();
        String shutDownMsg = String.format("专管员[%s]终止了该任务", adminUser);
        TaskLogBto taskLog = new TaskLogBto(node.getId(), shutDownMsg);
        taskLogService.addLog(taskLog);
        // 添加阅知信息
        List<Integer> involveUserIds = node.parse2Bto().getInvolveUserIdList();
        int lastUserId = involveUserIds.get(involveUserIds.size() - 1) == null ? 0 : involveUserIds.get(involveUserIds.size() - 1);
        List<Integer> msgUserIds = new ArrayList<>();
        if (lastUserId == 0) {
            msgUserIds = userService.findByUniteId(node.getOwnUnit()).stream()
                    .map(UserShenRe::getUserId)
                    .collect(toList());
        } else {
            msgUserIds = new ArrayList<>();
            msgUserIds.add(lastUserId);
        }
        MessageBto msgBto = new MessageBto(node.getId(), node.getBusinessType(), shutDownMsg, msgUserIds);
        messageService.add(msgBto);
    }

    /**
     * 将detail里的detailString里的检查数字该为指定数值
     *
     * @param detail
     * @param number
     */
    private DeviceCheckDetail setDetailCheckNumber(DeviceCheckDetail detail, Integer number) {
        String detailString = detail.getCheckDetail();
        String updatedString = Arrays.stream(detailString.split(","))
                .map(s -> s.split("-")[0] + "-" + number)
                .collect(joining(","));

        detail.setCheckDetail(updatedString);
        return detail;
    }

    /**
     * 省对区审查 未通过的 逻辑
     *
     * @param id detail Id
     * @return
     */
    private ResponseEntity provAuitNotPassed(Integer id) {
        //1. bill单的自查将百位数变为3
        DeviceCheckDetail detail = detailRepo.findById(id).get();
        String detailString = detail.getCheckDetail();
        String initalDetail = Arrays.stream(detailString.split(","))
                .map(s -> s.split("-")[0] + "-" + 119)
                .collect(joining(","));

        String updateDetail = changeHunds(detailString, 3);

        detailRepo.updateCheckDetail(id, updateDetail, "", 0, 0, 0);

        //2. 当前任务结束，开启一个新的 退回任务(连带一个新的detail)
        TaskBto currentTask = taskService.get(id, CONFIRM_CHECK_DETAIL.id);
        String remark = currentTask.getRemark();
        if (Objects.nonNull(remark) && remark.contains("ROLLBACK")) {
            Integer times = Integer.valueOf(remark.split("-")[1]);
            remark = "ROLLBACK-" + ++times;
        } else {
            remark = "ROLLBACK-1";
        }

        currentTask.setRemark(remark);
        TaskBto newTask = currentTask.toDo().copy().parse2Bto();
        currentTask.setCustomInfo(currentTask.getCustomInfo());
        taskService.moveToEnd(currentTask);

        // 补充退回日志
        String adminUser = authenticationUtils.getAuthentication().getCurrentUserInfo().getName();
        String shutDownMsg = String.format("专管员[%s]退回了该任务", adminUser);
        TaskLogBto taskLog = new TaskLogBto(currentTask.getId(), shutDownMsg);
        taskLogService.addLog(taskLog);

        // 创建新的detail 新的detail会在B的时候加入，所以老的stat要去除掉
        DeviceCheckDetail cDetail = detail.copyWithoutId();
        cDetail.setId(null);
        cDetail.setCheckDetail(initalDetail);
        cDetail.setCheckFiles("");
        cDetail.setCheckFileList(Lists.newArrayList());
        cDetail.setReview(false);
        cDetail = detailRepo.save(cDetail);
        // 创建新的任务(被拒绝的自查单位如果是区则是140状态，否则是160状态 )
        String unitName = unitsRepo.findById(cDetail.getCheckUnitId()).get().getName();
        Integer level = unitsRepo.findByName(unitName).getLevel();
        Integer initStatusId = 0;
        if (level == 3) {
            initStatusId = CHECK_DETAIL_REGION_0.id;
        } else {
            initStatusId = CHECK_DETAIL_CITY_0.id;
        }

        newTask.setBillStatus(initStatusId);
        newTask.setBillId(cDetail.getId());
        newTask.setId(0);
        newTask.setCustomInfo(newTask.getCustomInfo());
        newTask.getInvolveUserIdList().add(0);
        newTask.setCurrentPoint(newTask.getInvolveUserIdList().size() - 1);

        // 尝试从老任务中获取重做次数  - 新任务老任务都需要更新remark
        newTask.setRemark(remark);

        log.info("[核查模块] 省回退操作中... 任务remark = {}", remark);
        newTask.setRemark(remark);

        taskService.start(newTask);

        //3. 在stat的remark中追加信息 - 找到currentTask在其中的顺位
        TaskBto fatherTask = taskService.get(currentTask.getParentTaskId());
        List<Task> childTask = taskRepo.findAllByParentTaskId(fatherTask.getId());
        int pos = 0;
        for (int i = 0; i < childTask.size(); i++) {
            if (childTask.get(i).getId().equals(currentTask.getId())) {
                log.info("[核查模块] 记录remark的pos位置 = {}", i);
            }
        }

        DeviceCheckStat dcs = statRepo.findById(fatherTask.getBillId()).get();
        String[] groups = dcs.getRemark().split("\\|");
        String pading = groups[pos];
        dcs.setRemark(dcs.getRemark() + "|" + pading);
        log.info("[核查模块] 补充remark = {}", pading);

        // 老的stat要去除掉对应areaName的数据
        removeDetailFromDcs(detail, dcs);

        //4.父级任务变为进行中 如果父级是核查，变成111，如果父级是检查，变成131
        if (fatherTask.getCustomInfo().contains("exam")) {
            fatherTask.setBillStatus(CHECK_EXAM_STAT_1.id);
            //  父级的父级也要设置 exam的父级是check
            if (fatherTask.getParentTaskId() != 0) {
                Task checkTask = taskRepo.findById(fatherTask.getParentTaskId())
                        .orElseThrow(() -> new ApiException(String.format("没有找到核查任务,子Task id %d", fatherTask.getId())));
                log.info("[核查模块] 回滚核查任务 taskId = {}", checkTask.getId());
                checkTask.setBillStatus(CHECK_STAT_1.id);
                taskRepo.save(checkTask);
            }
        } else if (fatherTask.getCustomInfo().contains("check")) {
            fatherTask.setBillStatus(CHECK_STAT_1.id);
        }
        taskRepo.save(fatherTask.toDo());

        return ResponseEntity.ok(new ResultObj<>("回退操作成功!"));

    }

    /**
     * 省对区审查通过的逻辑
     *
     * @param id detail Id
     * @return
     */
    private ResponseEntity provAuditPass(Integer id) {
        //将自查的情况的百位数全部替换成2
        DeviceCheckDetail detail = detailRepo.findById(id).get();
        String updatedString = changeHunds(detail.getCheckDetail(), 2);
        detail.setCheckDetail(updatedString);
        detail.setUserCId(authenticationUtils.getAuthentication().getCurrentUserInfo().getUserId());
        detail.setReview(true);
        log.info("[核查模块] 审核通过 - 更新后的detailString形如 {}", updatedString.split(",")[0]);
        detailRepo.save(detail);

        //将对应stat中地区的comProgress 改为 2 comsitution 改为12
        String areaName = auService.findOne(AuExample.UnitId, detail.getCheckUnitId()).getName();
        TaskBto currentTask = taskService.get(id, CONFIRM_CHECK_DETAIL.id);
        TaskBto fatherTask = taskService.get(currentTask.getParentTaskId());
        DeviceCheckStat dcs = statRepo.findById(fatherTask.getBillId()).get();
        CheckStatVo csv = transUtil.checkStatDo2Vo(dcs);
        for (CheckDeviceStatVo vo : csv.getDeviceStatVoList()) {
            for (CheckAreaStatVo av : vo.getAreaStatList()) {
                if (av.getAreaName().equals(areaName)) {
                    log.info("[核查模块] 审核通过 - 地区 = {} 的统计数据在统计信息中被成功修改了", areaName);
                    av.auditPassed();
                }
            }
        }
        // 检验整个节点树是否所有节点都是审核通过，如果是，则自动完结任务
        TaskBto topNode = taskService.getTopNode(currentTask.getId());
        if (allDetailIsReview(topNode)) {
            log.info("[核查模块] 该节点树的时候自查节点都已经完结，完结整个节点树，触发的子节点id = {},顶节点id = {}", currentTask.getId(), topNode.getId());
            shutDown(topNode.getBillId());
        }

        return ResponseEntity.ok(new ResultObj<>("审核通过!"));
    }

    /**
     * 查询该节点所在的节点树的所有detail是否都已经被查阅
     *
     * @param topTask
     * @return
     */
    private boolean allDetailIsReview(TaskBto topTask) {
        // 获取顶节点
        List<Task> c1Tasks = taskRepo.findAllByParentTaskId(topTask.getId());
        // 获取detailList 根据detailList的checkUnit分组，取id最大的一条，每个组里必须为true才能触发
        List<Integer> detailIdList = new ArrayList<>();
        // 所有的检查任务是否都完结了
        boolean examIsDone = true;
        boolean detailIsDone = true;

        for (Task c1 : c1Tasks) {
            if (c1.getCustomInfo().equals("manual")) {
                detailIdList.add(c1.getBillId());
            }

            if (c1.getCustomInfo().equals("exam")) {
                if (!(c1.getBillStatus().equals(END.id) || c1.getBillStatus().equals(CHECK_SHUT_DOWN.id))) {
                    examIsDone = false;
                }

                Collection<Integer> regionDetailIds = taskRepo.findAllByParentTaskId(c1.getId()).stream()
                        .filter(t -> t.getCustomInfo().equals("manual"))
                        .collect(groupingBy(Task::getOwnUnit,
                                collectingAndThen(maxBy(Comparator.comparing(Task::getId)), task -> task.get().getBillId())))
                        .values();
                detailIdList.addAll(regionDetailIds);
            }
        }

        detailIsDone = detailRepo.findAllById(detailIdList).stream()
                .allMatch(DeviceCheckDetail::isReview);
        return detailIsDone && examIsDone;
    }

    private void removeDetailFromDcs(DeviceCheckDetail detail, DeviceCheckStat dcs) {
        String areaName = auService.findOne(AuExample.UnitId, detail.getCheckUnitId()).getName();
        CheckStatVo csv = transUtil.checkStatDo2Vo(dcs);
        for (CheckDeviceStatVo vo : csv.getDeviceStatVoList()) {
            List<CheckAreaStatVo> filterList = vo.getAreaStatList().stream()
                    .filter(casv -> !casv.getAreaName().equals(areaName)).collect(toList());
            if (filterList.size() != vo.getAreaStatList().size()) {
                log.info("[核查模块] 回退操作-将退回的数据从统计中去除了.");
                vo.setAreaStatList(filterList);
            }
        }

        statRepo.save(csv.toDo());
    }

    /**
     * 将detailString里的百位数改为指定数字
     *
     * @param detailString 要更改的自查详情字符串
     * @param value        想要改成的数字
     * @return
     */
    private String changeHunds(String detailString, Integer value) {
        String updateDetail = Arrays.stream(detailString.split(","))
                .filter(s -> StringUtils.isNoneEmpty(s))
                .map(s -> {
                    Integer number = Integer.valueOf(s.split("-")[1]);
                    int digit = number % 10;
                    int tens = number / 10 % 10;
                    number = value * 100 + tens * 10 + digit;
                    return s.split("-")[0] + "-" + number;
                })
                .collect(joining(","));
        return updateDetail;
    }

    /**
     * 将统计数据中指定城市的统计数据重置
     *
     * @param statId   stat主键id
     * @param cityName 要重置的城市名称
     */
    private void resetStatByCity(Integer statId, String cityName) {
        DeviceCheckStat stat = statRepo.findById(statId).get();
        CheckStatVo statVo = transUtil.checkStatDo2Vo(stat);
        for (CheckDeviceStatVo vo : statVo.getDeviceStatVoList()) {
            for (CheckAreaStatVo cas : vo.getAreaStatList()) {
                if (cas.getAreaName().equals(cityName)) {
                    cas.reset();
                }
            }
        }

        statRepo.save(statVo.toDo());
    }

    /**
     * 用来判断该节点类型
     *
     * @param task 任务节点
     * @return {@link com.tykj.dev.device.confirmcheck.common.CcNodeType}
     */
    private CcNodeType predNodeType(Task task) {
        // bussiness type = 7
        Integer type = task.getBusinessType();
        if (type.equals(CONFIRM_CHECK_STAT.id)) {
            // 核查 检查 统计数据确认
            if (task.getCustomInfo().equals("check")) {
                return CcNodeType.CHECK;
            }

            if (task.getCustomInfo().equals("exam")) {
                return CcNodeType.EXAM;
            }

            if (task.getTitle().contains("统计数据确认任务")) {
                return CcNodeType.CONFIRM;
            }
        } else if (type.equals(CONFIRM_CHECK_DETAIL.id)) {
            // 自查任务
            return CcNodeType.MANUAL;
        } else {
            // 其他任务
            return CcNodeType.NONE;
        }

        return CcNodeType.NONE;

    }

    /**
     * 任务结束后需要将当前城市的统计信息汇总上
     *
     * @param currentTask   当前任务
     * @param currentDetail 需要汇总的详情核查数据对象
     */
    private void summaryDetail(TaskBto currentTask, DeviceCheckDetail currentDetail) {
        log.info("[核查模块] 正在尝试进行Task id = {} 的自查数据汇总...", currentTask.getId());
        if (!currentTask.getBillStatus().equals(END.id)) {
            log.info("[核查模块] 该自查任务还未完成,结束汇总,当前任务状态id = {}", currentTask.getBillStatus());
        }

        // 先找到汇总地区的账单id 查询当前detail task 的 父级TASK
        Integer fatherTaskId = currentTask.getParentTaskId();
        int statId = taskRepo.findBillId(fatherTaskId, CONFIRM_CHECK_STAT.id);

        // 如果是第一个区域(通过判断所有的child节点没有结束)，替换掉父级的统计，如果不是第一个区域，则累加
        boolean firstArea = taskService.TaskTreeIsStart(fatherTaskId, false);
        List<CheckDeviceStatVo> addVos = parseStatString2Vo(currentTask, currentDetail);
        CheckStatVo cityStatVo = transUtil.checkStatDo2Vo(statRepo.findById(statId).get());
        //addVos在内部reduce一次
        addVos = addVos.stream()
                .collect(groupingBy(v -> v.getDeviceModel() + v.getDeviceName()))
                .values()
                .stream()
                .map(v -> v.stream().reduce(CheckDeviceStatVo::reduce).get())
                .collect(toList());

        //直接替换
        cityStatVo = cityStatVo.cleanReduce(addVos);

        // 把cityStatVo里本地区的进度更改为2
        String unitName = findUnitName(currentDetail);
        Integer areaId = unitsRepo.findByName(unitName).getAreaId();
        String areaName = areaRepo.findById(areaId).orElse(new Area(0, "省直属", 9999, "9999", 0, ""))
                .getName();

        // 完结任务，设置detailId
        for (CheckDeviceStatVo v : cityStatVo.getDeviceStatVoList()) {
            for (CheckAreaStatVo v1 : v.getAreaStatList()) {
                if (v1.getAreaName().equals(areaName)) {
                    v1.setAreaDetailId(currentDetail.getId());
                    v1.end();
                }
            }
        }
        // 第一个区域替换,否则累加
//        if (firstArea) {
//            cityStatVo.setDeviceStatVoList(addVos);
//        } else {
//            // 获得当前城市的统计信息 以及 要汇总的地区信息 并累加保存
//            cityStatVo = cityStatVo.cleanReduce(addVos);
//        }
        statRepo.save(cityStatVo.toDo());

        // 判断地区数据是否均汇总完毕
        boolean over = taskService.TaskTreeIsOver(fatherTaskId);

        // 如果汇总完毕则将父级的统计任务推进
//        if (over) {
//            log.info("[核查模块] 父节点 = {} 的所有子任务均已经完结，推动父节点的任务", fatherTaskId);
//            TaskBto fatherTask = taskService.get(fatherTaskId);
//            //如果上一个id是-1 则证明是所有人的跟踪统计，即自动核查，那么下一步推进到所有人的跟踪,否则设置为-2,即维持跟踪者id
//            //Integer lastUserId = fatherTask.getLastUserId() == -1 ? -1 : -2;
//            //TaskBto newFatherTask = taskService.moveToNext(fatherTask, lastUserId);
//            // 将父级的统计任务变为待办
//            fatherTask.getInvolveUserIdList().add(0);
//            fatherTask.setCurrentPoint(fatherTask.getCurrentPoint() + 1);
//            taskService.update(fatherTask);
//        }
        log.info("[核查模块] 数据汇总完毕");
    }

    /**
     * 获取指定任务的第一步的任务状态
     *
     * @param currentStatusId 任务状态id
     */
    private StatusEnum getFirstStatus(Integer currentStatusId, Integer defaultStatusId) {
        int remainder = currentStatusId % 10;
        StatusEnum resultEnum = GlobalMap.getStatusEnumMap().get(currentStatusId - remainder);
        if (Objects.isNull(resultEnum)) {
            return GlobalMap.getStatusEnumMap().get(defaultStatusId);
        } else {
            return resultEnum;
        }
    }

    public List<CheckDeviceStatVo> parseStatString2Vo(TaskBto task, DeviceCheckDetail detail) {
        String statString = detail.getCheckDetail();
        List<CheckDeviceStatVo> statVoList = new ArrayList<>();
        //分为 id - status 的数组 其中status 0缺失 1无误 2新增 3不在库
        String[] statArray = statString.split(",");
        //将 id - status 转化为 model - count - status(只统计新增和无误的作为数量)


        long start1 = System.currentTimeMillis();
        // 根这里unid可以从detail里拿,根据unitId 查到 areaId 根据 areaId 查询到 areaName
        String areaName = findUnitName(detail);
        // detailId与statId只需要查询一次
        int detailId = Optional.ofNullable(taskRepo.findBillIdByTaskId(task.getId())).orElse(0);

        int statId = Optional.ofNullable(taskRepo.findBillId(task.getParentTaskId(), CONFIRM_CHECK_STAT.id))
                .orElse(0);

        long end1 = System.currentTimeMillis();
        log.info("[核查TEST] 基础数据查询消耗 {} ms ", end1 - start1);


        // checkDevice 批量查询做一个MAP缓存

        long start = System.currentTimeMillis();
        List<Integer> idList = Arrays.stream(statArray)
                .filter(StringUtils::isNotEmpty)
                .map(s -> s.split("-")[0])
                .filter(StringUtils::isNotEmpty)
                .map(Integer::parseInt)
                .collect(toList());


        List<DeviceLibrary> allDevice = dcService.getAllDeviceLibraryList();
        Map<Integer, DeviceLibrary> deviceMap = allDevice.stream()
                .filter(d -> idList.contains(d.getId()))
                .collect(toMap(DeviceLibrary::getId, Function.identity()));
        long end = System.currentTimeMillis();

        log.info("[核查TEST] 批量查询id集合耗时 {} ms ", end - start);

        long start2 = System.currentTimeMillis();
        for (String s : statArray) {
            if (StringUtils.isEmpty(s)) {
                continue;
            }
            String[] device = s.split("-");
            if (device.length < 2 || StringUtils.isEmpty(device[0])) {
                continue;
            }
            int deviceId = Integer.parseInt(device[0]);
            int proofResult = Integer.parseInt(device[1]);

            DeviceLibrary checkDevice = deviceMap.get(deviceId);
            if (Objects.isNull(checkDevice)) {
                continue;
            }

            CheckAreaStatVo checkAreaStatVo;
            // 百位数 0/1 待审核 2 无误 3未通过
            // 十位数 1 人工 2 自动
            // 0缺失1无b误2新增3不在库 8已退回 9未检查
            int digits = proofResult % 10;
            int tens = proofResult / 10 % 10;
            int huns = proofResult / 100 % 10;

            // 个位数判断自查结果
            if (digits == 9) {
                checkAreaStatVo = new CheckAreaStatVo(areaName, 1, 1, 0, 10, statId, detailId);
            } else if (digits == 3) {
                //跳过非在库的统计
                continue;
            } else if (digits == 1) {
                checkAreaStatVo = new CheckAreaStatVo(areaName, 1, 1, 2, 10, statId, detailId);
            } else if (digits == 0) {
                checkAreaStatVo = new CheckAreaStatVo(areaName, 0, 1, 2, 10, statId, detailId);
            } else {
                checkAreaStatVo = new CheckAreaStatVo(areaName, 1, 1, 2, 10, statId, detailId);
            }

            // 百位数判断审核情况 0,1未检查 2是无误 3是未通过
            if (huns == 0 || huns == 1) {
                checkAreaStatVo.setComSituation(10);
            } else if (huns == 2) {
                checkAreaStatVo.setComSituation(12);
            } else if (huns == 3) {
                checkAreaStatVo.setComSituation(13);
            }

            List<CheckAreaStatVo> areaStatVoList = new ArrayList<>();
            areaStatVoList.add(checkAreaStatVo);

            statVoList.add(new CheckDeviceStatVo(checkDevice.getModel(), checkDevice.getName(), 1, areaStatVoList));
        }
        long end2 = System.currentTimeMillis();
        log.info("[核查TEST] 拼装deviceStat数据消耗了 {} ms ", end2 - start2);

        return statVoList;
    }


    /**
     * @param initUnitNames 初始化指定单位的数据
     * @return
     */
    private DeviceCheckStat initStatData(List<String> initUnitNames, String title, String remark, Integer checkAId, Integer checkBId, String startUnitName, List<Units> unitsList, LocalDateTime endTime) {
        //获得要被统计的单位名列表
        List<String> unitNameList = unitsList.stream()
                .map(Units::getName)
                .collect(toList());

        List<AreaUnit> auList = unitsList.stream()
                .map(units -> auService.findOne(AuExample.UnitId, units.getUnitId()))
                .collect(toList());

        List<CheckDeviceStatVo> cdv = dcService.getAllDeviceLibraryList()
                .stream()
                .filter(dev -> unitNameList.contains(dev.getOwnUnit()))
                .filter(dev -> initUnitNames.contains(dev.getOwnUnit()))
//                .filter(d->d.getLifeStatus() != 2 & d.getLifeStatus() != 14)
                .map(transUtil::device2InitStatVo)
                .collect(toList());

        Map<String, List<CheckDeviceStatVo>> map = cdv.stream()
                .collect(groupingBy(d -> d.getDeviceModel() + d.getDeviceName()));


        Collection<CheckDeviceStatVo> statVos = cdv.stream()
                .collect(toMap(d -> d.getDeviceModel() + d.getDeviceName(), Function.identity(), CheckDeviceStatVo::reduce))
                .values();

        return new DeviceCheckStat(
                CheckType.CT_EXAM,
                title,
                startUnitName + "待核查装备统计单",
                JacksonUtil.toJSon(new ArrayList<>(statVos)),
                checkAId,
                checkBId,
                remark,
                endTime);
    }


    /**
     * 构建初始化核查统计数据
     * 依据不同地区装备的在库情况构造出舒适化的统计数据出来
     *
     * @param title         标题
     * @param remark        备注
     * @param checkAId      核查组成员A id
     * @param checkBId      核查组成员B id
     * @param startUnitName 发起核查的单位名
     * @param unitsList     被核查单位列表
     * @return 一份初始化好统计数据(没有向上合并 ， 只做了同级地区数据合并)的核查统计单
     */
    private DeviceCheckStat initStatData(String title, String remark, Integer checkAId, Integer checkBId, String startUnitName, List<Units> unitsList, LocalDateTime endTime) {
        //获得要被统计的单位名列表
        List<String> unitNameList = unitsList.stream()
                .map(Units::getName)
                .collect(toList());

        List<AreaUnit> auList = unitsList.stream()
                .map(units -> auService.findOne(AuExample.UnitId, units.getUnitId()))
                .collect(toList());

        Collection<CheckDeviceStatVo> statVos;
        List<CheckDeviceStatVo> cds = dcService.getAllDeviceLibraryList()
                .stream()
                .filter(dev -> unitNameList.contains(dev.getOwnUnit()))
//                .filter(d->d.getLifeStatus() != 2 & d.getLifeStatus() != 14)
                .map(transUtil::device2InitStatVo)
                .collect(toList());

        statVos = cds.stream()
                .collect(toMap(d -> d.getDeviceModel() + d.getDeviceName(), Function.identity(), CheckDeviceStatVo::reduce))
                .values();

        //缺省地区数据补充 下面这段代码意义不明 - - 先注释掉 ，后面想起来了再决定是用还是删除
//        for (CheckDeviceStatVo statVo : statVos) {
//            if (statVo.getAreaStatList().size() < unitNameList.size()) {
//                List<String> existsAreaNames = statVo.getAreaStatList().stream()
//                        .map(CheckAreaStatVo::getAreaName)
//                        .collect(toList());
//                List<CheckAreaStatVo> supplementVos = auList.stream()
//                        .filter(au -> !existsAreaNames.contains(au.getName()))
//                        .map(au -> new CheckAreaStatVo(au.getName(), 0, 0, 0, 0, 0, 0))
//                        .collect(toList());
//                statVo.getAreaStatList().addAll(supplementVos);
//            }
//        }

        return new DeviceCheckStat(
                CheckType.CT_EXAM,
                title,
                startUnitName + "待核查装备统计单",
                JacksonUtil.toJSon(new ArrayList<>(statVos)),
                checkAId,
                checkBId,
                remark,
                endTime);
    }

    private String addNode(String originalNode, Integer fatherId) {
        return originalNode + fatherId + ".";
    }

    /**
     * 合并CheckAreaStatVo对象
     *
     * @param casList
     * @param finalCityName 最终合并用的城市名
     * @return
     */
    public CheckAreaStatVo combineCaList(List<CheckAreaStatVo> casList, String finalCityName) {

        int supposeCount = 0;
        int actualCount = 0;
        int progressCount = 0;
        int comSituationCount = 0;

        final int finalProgress;
        final int finalSituation;

        for (CheckAreaStatVo v : casList) {
            supposeCount += v.getSupposeCount();
            actualCount += v.getActualCount();
            progressCount += v.getComProgress();
            comSituationCount += v.getComSituation();
        }

        for (CheckAreaStatVo v : casList) {
            if (v.getComSituation() == -1) {
                comSituationCount = -1;
            }
        }


        //comprogress合并逻辑 if all 0->0 2->2 3->3 else 1

        boolean isDone = casList.stream()
                .mapToInt(CheckAreaStatVo::getComProgress)
                .allMatch(value -> value == 2);

        boolean isRollbakck = casList.stream()
                .mapToInt(CheckAreaStatVo::getComProgress)
                .anyMatch(value -> value == 3);

        if (progressCount == 0) {
            finalProgress = 0;
        } else if (isDone) {
            finalProgress = 2;
        } else if (isRollbakck) {
            finalProgress = 3;
        } else {
            finalProgress = 1;
        }

        // comsitution 合并逻辑 0->0 , 1->1 , 0,1->1
        if (comSituationCount == -1) {
            finalSituation = -1;
        } else if (comSituationCount == 0) {
            finalSituation = 0;
        } else {
            finalSituation = 1;
        }

        return new CheckAreaStatVo(finalCityName, actualCount, supposeCount, finalProgress, finalSituation, 0, 0);
    }

    private void findBySystem() {
        selfCheckController.findBySystem1();
    }

    /**
     * find unit's name by detail's unitId
     * @param deviceCheckDetail detail obj
     * @return unit's name
     */
    private String findUnitName(DeviceCheckDetail deviceCheckDetail) {
        return unitsRepo.findById(deviceCheckDetail.getCheckUnitId()).get().getName();
    }
}




