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

import com.tykj.dev.config.swagger.AutoDocument;
import com.tykj.dev.device.confirmcheck.entity.domain.DeviceCheckBillEntity;
import com.tykj.dev.device.confirmcheck.entity.domain.DeviceCheckDetailEntity;
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.DeviceCheckStatRepo;
import com.tykj.dev.device.confirmcheck.utils.ObjTransUtil;
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.TaskLogService;
import com.tykj.dev.device.task.service.TaskService;
import com.tykj.dev.device.task.subject.bto.TaskBto;
import com.tykj.dev.device.task.subject.bto.TaskLogBto;
import com.tykj.dev.device.task.subject.common.GlobalMap;
import com.tykj.dev.device.task.subject.common.StatusEnum;
import com.tykj.dev.device.task.subject.domin.Task;
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.Units;
import com.tykj.dev.device.user.subject.entity.User;
import com.tykj.dev.device.user.util.AuthenticationUtils;
import com.tykj.dev.misc.base.ResultObj;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import static com.tykj.dev.device.task.subject.common.BusinessEnum.CONFIRM_CHECK_DETAIL;
import static com.tykj.dev.device.task.subject.common.BusinessEnum.CONFIRM_CHECK_STAT;
import static com.tykj.dev.device.task.subject.common.StatusEnum.*;
import static com.tykj.dev.misc.utils.TimestampUtil.localDateToDate;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toList;

/**
 * @author dengdiyi
 */
@RestController
@RequestMapping(value = "/check/confirm")
@AutoDocument
@Api(tags = "核查模块")
@Transactional
@Slf4j
public class DeviceCheckController {

    @Autowired
    private DeviceCheckStatRepo 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 TaskLogService logService;
    @Autowired
    private AuthenticationUtils AuthenticationUtils;

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

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

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

    /**
     * 手动发起核查
     * <li>1. 添加发起核查bill记录</>
     * <li>2. 构建发起单位的统计账单与Task</>
     * <li>3. 构建被核查单位的详情账单与Task</li>
     *
     * @param billVo 核查发起对象
     */
    @ApiOperation(value = "发起核查", notes = "发起核查流程")
    @PostMapping("/check/bill")
    public ResponseEntity startManualCheck(@RequestBody CheckBillVo billVo) {
        // 1. 添加发起核查bill记录
        DeviceCheckBillEntity billDo = transUtil.checkBillVo2Do(billVo);
        billRepo.save(billDo);

        // 2 构建发起单位的 统计账单 与 统计任务
        Integer startUnitId = billVo.getUnitId();
        Units startUnit = unitsRepo.findById(startUnitId).get();
        List<Units> checkedUnits = unitsRepo.findByAreaIdIn(billVo.getAreaRange());
        List<String> checkedUnitNames = checkedUnits.stream().map(Units::getName).collect(toList());

        log.info("[核查模块]发起手动核查，发起单位为{},被查单位为{}", startUnit.getName(), checkedUnitNames);
        // 2-1 构建发起单位的 统计账单
        DeviceCheckStat provinceCheckStat = initStatData(startUnit.getName(), checkedUnits);
        Integer billId = statRepo.save(provinceCheckStat).getId();
        // 2-2 构建发起单位的 统计任务
        TaskBto provStatTask = new Task(CHECK_STAT_0.id, CONFIRM_CHECK_STAT.name, 0, ".0.", CONFIRM_CHECK_STAT.id, billId, startUnitId)
                .parse2Bto();
        provStatTask.getInvolveUserIdList().add(AuthenticationUtils.getAuthentication().getCurrentUserInfo().getUserId());
        provStatTask.getInvolveUserIdList().add(-1);
        provStatTask.setCurrentPoint(1);
        provStatTask = taskService.start(provStatTask).parse2Bto();

        // 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 (Units unit : checkedUnits) {
            // 3-1  构建被查单位的 自查账单
            DeviceCheckDetailEntity unitDetailDoc = DeviceCheckDetailEntity.EmptyWithChecker(billVo.getUserAId(), billVo.getUserBId(), 0, 0, unit.getName(), devInLib.getOrDefault(unit.getName(), new ArrayList<>()), devNotInLib.getOrDefault(unit.getName(), new ArrayList<>()));
            DeviceCheckDetailEntity detail = detailRepo.save(unitDetailDoc);
            // 3-1  构建被查单位的 自查任务 (根据被查单位的级别来区分是县级状态是市级状态)
            TaskBto checkedTask = new TaskBto(CHECK_DETAIL_0.id, "自核查任务", provStatTask.getId(), addNode(provStatTask.getNodeIdDetail(), provStatTask.getId()), CONFIRM_CHECK_DETAIL.id, detail.getId(), unit.getUnitId(), 0);
            taskService.start(checkedTask);
        }

        log.info("[核查模块] {}单位成功发起对 {} 单位的核查任务分发", startUnit.getName(), checkedUnitNames);
        return ResponseEntity.ok(new ResultObj(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 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();
        detailRepo.updateCheckDetail(id, detailString, checkResult, currentUser.getUserId(), assignUserId);
        //2. 推进TASK 状态
        TaskBto currentTask = taskService.get(id, CONFIRM_CHECK_DETAIL.id);
        currentTask.getInvolveUserIdList().set(0, AuthenticationUtils.getAuthentication().getCurrentUserInfo().getUserId());
        taskService.moveToNext(currentTask, assignUserId);
        logService.addLog(new TaskLogBto(currentTask.getId(), "A岗核查成功"));
        log.info("[核查模块] A岗核查操作成功");
        return ResponseEntity.ok(new ResultObj("专管员A操作成功"));
    }

    /**
     * 对于专管员B来说的逻辑
     */
    @ApiOperation(value = "专管员B核查详情单")
    @PutMapping("/detail/B/{id}")
    public ResponseEntity checkUserB(@PathVariable Integer id,
                                     @RequestParam int checkStatus,
                                     @RequestParam(required = false, defaultValue = "0") int checkUserAId,
                                     @RequestParam(required = false, defaultValue = "0") int checkUserBId) {
        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);
        if (!currentTask.getBillStatus().equals(CHECK_DETAIL_1.id)) {
            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流程还是多流程的
            DeviceCheckDetailEntity detailDo = detailRepo.findById(id).get();
            Integer userAId = detailDo.getCheckUserAId();
            Integer userBId = detailDo.getCheckUserBId();

            // 如果是4流程的，则需要指定核查组成员A接任务
            if (userAId > 0 && userBId > 0) {
                detailRepo.updateCheckStatus(id, checkStatus);
                currentTask = taskService.moveToNext(currentTask, checkUserAId);
                logService.addLog(new TaskLogBto(currentTask.getId(), "B岗审核成功,等待核查组确认"));
            } else {
                // 如果是2流程的，则直接结束该任务
                detailRepo.updateCheckStatus(id, checkStatus);
                taskService.moveToEnd(currentTask);
                logService.addLog(new TaskLogBto(currentTask.getId(), "B岗审核成功"));
            }
        } else {
            //不通过则回到第一阶段
            StatusEnum firstStatus = getFirstStatus(currentTask.getBillStatus());
            taskService.moveToSpecial(currentTask, firstStatus, currentTask.getFirstUserId());
            logService.addLog(new TaskLogBto(currentTask.getId(), "B岗审核失败，跳回A岗人员操作"));
        }

        log.info("[核查模块] 专管员B操作成功");
        return ResponseEntity.ok(new ResultObj("专管B操作成功"));
    }


    @ApiOperation(value = "核查组A/B确认核查详情单")
    @PutMapping("/detail/C/{id}")
    public ResponseEntity checkUserC(@PathVariable Integer id,
                                     @RequestParam boolean pass) {
        TaskBto currentTask = taskService.get(id, CONFIRM_CHECK_DETAIL.id);
        DeviceCheckDetailEntity currentDetail = detailRepo.findById(id).get();
        if (pass) {
            // 如果当前是第3步(利用余数来判断)，则需要指定核查组B的人来接受任务
            log.info("[核查模块] C 检查组成员A正在进行核查操作，核查详情账单id为 : {}", id);
            if (currentTask.getBillStatus() % 10 == 2) {
                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)));
                }
                currentTask = taskService.moveToNext(currentTask, currentDetail.getCheckUserBId());
                logService.addLog(new TaskLogBto(currentTask.getId(), "核查组A审核成功"));
                log.info("[核查模块] C 操作完毕");
            } else if (currentTask.getBillStatus() % 10 == 3) {
                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);
                currentTask = taskService.moveToEnd(currentTask);
                logService.addLog(new TaskLogBto(currentTask.getId(), "核查组B审核成功"));
                // 任务结束后需要将当前城市的统计信息汇总上去
                Units units = unitsRepo.findById(currentTask.getOwnUnit()).get();
                int level = units.getLevel();
                // 先找到汇总地区的账单id 查询当前detail task 的 父级TASK
                Integer fatherTaskId = currentTask.getParentTaskId();
                int statId = taskRepo.findBillId(fatherTaskId, CONFIRM_CHECK_STAT.id);

                // 获得当前城市的统计信息 以及 要汇总的地区信息 并累加保存
                List<CheckDeviceStatVo> addVos = parseStatString2Vo(currentTask, level, currentDetail.getCheckDetail());
                CheckStatVo resultVo = statRepo.findById(statId).get().toVo();
                resultVo.setDeviceStatVoList(accumulateStat(addVos, resultVo.getDeviceStatVoList()));
                statRepo.save(resultVo.toDo());

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

                // 如果汇总完毕则将父级的统计任务推进
                if (over) {
                    TaskBto fatherTask = taskService.get(fatherTaskId);
                    TaskBto statTask = taskService.moveToNext(fatherTask, fatherTask.getLastUserId());
                    logService.addLog(new TaskLogBto(statTask.getId(), "地区数据已统计完毕，等待确认", new ArrayList<>(), fatherTask.getFirstUserId()));
                }
                log.info("[核查模块] D 操作完毕");
            }

        } else {
            // 如果没通过则返回第1步
            StatusEnum firstStatus = getFirstStatus(currentTask.getBillStatus());
            currentTask = taskService.moveToSpecial(currentTask, firstStatus, currentTask.getFirstUserId());
            logService.addLog(new TaskLogBto(currentTask.getId(), "核查组B审核未过，返回到初始状态"));
        }

        return ResponseEntity.ok(new ResultObj("操作成功"));
    }


    /**
     * @param statId 统计账单主键id
     */
    @ApiOperation(value = "统计数据确认")
    @PostMapping("/stat/verify")
    public ResponseEntity statConfirm(@RequestParam int statId) {
        log.info("[核查模块] 正在进行统计数据确认，统计账单id为 : {}", statId);
        //将当前的统计task完结
        TaskBto currentTask = taskService.get(statId, CONFIRM_CHECK_STAT.id);
        currentTask = taskService.moveToEnd(currentTask);
        logService.addLog(new TaskLogBto(currentTask.getId(), "地区数据已统计完毕，等待确认"));

        //如果有上级统计任务 则累加当前地区数据
        Integer parentTaskId = currentTask.getParentTaskId();
        boolean hasParent = parentTaskId != 0;
        if (hasParent) {
            // 累加当前地区数据
            TaskBto parentTask = taskService.get(parentTaskId);
            CheckStatVo cityStat = statRepo.findById(statId).get().toVo();
            CheckStatVo provinceStat = statRepo.findById(parentTask.getBillId()).get().toVo();
            List<CheckDeviceStatVo> accStat = accumulateStat(cityStat.getDeviceStatVoList(), provinceStat.getDeviceStatVoList());
            provinceStat.setDeviceStatVoList(accStat);
            statRepo.save(provinceStat.toDo());
            // 如果所有子地区统计任务都已经完结，则推进父地区统计任务进度
            boolean allOver = taskService.TaskTreeIsOver(parentTaskId);
            if (allOver) {
                TaskBto statTask = taskService.moveToEnd(parentTask);
                logService.addLog(new TaskLogBto(statTask.getId(), "统计数据确认操作"));
            }

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


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

    private List<CheckDeviceStatVo> accumulateStat(List<CheckDeviceStatVo> originalVos, List<CheckDeviceStatVo> addVos) {
        for (CheckDeviceStatVo originalVo : originalVos) {
            for (CheckDeviceStatVo addVo : addVos) {
                if (originalVo.getDeviceModel().equals(addVo.getDeviceModel())) {
                    addVo.add(originalVo);
                }
            }
        }
        return addVos;
    }

    public List<CheckDeviceStatVo> parseStatString2Vo(TaskBto task, int cityLevel, String statString) {
        List<CheckDeviceStatVo> statVoList = new ArrayList<>();
        //分为 id - status 的数组 其中status 0缺失 1无误 2新增 3不在库
        String[] statArray = statString.split(",");
        //将 id - status 转化为 model - count - status(只统计新增和无误的作为数量)
        for (String s : statArray) {
            String[] device = s.split("-");
            int deviceId = Integer.parseInt(device[0]);
            int deviceStatus = Integer.parseInt(device[1]);
            DeviceLibrary checkDevice = deviceRepo.findById(deviceId).get();
            //查询出地区对应的统计账单与详情账单
            // 如果是level为3的城市，那么可以通过taskId获得详情id
            int statId = 0;
            int detailId = 0;
            if (cityLevel == 3) {
                Integer resultDetailId = taskRepo.findBillIdByTaskId(task.getId());
                detailId = resultDetailId == null ? 0 : resultDetailId;
            }

            // 如果是level为2的城市，可以通过unitId以及fatherId获得统计
            if (cityLevel == 2) {
                Integer resultDetailId = taskRepo.findBillIdByTaskId(task.getId());
                detailId = resultDetailId == null ? 0 : resultDetailId;
                Integer resultStatId = taskRepo.findBillId(task.getParentTaskId(), CONFIRM_CHECK_STAT.id);
                statId = resultStatId == null ? 0 : resultStatId;
            }

            // 根据unitId 查到 areaId 根据 areaId 查询到 areaName
            int areaId = unitsRepo.findAreaId(task.getOwnUnit());
            String areaName = areaRepo.findNameById(areaId);
            CheckAreaStatVo checkAreaStatVo;
            if (deviceStatus == 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 startUnitName 发起核查的单位名
     * @param unitsList     被核查单位列表
     */
    private DeviceCheckStat initStatData(String startUnitName, List<Units> unitsList) {
        //获得要被统计的单位名列表
        List<String> unitNameList = unitsList.stream().map(Units::getName).collect(toList());
        Map<String, List<DeviceLibrary>> modelMap = deviceRepo.findAll()
                .stream()
                .collect(groupingBy(DeviceLibrary::getModel));

        //查询所有装备-按照型号分组-遍历每一组型号的装备，过滤出所属在指定单位的装备，并按照单位名称分组
        List<CheckDeviceStatVo> checkDeviceStatVos = new ArrayList<>();
        modelMap.forEach((model, deviceModelList) -> {
            //按照市级区域分组
            Map<String, List<DeviceLibrary>> unitMap = deviceModelList.stream()
                    .filter(device -> unitNameList.contains(device.getOwnUnit()))
                    .collect(groupingBy(DeviceLibrary::getOwnUnit));
            List<CheckAreaStatVo> areaStatVoList = new ArrayList<>();
            // 遍历每个地区的该型号的装备 -> 构造出该地区的该型号的初始数据
            unitMap.forEach((unitName, devices) -> {
                //unitName map to area
                int areaId = unitsRepo.findAreaIdByName(unitName);
                String areaName = areaRepo.findNameById(areaId);
                areaStatVoList.add(new CheckAreaStatVo(areaName, 0, devices.size(), 0, 0, 0, 0));
            });
            //构造出各型号装备的list - 如果该型号的装备在所有地区都没有，那么就不添加这条数据
            if (!CollectionUtils.isEmpty(areaStatVoList)) {
                checkDeviceStatVos.add(new CheckDeviceStatVo(model, deviceModelList.get(0).getName(), deviceModelList.size(), areaStatVoList));
            }
        });

        //构造最终数据
        LocalDate startTime = LocalDate.now();
        LocalDate endTime = startTime.plusMonths(1);

        return new CheckStatVo(null,
                "2020年下半年核查--" + startUnitName,
                startUnitName + "待核查装备统计",
                localDateToDate(startTime),
                localDateToDate(endTime),
                checkDeviceStatVos).toDo();

    }

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