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

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.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.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.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.subject.domin.DeviceLibrary;
import com.tykj.dev.device.task.repository.TaskDao;
import com.tykj.dev.device.task.service.TaskService;
import com.tykj.dev.device.task.subject.bto.TaskBto;
import com.tykj.dev.device.task.subject.domin.Task;
import com.tykj.dev.device.user.base.enums.AuExample;
import com.tykj.dev.device.user.cache.AreaCache;
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.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.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.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDate;
import java.time.LocalDateTime;
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 {

    Map<Integer,String> desMap=new HashMap<>();

    Map<Integer,String> desBillMap=new HashMap<>();

    @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 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 MyWebSocket myWebSocket;

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

    /**
     * @return level = 0 ,1 ,2 的单位
     */
    @GetMapping("/unit")
    @ApiOperation(value = "查询默认的可以被核查单位的清单(省本直,省直属,市局)")
    public ResponseEntity findDefaultUnits() {

        return ResponseEntity.ok(new ResultObj<>(unitsRepo.findAllByTypeNotInAndLevelIn(Lists.newArrayList(3),Lists.newArrayList(0,1,2))));
    }

    @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 = "根据id查询核查详情数据", 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 = "统一跳转接口", notes = "可以通过这个接口进行跳转")
    @GetMapping("/link")
    public ResponseEntity unionLink(@RequestParam Integer type, @RequestParam Integer billId) {

        LinkVo linkVo = new LinkVo();
        //type = 7 统计
        if (type.equals(CONFIRM_CHECK_STAT.id)) {
            DeviceCheckStat ct = statRepo.findById(billId).get();
            CheckStatVo ctVo = transUtil.checkStatDo2Vo(ct);
            linkVo.setTitle(ctVo.getTitle());
            LocalDateTime endTime = ctVo.getEndTime();

            linkVo.setEndTime(ctVo.getEndTime().toLocalDate());
            List<LinkCheckDetail> lcdList = new ArrayList<>();
            List<LinkExamDetail> ledList = new ArrayList<>();

            // check type = 0 核查页面 看见的是市检查与省直属自查
            if (ctVo.getCheckType() == 0) {
                linkVo.setType(1);
                // 根据tpye和billId找到父级节点
                Task rootTask = taskRepo.findAllByBillIdAndBusinessType(billId, type).get(0);
                // 找到所有的子节点
                List<Task> childTask = taskRepo.findAllByParentTaskId(rootTask.getId());

                long startTime = System.currentTimeMillis();
                long finalTime = System.currentTimeMillis();
                for (Task child : childTask) {
                    Integer childBusType = child.getBusinessType();
                    Integer childBusId = child.getBillId();
                    // 处理子节点，子节点有两种类型 - 检查类统计与自查类数据
                    if (childBusType.equals(CONFIRM_CHECK_STAT.id)) {
                        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 并求和
                        List<Task> cctask = taskRepo.findAllByParentTaskId(child.getId());

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

                        for (Task cct : cctask) {
                            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);
                        }

                        String areaName = auService.findOne(AuExample.UnitId, child.getOwnUnit()).getName();
                        CheckAreaStatVo cas = combineCaList(totalList, areaName);

                        LinkCheckDetail lcd = rev2lcd(child, endTime, cas.reverse());
                        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 {
                        startTime = System.currentTimeMillis();

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

                        List<CheckDeviceStatVo> list = parseStatString2Vo(child.parse2Bto(), childDetail);
                        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);
                        }

//                        CheckAreaStatVo cas = parseStatString2Vo(child.parse2Bto(), childDetail).stream()
//                                .map(CheckDeviceStatVo::getAreaStatList)
//                                .flatMap(checkAreaStatVos -> checkAreaStatVos.stream())
//                                .reduce(CheckAreaStatVo::combine)
//                                .orElse(new CheckAreaStatVo("默认地区", 0, 0, 0, 0, 0, 0));

                        LinkExamDetail led = rev2led(child, endTime, cas.reverse());
                        led.setCheckUnit(unitName);
                        ledList.add(led);

                        finalTime = System.currentTimeMillis();
                        log.info("[TEST] CHILD BILL ID = {} 的自查任务COST {} MS", child.getBillId(), finalTime - startTime);
                    }
                }
                linkVo.setLcDetail(lcdList);
                linkVo.setLeDetail(ledList);
                finalTime = System.currentTimeMillis();
                log.info("[TEST] COST {} MS", finalTime - startTime);
            }
            // check type = 1 检查页面 看见的是自查
            if (ctVo.getCheckType() == 1) {
                linkVo.setType(2);
                //看到的都是自查 根据tpye和billId找到父级节点
                Task rootTask = taskRepo.findAllByBillIdAndBusinessType(billId, type).get(0);
                // 找到所有的子节点
                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) {

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

                    DeviceCheckDetail childDetail = detailRepo.findById(childBusId).get();
                    String unitName = childDetail.getCheckUnit();

                    List<CheckAreaStatVo> casList = parseStatString2Vo(child.parse2Bto(), childDetail).stream()
                            .map(CheckDeviceStatVo::getAreaStatList)
                            .flatMap(checkAreaStatVos -> checkAreaStatVos.stream())
                            .collect(toList());
                    //自查的areaName要从detail里找
                    String areaName = childDetail.getCheckUnit();
                    CheckAreaStatVo cas;
                    if (casList.isEmpty()) {
                        cas = new CheckAreaStatVo("默认地区", 0, 0, 0, 0, 0, 0);
                    } else {
                        cas = combineCaList(casList, areaName);
                    }

//
//                    CheckAreaStatVo cas = parseStatString2Vo(child.parse2Bto(), childDetail).stream()
//                            .map(CheckDeviceStatVo::getAreaStatList)
//                            .flatMap(checkAreaStatVos -> checkAreaStatVos.stream())
//                            .reduce(CheckAreaStatVo::combine)
//                            .get();

                    LinkExamDetail led = rev2led(child, endTime, cas.reverse());
                    led.setCheckUnit(unitName);

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

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

            }
        }

        // type = 8 跳转
        if (type.equals(CONFIRM_CHECK_DETAIL.id)) {
            linkVo.setType(3);
            linkVo.setDetailId(billId);
        }

        return ResponseEntity.ok(linkVo);

    }

    private LinkCheckDetail rev2lcd(Task task, LocalDateTime endTime, RevAreaStat revAreaStat) {
        LinkCheckDetail lcd = new LinkCheckDetail();
        lcd.setId(task.getBillId());
        //核查情况 无误/有误/逾期
        if (endTime.isBefore(LocalDateTime.now()) && task.getBillStatus() != 9999 && revAreaStat.getComProgress() != 2) {
            lcd.setCheckSituation("逾期");
        } else if (revAreaStat.getComSituation() == 1) {
            lcd.setCheckSituation("有误");
        } else if (revAreaStat.getComSituation() == 0) {
            if (revAreaStat.getComProgress() == 0){
                lcd.setCheckSituation("无");
            }else {
                lcd.setCheckSituation("无误");
            }
        } else {
            lcd.setCheckSituation("异常");
        }
        //核查结果
        if (task.getBillStatus() == 9999) {
            lcd.setCheckResult("完成");
        } else if (revAreaStat.getComProgress() == 0) {
            lcd.setCheckResult("未开始");
        } else if (revAreaStat.getComProgress() == 1) {
            lcd.setCheckResult("进行中");
        } else if (revAreaStat.getComProgress() == 2 && task.getBillStatus() % 10 == 0) {
            lcd.setCheckResult("进行中");
        } else if (revAreaStat.getComProgress() == 2 && task.getBillStatus() % 10 == 1) {
            lcd.setCheckResult("进行中");
        } else if (revAreaStat.getComProgress() == 2) {
            lcd.setCheckResult("完成");
        }

        //核查单位名
        lcd.setCheckUnit(revAreaStat.getAreaName() + "局");

        //备注判断 新增缺失
        lcd.setRemark(revAreaStat.getActualCount() - revAreaStat.getSupposeCount());

        return lcd;
    }

    private LinkExamDetail rev2led(Task task, LocalDateTime endTime, RevAreaStat revAreaStat) {
        LinkExamDetail led = new LinkExamDetail();
        led.setId(task.getBillId());
        //核查情况 无误/有误/逾期
        if (endTime.isBefore(LocalDateTime.now()) && task.getBillStatus() != 9999 && revAreaStat.getComProgress() != 2) {
            led.setCheckSituation("逾期");
        } else if (revAreaStat.getComSituation() == 1) {
            led.setCheckSituation("有误");
        } else if (revAreaStat.getComSituation() == 0) {
            if (revAreaStat.getComProgress() == 0){
                led.setCheckSituation("无");
            }else {
                led.setCheckSituation("无误");
            }
        } else {
            led.setCheckSituation("异常");
        }
        //核查结果 还需要检查任务的当前情况，以防止AB岗中途访问数据导致进度异常

        if (task.getBillStatus() == 9999) {
            led.setCheckResult("完成");
        } else if (revAreaStat.getComProgress() == 0) {
            led.setCheckResult("未开始");
        } else if (revAreaStat.getComProgress() == 1) {
            led.setCheckResult("进行中");
        } else if (revAreaStat.getComProgress() == 2 && task.getBillStatus() % 10 == 0) {
            led.setCheckResult("进行中");
        } else if (revAreaStat.getComProgress() == 2 && task.getBillStatus() % 10 == 1) {
            led.setCheckResult("进行中");
        } else if (revAreaStat.getComProgress() == 2) {
            led.setCheckResult("完成");
        }

        //核查单位名
        led.setCheckUnit(revAreaStat.getAreaName() + "局");

        //备注判断 新增缺失
        led.setRemark(revAreaStat.getActualCount() - revAreaStat.getSupposeCount());


        return led;
    }

    /**
     * @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);
    }

    @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.getOne(statId);
        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 = "检查地区是否发起核查", notes = "检查地区是否发起核查")
    @PostMapping("/checkPossible")
    public ResponseEntity checkPossible(@RequestBody UnitIds uuid){
        List<String> unitNames = uuid.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.发起自己的自查 (市,tpy2=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_EXAM_STAT_0.id, "省" + CONFIRM_CHECK_STAT.name, 0, ".0.", CONFIRM_CHECK_STAT.id, statId, startUnitId)
                .parse2Bto();
        provStatTask.getInvolveUserIdList().add(authenticationUtils.getAuthentication().getCurrentUserInfo().getUserId());
        provStatTask.getInvolveUserIdList().add(-1);
        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, unit.getName() + "检查统计任务", 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 = 1,2) tpye = 2
        List<Units> selfCheckUnits = checkedUnits.stream()
                .filter(units -> Arrays.asList(0, 1).contains(units.getLevel()) || units.getType() == 2)
                .collect(toList());

        // 获取所有在库装备与不在库装备
        Map<String, List<DeviceLibrary>> devInLib = deviceRepo.findAll().stream()
                .filter(device -> device.getOwnUnit().equals(device.getLocationUnit()))
                .collect(groupingBy(DeviceLibrary::getOwnUnit));

        Map<String, List<DeviceLibrary>> devNotInLib = deviceRepo.findAll().stream()
                .filter(device -> !device.getOwnUnit().equals(device.getLocationUnit()))
                .collect(groupingBy(DeviceLibrary::getOwnUnit));


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

        for (Units unit : selfCheckUnits) {
            //核查组成员名称用当前登录用户
            String names = loginUser.getName();
            // 3-1  构建被查单位的 自查账单
            DeviceCheckDetail unitDetailDoc = DeviceCheckDetail.EmptyWithChecker(names + "|" + "默认备注", ccVO.getRemark(), 0, 0, 0, 0, unit.getName(), devInLib.getOrDefault(unit.getName(), new ArrayList<>()), devNotInLib.getOrDefault(unit.getName(), new ArrayList<>()));
            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  构建被查单位的 自查任务 (根据被查单位的级别来区分是县级状态是市级状态) 这里的父级任务应该是省统计
            // 如果是市level=2 的话,father Id为市的检查id
            Integer fatherId = 0;
            if (unit.getLevel() == 2) {
                fatherId = cityExamIdMap.get(unit.getUnitId());
            } else {
                fatherId = provStatTask.getId();
            }

            //处理ownUnit的代码 - 用于处理没有账号的单位的可见性
            Integer ownUnitId = 0;
            if (unit.getType() == 2) {
                ownUnitId = 1;
            } else {
                ownUnitId = unit.getUnitId();
            }

            TaskBto checkedTask = new TaskBto(CHECK_EXAM_DETAIL_0.id, unit.getName() + "自核查任务", 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) {
        //初始化数据结构
        List<Integer> detailIds = new ArrayList<>();
        Integer startUnitId = ceVo.getUnitId();
        Units startUnit = unitsRepo.findById(startUnitId).get();
        List<CheckExamDetailVo> examDetailVos = ceVo.getDetail();
        Integer uAreaId = auService.findOne(AuExample.UnitId, startUnitId).getId();
        //如果是create检查,那么不需要添加自己单位的自查,如果是update检查，那么需要添加单位的自查
        if (ceVo.getExamStatId() != 0) {
            //update
            log.info("[核查模块] update核查,自动添加id = {}市的自核查任务", startUnitId);
            examDetailVos.add(examDetailVos.get(0).copy(uAreaId, "由省核查发起的自检查"));
        }


        List<String> tmpString = new ArrayList<>();
        for (CheckExamDetailVo vo : examDetailVos) {
            tmpString.add(vo.getGroupName() + "," + vo.getUserNames().stream().collect(joining(",")));
        }
        String groupUserString = tmpString.stream().collect(joining("|"));

        List<Integer> areaIds = examDetailVos.stream().map(CheckExamDetailVo::getAreaId).collect(toList());
        List<Units> checkedUnits = unitsRepo.findByAreaIdIn(areaIds);
        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_0.id, CHECK_EXAM_STAT_0.name, 0, ".0.", CONFIRM_CHECK_STAT.id, statId, startUnitId)
                    .parse2Bto();
            cityStatTask.setCustomInfo("exam");
            cityStatTask.getInvolveUserIdList().add(currentUserId);
            cityStatTask.getInvolveUserIdList().add(-1);
            cityStatTask.setCurrentPoint(1);
            cityStatTask = taskService.start(cityStatTask);
        } else {
            // 将原来的待办改为跟踪
            cityStatTask = taskRepo.findByBillIdAndBusinessType(ceVo.getExamStatId(), CONFIRM_CHECK_STAT.id).get().parse2Bto();
            cityStatTask.getInvolveUserIdList().add(-1);
            cityStatTask.setCurrentPoint(cityStatTask.getCurrentPoint() + 1);
            cityStatTask.setCreateUserId(currentUserId);
            taskService.update(cityStatTask);
        }

        // 3 构建被查单位的 自查账单 与 自查任务
        // 获取所有在库装备与不在库装备
        Map<String, List<DeviceLibrary>> devInLib = deviceRepo.findAll().stream()
                .filter(device -> device.getOwnUnit().equals(device.getLocationUnit()))
                .collect(groupingBy(DeviceLibrary::getOwnUnit));

        Map<String, List<DeviceLibrary>> devNotInLib = deviceRepo.findAll().stream()
                .filter(device -> !device.getOwnUnit().equals(device.getLocationUnit()))
                .collect(groupingBy(DeviceLibrary::getOwnUnit));

        // 3. 构建被核查单位的详情账单与Task
        // 对每个需要核查的单位构建其detail账单与task
        for (CheckExamDetailVo ed : examDetailVos) {
            Units unit = unitsRepo.findByAreaId(ed.getAreaId()).get(0);
            String names = ed.getUserNames().stream().collect(joining(","));
            // 3-1  构建被查单位的 自查账单
            DeviceCheckDetail unitDetailDoc = DeviceCheckDetail.EmptyWithChecker(names + "|" + ed.getRemark(), ceVo.getTitle()+"%^&"+ed.getRemark(), 0, 0, 0, 0, unit.getName(), devInLib.getOrDefault(unit.getName(), new ArrayList<>()), devNotInLib.getOrDefault(unit.getName(), new ArrayList<>()));
            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(CHECK_EXAM_DETAIL_0.id, "自核查任务", cityStatTask.getId(), addNode(cityStatTask.getNodeIdDetail(), cityStatTask.getId()), CONFIRM_CHECK_DETAIL.id, detail.getId(), unit.getUnitId(), 0);
            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));
    }


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



    /**
     * 对于专员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,
                                                @RequestBody DevLibVo devLibVo) {
        //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.updateCheckDetail(id, detailString, checkResult, currentUser.getUserId(), assignUserId, (int) count);
        //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 = detailRepo.findById(id).get().getCheckUnit();
        AreaUnit areaUnit = auService.findOne(AuExample.UnitName, unitName);
        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_1.id) || currentTask.getBillStatus().equals(CHECK_EXAM_DETAIL_1.id);
        if (!CEDStep1) {
            return ResponseEntity.status(400).body(new ResultObj<>(String.format("当前任务的状态异常！当前任务状态为 %s , 任务状态应该为 %s", GlobalMap.getStatusEnumMap().get(currentTask.getBillStatus()), CHECK_DETAIL_1.name)));
        }

        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流程还是多流程的
            DeviceCheckDetail currentDetail = detailRepo.findById(id).get();
            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("[核查模块] 该详情任务是一个2流程任务，结束任务并统计数据...");
                summaryDetail(currentTask, currentDetail);
                taskService.moveToEnd(currentTask);
            }
        } else {
            //不通过则回到第一阶段
            StatusEnum firstStatus = getFirstStatus(currentTask.getBillStatus());
            taskService.moveToSpecial(currentTask, firstStatus, currentTask.getFirstUserId());
        }
        log.info("[核查模块] 专管员B操作成功");
        return ResponseEntity.ok(new ResultObj<>("专管B操作成功"));
    }


    @ApiOperation(value = "核查组A/B确认核查详情单")
    @PutMapping("/detail/C/{id}")
    public ResponseEntity<ResultObj> checkUserC(@PathVariable Integer id,
                                                @RequestParam boolean pass) {
        TaskBto currentTask = taskService.get(id, CONFIRM_CHECK_DETAIL.id);
        DeviceCheckDetail currentDetail = detailRepo.findById(id).get();
        if (pass) {
            // 如果当前是第3步(利用余数来判断)，则需要指定核查组B的人来接受任务
            boolean stepC = currentTask.getBillStatus() % 10 == 2;
            boolean stepD = currentTask.getBillStatus() % 10 == 3;
            if (stepC) {
                log.info("[核查模块] C 检查组成员A正在进行核查操作，核查详情账单id为 : {}", id);
                if (!currentTask.getBillStatus().equals(CHECK_DETAIL_2.id)) {
                    return ResponseEntity.status(400).body(new ResultObj<>(String.format("当前任务的状态异常！当前任务状态为 %s , 任务状态应该为 %s", GlobalMap.getStatusEnumMap().get(currentTask.getBillStatus()), CHECK_DETAIL_2.name)));
                }
                taskService.moveToNext(currentTask, currentDetail.getCheckUserBId());
                log.info("[核查模块] C 操作完毕");
            }

            if (stepD) {
                if (!currentTask.getBillStatus().equals(CHECK_DETAIL_3.id)) {
                    return ResponseEntity.status(400).body(new ResultObj<>(String.format("当前任务的状态异常！当前任务状态为 %s , 任务状态应该为 %s", GlobalMap.getStatusEnumMap().get(currentTask.getBillStatus()), CHECK_DETAIL_3.name)));
                }
                // 如果当前是第4步，则直接结束任务,并且进行结果汇总
                log.info("[核查模块] D 检查组成员A正在进行核查操作，核查详情账单id为 : {}", id);
                summaryDetail(currentTask, currentDetail);
                currentTask = taskService.moveToEnd(currentTask);
                log.info("[核查模块] D 操作完毕");
            }
        } else {
            // 如果没通过则返回第1步
            StatusEnum firstStatus = getFirstStatus(currentTask.getBillStatus());
            taskService.moveToSpecial(currentTask, firstStatus, currentTask.getFirstUserId());
        }
        return ResponseEntity.ok(new ResultObj<>("操作成功"));
    }

    /**
     * @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);

        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;

        if (hasParent) {
            //市统计的办结 -> 开启对应市的待办任务
            String areaName = auService.findOne(AuExample.UnitId, currentTask.getOwnUnit()).getName();
            Integer provId = areaRepo.findAreasByType(1).get(0).getId();
            TaskBto cityDoneTask = new TaskBto(CHECK_STAT_1.id, areaName + "统计确认待办任务", parentTaskId, ".", CONFIRM_CHECK_STAT.id, statId, provId, 0);
            cityDoneTask = taskService.start(cityDoneTask);
            log.info("[核查模块] 统计确认待办任务生成成功, id为 : {}", cityDoneTask.getId());
        }

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

    /**
     * 任务结束后需要将当前城市的统计信息汇总上
     *
     * @param currentTask   当前任务
     * @param currentDetail 需要汇总的详情核查数据对象
     */
    private void summaryDetail(TaskBto currentTask, DeviceCheckDetail currentDetail) {
        log.info("[核查模块] 正在进行数据汇总");
        // 先找到汇总地区的账单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 = currentDetail.getCheckUnit();
        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;
            taskService.moveToNext(fatherTask, lastUserId);
        }
        log.info("[核查模块] 数据汇总完毕");
    }

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

    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(只统计新增和无误的作为数量)

        // 根这里uuid可以从detail里拿,根据unitId 查到 areaId 根据 areaId 查询到 areaName
        String areaName = auService.findOne(AuExample.UnitName, detail.getCheckUnit()).getName();

        // 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);

        // checkDevice 批量查询做一个MAP缓存
        List<Integer> idList = Arrays.stream(statArray)
                .filter(StringUtils::isNotEmpty)
                .map(s -> s.split("-")[0])
                .map(Integer::parseInt)
                .collect(toList());
        Map<Integer, DeviceLibrary> deviceMap = deviceRepo.findAllByIdIn(idList)
                .stream()
                .collect(toMap(DeviceLibrary::getId, Function.identity()));

        for (String s : statArray) {
            if (StringUtils.isEmpty(s)) {
                continue;
            }
            String[] device = s.split("-");
            int deviceId = Integer.parseInt(device[0]);
            int proofResult = Integer.parseInt(device[1]);

            DeviceLibrary checkDevice = deviceMap.get(deviceId);

            CheckAreaStatVo checkAreaStatVo;
            // 十位数 1 人工 2 自动
            // 0缺失1无误2新增3不在库9未检查
            int digits = proofResult % 10;

            if (digits == 9) {
                checkAreaStatVo = new CheckAreaStatVo(areaName, 1, 1, 0, 0, statId, detailId);
            } else if (digits == 3) {
                //跳过非在库的统计
                continue;
            } else if (digits == 1) {
                checkAreaStatVo = new CheckAreaStatVo(areaName, 1, 1, 2, 0, statId, detailId);
            } else if (digits == 0) {
                checkAreaStatVo = new CheckAreaStatVo(areaName, 0, 1, 2, 1, statId, detailId);
            } else {
                checkAreaStatVo = new CheckAreaStatVo(areaName, 1, 1, 2, 1, statId, detailId);
            }

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

            statVoList.add(new CheckDeviceStatVo(checkDevice.getModel(), checkDevice.getName(), 1, areaStatVoList));
        }

        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 = deviceRepo.findAll()
                .stream()
                .filter(dev -> unitNameList.contains(dev.getOwnUnit()))
                .filter(dev -> initUnitNames.contains(dev.getOwnUnit()))
                .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 = deviceRepo.findAll()
                .stream()
                .filter(dev -> unitNameList.contains(dev.getOwnUnit()))
                .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();
        }

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

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

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

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


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




