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.TaskService;
import com.tykj.dev.device.task.subject.bto.TaskBto;
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 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.CHECK_DETAIL_0;
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(rollbackFor = Exception.class)
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;

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

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

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

    /**
     * 手动发起核查
     * <li>1. 添加发起核查bill记录</>
     * <li>2. 构建发起单位的统计账单与Task</>
     * <li>3. 构建被核查单位的详情账单与Task</li>
     *
     * @param billVo 核查发起对象
     */
    @ApiOperation(value = "发起核查", notes = "发起核查流程")
    @PostMapping("/check/bill")
    public ResultObj 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());

        // 2-1 构建发起单位的 统计账单
        DeviceCheckStat provinceCheckStat = initStatData(startUnit.getName(), checkedUnits);
        Integer billId = statRepo.save(provinceCheckStat).getId();
        // 2-2 构建发起单位的 统计任务
        TaskBto provStatTask = new Task(CONFIRM_CHECK_STAT.name, 0, ".0.", CONFIRM_CHECK_STAT.id, billId, startUnitId)
                .parse2Bto();
        taskService.start(provStatTask);

        // 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.get(unit.getName()), devNotInLib.get(unit.getName()));
            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);
        }

        return 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
        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);
        taskService.moveToNext(currentTask);
        return ResponseEntity.ok(new ResultObj("专管员A操作成功"));
    }

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

        if (checkStatus == 0) {
            return ResponseEntity.status(400).body("checkStatus不应该为0!");
        }

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

        // 审核通过与不通过的逻辑不同
        TaskBto currentTask = taskService.get(id, CONFIRM_CHECK_DETAIL.id);
        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);
                taskService.moveToNext(currentTask, checkUserAId);
            } else {
                // 如果是2流程的，则直接结束该任务
                detailRepo.updateCheckStatus(id, checkStatus);
                taskService.moveToEnd(currentTask);
            }
        } else {
            //不通过则回到第一阶段
            StatusEnum firstStatus = getFirstStatus(currentTask.getBillStatus());
            taskService.moveToSpecial(currentTask, firstStatus, currentTask.getFirstUserId());
        }

        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的人来接受任务
            if (currentTask.getBillStatus() % 10 == 2) {
                currentTask = taskService.moveToNext(currentTask, currentDetail.getCheckUserBId());
            }

            // 如果当前是第4步，则直接结束任务,并且进行结果汇总
            if (currentTask.getBillStatus() % 10 == 3) {
                currentTask = taskService.moveToEnd(currentTask);
                // 任务结束后需要将当前城市的统计信息汇总上去
                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);
                    taskService.moveToNext(fatherTask, fatherTask.getLastUserId());
                }
            }

        } else {
            // 如果没通过则返回第1步
            StatusEnum firstStatus = getFirstStatus(currentTask.getBillStatus());
            taskService.moveToSpecial(currentTask, firstStatus, currentTask.getFirstUserId());
        }

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


    @ApiOperation(value = "统计数据确认")
    @PostMapping("/stat/verify")
    public ResponseEntity statConfirm(@RequestParam int statId,
                                      @RequestParam int areaId) {
        //将当前的统计task完结
        TaskBto currentTask = taskService.get(CONFIRM_CHECK_STAT.id, statId);
        taskService.moveToEnd(currentTask);

        //如果有上级统计任务 则累加当前地区数据
        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) {
                taskService.moveToEnd(parentTask);
            }

        }

        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 + ".";
    }
}
