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

import com.github.wenhao.jpa.Specifications;
import com.google.common.collect.ImmutableMap;
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.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.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 {

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

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

    /**
     * @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();
        String title = statRepo.getOne(statId).getTitle();

        return ResponseEntity.ok(title);
    }

    @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("/startCheck")
    public ResponseEntity startCheck(@RequestBody CheckCheckVo ccVO) {
        //构建省的统计账单
        Integer startUnitId = ccVO.getUnitId();
        Units startUnit = unitsRepo.findById(startUnitId).get();
        List<Units> checkedUnits = unitsRepo.findByAreaIdIn(ccVO.getAreaRange());
        List<String> checkedUnitNames = checkedUnits.stream().map(Units::getName).collect(toList());
        log.info("[核查模块]发起核查，发起单位为{},被查单位为{}", startUnit.getName(), checkedUnitNames);

        DeviceCheckStat provinceCheckStat = initStatData(ccVO.getTitle(), ccVO.getRemark(), 0, 0, startUnit.getName(), checkedUnits, ccVO.getEndTime().atStartOfDay());
        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);

        //构建市的检查任务
        for (Units unit : checkedUnits) {
            //构建市的统计账单
            String cityTitle = unit.getName() + "检查统计";
            DeviceCheckStat cityCheckStat = initStatData(cityTitle, ccVO.getRemark(), 0, 0, unit.getName(), Collections.emptyList(), ccVO.getEndTime().atStartOfDay());
            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");
            taskService.start(cityStatTask);
        }
        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();
        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());

        log.info("[核查模块]发起手动核查，发起单位为{},被查单位为{}", startUnit.getName(), checkedUnitNames);
        // 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;
        }
        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(), 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");
            taskService.start(checkedTask);
        }

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


    /**
     * 对于专员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() == 1)
                .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();
        Integer cityId = currentTask.getOwnUnit();
        AreaUnit areaUnit = auService.findOne(AuExample.UnitId, cityId);
        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, @RequestBody List<FileRet> checkFiles) {
        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) {
            //依据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 + "统计确认待办任务", 0, ".", 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.getCheckDetail());
        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);

        // 第一个区域替换,否则累加
//        if (firstArea) {
//            cityStatVo.setDeviceStatVoList(addVos);
//        } else {
//            // 获得当前城市的统计信息 以及 要汇总的地区信息 并累加保存
//            cityStatVo = cityStatVo.cleanReduce(addVos);
//        }
        statRepo.save(cityStatVo.toDo());

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

        // 如果汇总完毕则将父级的统计任务推进
        if (over) {
            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, String statString) {
        List<CheckDeviceStatVo> statVoList = new ArrayList<>();
        //分为 id - status 的数组 其中status 0缺失 1无误 2新增 3不在库
        String[] statArray = statString.split(",");
        //将 id - status 转化为 model - count - status(只统计新增和无误的作为数量)

        // 根据unitId 查到 areaId 根据 areaId 查询到 areaName
        int areaId = unitsRepo.findAreaId(task.getOwnUnit());
        String areaName = areaRepo.findNameById(areaId);

        for (String s : statArray) {
            String[] device = s.split("-");
            int deviceId = Integer.parseInt(device[0]);
            int proofResult = Integer.parseInt(device[1]);
            DeviceLibrary checkDevice = deviceRepo.findById(deviceId).get();
            // 查询出地区对应的统计账单与详情账单
            // 查询出对应的detailId与statId(从fatherTask中获得)
            int detailId = Optional.ofNullable(taskRepo.findBillIdByTaskId(task.getId())).orElse(0);

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

            CheckAreaStatVo checkAreaStatVo;
            // proofResult 9=(1,1) 1=(2,0) other=(2,1)
            if (proofResult == 9) {
                checkAreaStatVo = new CheckAreaStatVo(areaName, 1, 1, 1, 1, statId, detailId);
            } else if (proofResult == 1) {
                checkAreaStatVo = new CheckAreaStatVo(areaName, 1, 1, 2, 0, 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());

        Collection<CheckDeviceStatVo> statVos = deviceRepo.findAll()
                .stream()
                .filter(dev -> unitNameList.contains(dev.getOwnUnit()))
                .filter(dev -> initUnitNames.contains(dev.getOwnUnit()))
                .map(transUtil::device2InitStatVo)
                .collect(toMap(d -> d.getDeviceModel() + d.getDeviceName(), Function.identity(), CheckDeviceStatVo::reduce))
                .values();

        return new DeviceCheckStat(
                CheckType.MANUAL_CHECK,
                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 = deviceRepo.findAll()
                .stream()
                .filter(dev -> unitNameList.contains(dev.getOwnUnit()))
                .map(transUtil::device2InitStatVo)
                .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.MANUAL_CHECK,
                title,
                startUnitName + "待核查装备统计单",
                JacksonUtil.toJSon(new ArrayList<>(statVos)),
                checkAId,
                checkBId,
                remark,
                endTime);
    }

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




