package com.tykj.dev.device.confirmcheck.service.impl;

import com.google.common.collect.Lists;
import com.tykj.dev.blockcha.subject.entity.BcHash;
import com.tykj.dev.blockcha.subject.service.BlockChainUtil;
import com.tykj.dev.device.confirmcheck.common.CheckType;
import com.tykj.dev.device.confirmcheck.common.TaskPeriod;
import com.tykj.dev.device.confirmcheck.entity.domain.DeviceCheckBill;
import com.tykj.dev.device.confirmcheck.entity.domain.DeviceCheckDetail;
import com.tykj.dev.device.confirmcheck.entity.domain.DeviceCheckPeriod;
import com.tykj.dev.device.confirmcheck.entity.domain.DeviceCheckStat;
import com.tykj.dev.device.confirmcheck.entity.vo.CheckBillSelectVo;
import com.tykj.dev.device.confirmcheck.entity.vo.CheckDeviceStatVo;
import com.tykj.dev.device.confirmcheck.entity.vo.CheckStatTableVo;
import com.tykj.dev.device.confirmcheck.repository.DeviceCheckBillDao;
import com.tykj.dev.device.confirmcheck.repository.DeviceCheckDetailDao;
import com.tykj.dev.device.confirmcheck.repository.DeviceCheckPeriodDao;
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.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.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.misc.exception.ApiException;
import com.tykj.dev.misc.utils.JacksonUtil;
import com.tykj.dev.misc.utils.MapperUtils;
import com.tykj.dev.misc.utils.PageUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.data.domain.Page;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ScheduledFuture;
import java.util.function.Function;
import java.util.stream.Collectors;

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.CHECK_DETAIL_0;
import static com.tykj.dev.misc.base.StatusEnum.CHECK_STAT_0;
import static java.util.stream.Collectors.*;

/**
 * ConfirmCheckServiceImpl.
 *
 * @author Matrix <xhyrzldf@gmail.com>
 * @since 2020/10/13 at 4:07 下午
 */
@Slf4j
@Service
public class ConfirmCheckServiceImpl implements ConfirmCheckService, CommandLineRunner {

    @Autowired
    private DeviceCheckStatDao statDao;

    @Autowired
    private DeviceCheckPeriodDao periodDao;

    @Autowired
    private ObjTransUtil objTransUtil;

    @Autowired
    private AreaDao areaRepo;

    @Autowired
    private UnitsDao unitsRepo;

    @Autowired
    private DeviceLibraryDao deviceRepo;

    @Autowired
    private DeviceCheckDetailDao detailRepo;

    @Autowired
    private ObjTransUtil transUtil;

    @Autowired
    private TaskService taskService;

    @Autowired
    private AreaCache areaCache;

    private ScheduledFuture<?> future;

    @Autowired
    private ThreadPoolTaskScheduler threadPoolTaskScheduler;

    @Autowired
    private BlockChainUtil blockChainUtil;

    @Autowired
    private DeviceCheckBillDao deviceCheckBillDao;

    //实例化一个线程池任务调度类,可以使用自定义的ThreadPoolTaskScheduler
    @Bean
    public ThreadPoolTaskScheduler threadPoolTaskScheduler() {
        ThreadPoolTaskScheduler executor = new ThreadPoolTaskScheduler();
        return new ThreadPoolTaskScheduler();
    }

    @Override
    @NotNull
    public Map<String, List<Integer>> autoCheck() {
        // 构建返回数据对象
        Map<String, List<Integer>> resultIds = new HashMap<>(8);
        resultIds.put("statId", new ArrayList<>());
        resultIds.put("detailId", new ArrayList<>());
        resultIds.put("taskId", new ArrayList<>());

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

        // 发起省级的统计 - 获得所有的市级单位
        List<Units> cityUnits = unitsRepo.findAllByLevel(2);
        Units provUnit = unitsRepo.findById(1).get();
        String baseTitle = "自动-" + LocalDate.now().getYear() + "年" + (LocalDate.now().getMonthValue() + 1) + "月";
        // 构建省的统计账单

        DeviceCheckStat provStatDo = new DeviceCheckStat(
                CheckType.CT_CHECK,
                baseTitle + provUnit.getName() + "核查统计单",
                "核查统计单",
                "系统发起的统计|" + provUnit.getName() + "|" + provUnit.getAreaId());

        DeviceCheckStat provStat = statDao.save(provStatDo);
        resultIds.get("statId").add(provStat.getId());
        List<DeviceLibrary> deviceList = deviceRepo.findAll();
        Map<String, List<DeviceLibrary>> devInLib = deviceList.stream()
                .filter(device -> device.getOwnUnit().equals(device.getLocationUnit()))
                .collect(groupingBy(DeviceLibrary::getOwnUnit));

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

        // 构建省统Task
        TaskBto provStatTask = new Task(CHECK_STAT_0.id, "省自动核查", 0, ".0.", CONFIRM_CHECK_STAT.id, provStat.getId(), provUnit.getUnitId())
                .parse2Bto();
        provStatTask.setInvolveUserIdList(Lists.newArrayList(-1));
        provStatTask.setCustomInfo("auto");
        provStatTask = taskService.start(provStatTask);
        resultIds.get("taskId").add(provStatTask.getId());

        List<CheckDeviceStatVo> statVoList = new ArrayList<>();
        List<DeviceCheckStat> cityStatList = new ArrayList<>();
        List<String> countyAreaNames = new ArrayList<>();
        // 构建所有市的统计账单(先不考虑JSON-INFO)
        for (Units cityUnit : cityUnits) {
            List<Integer> areaIds = areaRepo.findAllByFatherId(cityUnit.getAreaId()).stream().map(Area::getId).collect(toList());
            List<Units> countyUnits = unitsRepo.findByAreaIdIn(areaIds);

            // 构建市统账单
            DeviceCheckStat cityStatDo = new DeviceCheckStat(
                    CheckType.CT_CHECK,
                    baseTitle + cityUnit.getName() + "核查统计单",
                    "核查统计单",
                    "系统发起的统计|" + cityUnit.getName() + "|" + cityUnit.getAreaId()
            );

            DeviceCheckStat cityStat = statDao.save(cityStatDo);
            cityStatList.add(cityStat);
            String cityName = areaCache.findById(cityUnit.getAreaId()).getName();
            areaStatIdMap.put(cityName, cityStat.getId());
            resultIds.get("statId").add(cityStat.getId());
            // 构建市统task 获得id
            TaskBto cityStatTask = new Task(CHECK_STAT_0.id, cityUnit.getName() + "自动核查统计", provStatTask.getId(), addNode(provStatTask.getNodeIdDetail(), provStatDo.getId()), CONFIRM_CHECK_STAT.id, cityStat.getId(), cityUnit.getUnitId())
                    .parse2Bto();
            cityStatTask.setInvolveUserIdList(Lists.newArrayList(-1));
            cityStatTask.setCustomInfo("auto");
            cityStatTask = taskService.start(cityStatTask);
            resultIds.get("taskId").add(cityStatTask.getId());

            // 构建市自查账单
            DeviceCheckDetail cityDetailDo = DeviceCheckDetail.EmptyWithChecker(
                    "系统发起的自查|" + cityUnit.getName() + "|" + cityUnit.getAreaId(),
                    baseTitle,
                    0, 0, 0, 0,
                    cityUnit.getName(),
                    devInLib.getOrDefault(cityUnit.getName(), new ArrayList<>()),
                    devNotInLib.getOrDefault(cityUnit.getName(), new ArrayList<>()));
            DeviceCheckDetail cityDetail = detailRepo.save(cityDetailDo);
            resultIds.get("detailId").add(cityDetail.getId());

            List<CheckDeviceStatVo> cityStatVoList = deviceList.stream()
                    .filter(d -> d.getOwnUnit().equals(cityUnit.getName()))
                    .map(d -> transUtil.device2InitStatVo(d, cityName, cityStat.getId(), cityDetail.getId()))
                    .collect(toList());
            statVoList.addAll(cityStatVoList);
            // 构建市自查TASK
            TaskBto cityDetailTask = new TaskBto(CHECK_DETAIL_0.id, cityUnit.getName() + "自动核查自查", cityStatTask.getId(), addNode(cityStatTask.getNodeIdDetail(), cityStatTask.getId()), CONFIRM_CHECK_DETAIL.id, cityDetail.getId(), cityUnit.getUnitId(), 0);
            cityDetailTask.setCustomInfo("auto");
            cityDetailTask = taskService.start(cityDetailTask);
            resultIds.get("taskId").add(cityDetailTask.getId());

            // 构建县任务
            for (Units countyUnit : countyUnits) {
                String countyName = areaCache.findById(countyUnit.getAreaId()).getName();
                countyAreaNames.add(countyName);
                //构建县自查账单
                DeviceCheckDetail countyDetailDo = DeviceCheckDetail.EmptyWithChecker(
                        "系统发起的自查|" + countyUnit.getName() + "|" + countyUnit.getAreaId(),
                        baseTitle,
                        0, 0, 0, 0,
                        countyUnit.getName(),
                        devInLib.getOrDefault(countyUnit.getName(), new ArrayList<>()),
                        devNotInLib.getOrDefault(countyUnit.getName(), new ArrayList<>()));
                DeviceCheckDetail countyDetail = detailRepo.save(countyDetailDo);
                resultIds.get("detailId").add(countyDetail.getId());
                List<CheckDeviceStatVo> countyStatVoList = deviceList.stream()
                        .filter(d -> d.getOwnUnit().equals(countyUnit.getName()))
                        .map(d -> transUtil.device2InitStatVo(d, countyName, cityStat.getId(), countyDetail.getId()))
                        .collect(toList());
                statVoList.addAll(countyStatVoList);

                //构建县自查TASK
                TaskBto countyDetailTask = new TaskBto(CHECK_DETAIL_0.id, countyUnit.getName() + "自动核查自查", cityStatTask.getId(), addNode(cityStatTask.getNodeIdDetail(), cityStatTask.getId()), CONFIRM_CHECK_DETAIL.id, countyDetail.getId(), countyUnit.getUnitId(), 0);
                countyDetailTask.setCustomInfo("auto");
                countyDetailTask = taskService.start(countyDetailTask);
                resultIds.get("taskId").add(countyDetailTask.getId());
            }

        }

        // 处理JSON INFO 数据
        // 对于省统计来说，需要把区的数据累加到市级中去
        List<CheckDeviceStatVo> statVoListCopy1 = MapperUtils.deepClone(statVoList, CheckDeviceStatVo::new);
        List<CheckDeviceStatVo> statVoListCopy2 = MapperUtils.deepClone(statVoList, CheckDeviceStatVo::new);

        //按照区数据与市数据进行分组 - true->区级装备统计信息 false->市级装备统计是信息
        Map<Boolean, List<CheckDeviceStatVo>> regionMap = statVoListCopy1.stream()
                .collect(partitioningBy(stat -> countyAreaNames.contains(stat.getAreaStatList().get(0).getAreaName())));
        // 区数据
        List<CheckDeviceStatVo> countyVo = regionMap.get(true);
        // 市级数据
        Map<String, CheckDeviceStatVo> map = regionMap.get(false).stream()
                .collect(toMap(d -> d.getDeviceModel()+d.getDeviceName(), Function.identity()));

        //查找区域数据内的父级地区(即市)，将数据count数据add进去(通过两次get),将areaList数据也加进去

        for (CheckDeviceStatVo v : countyVo) {
            String couName = v.getAreaStatList().get(0).getAreaName();
            String cityName = areaCache.findFatherByName(couName).getName();
            //把相同型号与名字的的区级的数据merge到市级即可,没有的话改个名加进去
            Integer cityStatId = areaStatIdMap.get(cityName);
            map.computeIfPresent(v.getDeviceModel()+v.getDeviceName(), (k, value) -> value.reduce(v, cityName, cityStatId));
            map.computeIfAbsent(v.getDeviceModel()+v.getDeviceName(), k -> {
                v.getAreaStatList().forEach(area -> area.setAreaName(cityName));
                return v;
            });
        }
        //将市级Map里的数据全部取出来展平一层并累加

        List<CheckDeviceStatVo> filterVoList = new ArrayList<>(map.values());
        provStat.setStatInfo(JacksonUtil.toJSon(filterVoList));
        statDao.save(provStat);

        // 对于市统计来说，只需要自己本市以及其下地区的数据
        for (DeviceCheckStat csd : cityStatList) {
            List<String> cityNames = new ArrayList<>();
            if (csd.getRemark().split("\\|").length <= 1) {
                continue;
            }
            Integer cityId = Integer.valueOf(csd.getRemark().split("\\|")[2]);
            String cityName = areaCache.findById(cityId).getName();
            List<String> childNames = areaRepo.findByFatherId(cityId).stream().map(Area::getName).collect(toList());
            cityNames.add(cityName);
            cityNames.addAll(childNames);

            //去除其他地区的数据
            List<CheckDeviceStatVo> cityStatVo = new ArrayList<>();
            statVoListCopy2.stream()
                    .filter(stat -> cityNames.contains(stat.getAreaStatList().get(0).getAreaName()))
                    .collect(Collectors.groupingBy(d -> d.getDeviceModel()+d.getDeviceName(), reducing(CheckDeviceStatVo::reduce)))
                    .forEach((k, v) -> cityStatVo.add(v.get()));
            csd.setStatInfo(JacksonUtil.toJSon(cityStatVo));
        }

        statDao.saveAll(cityStatList);
        return resultIds;
    }


    /**
     * 根据关键字查询报告列表
     * @return {@link CheckStatTableVo} 's List
     */
    @Override
    public Page<CheckStatTableVo> findAllStatTable(CheckBillSelectVo checkBillSelectVo) {
        //过滤出顶级节点
        List<TaskBto> topStatList = taskService.findBillType(7);
        List<TaskBto> topDetailList = taskService.findBillType(8);
        topStatList.addAll(topDetailList);

        List<Integer> topIdList = topStatList.stream()
                .filter(taskBto -> taskBto.getParentTaskId().equals(0) || taskBto.getParentTaskId() == null)
                .map(TaskBto::getBillId)
                .collect(toList());

        List<CheckStatTableVo> tableVos = statDao.findAll(checkBillSelectVo.getPageable().getSort()).stream()
                .filter(deviceCheckStat -> !"[]".equals(deviceCheckStat.getStatInfo()))
                .map(objTransUtil::stat2TableVo)
                .filter(vo -> keywordFilter(vo, checkBillSelectVo.getKeyword()))
                .filter(v->topIdList.contains(v.getId()))
                .collect(Collectors.toList());

        return PageUtil.getPerPage(checkBillSelectVo.getPage(),checkBillSelectVo.getSize(),tableVos,checkBillSelectVo.getPageable());

    }

    /**
     * 更新自动核查的任务周期
     *
     * @param taskPeriod 要更新成为的周期
     */
    @Override
    public Integer updateTaskPeriod(TaskPeriod taskPeriod) {
        log.info("[核查模块] 更新自动核查任务周期，更新为 {}", taskPeriod.name());
        return periodDao.save(new DeviceCheckPeriod(taskPeriod)).getId();
    }

    /**
     * 获得当前自动核查的任务周期
     *
     * @return 当前的 {@link TaskPeriod}
     */
    @Override
    public DeviceCheckPeriod getCurrentTaskPeriod() {
        DeviceCheckPeriod tcp = periodDao.findTopByOrderByIdDesc();
        return tcp == null ? new DeviceCheckPeriod(TaskPeriod.yearly) : tcp;
    }

    /**
     * 获得下一次计划任务的执行时间
     *
     * @return 下次计划任务的执行时间
     */
    @Override
    public LocalDate getNextTaskDate() {
        // 获得当前的period
        DeviceCheckPeriod dcp = periodDao.findTopByOrderByIdDesc();
        TaskPeriod period;
        if (dcp == null) {
            period = TaskPeriod.yearly;
        } else {
            period = dcp.getCronExpression();
        }
        LocalDate now = LocalDate.now();
        switch (period) {
            case yearly:
                return now.plusYears(1L).withDayOfYear(1);
            case monthly:
                return now.plusMonths(1L).withDayOfMonth(1);
            case quarterly:
                // 3 - 当前月对3的余数 即为要添加的月份数 例如 8 +  (3 - 8%3) = 9
                int plusMonth = 3 - now.getMonthValue() % 3;
                return now.plusMonths(plusMonth).withDayOfMonth(1);
            default:
                throw new ApiException("获取当前计划任务时间出错！");
        }
    }

    /**
     * 开启自动核查计划任务
     *
     * @return 任务是否开启成功
     */
    @Override
    public boolean startAutoCheckCron() {
        boolean flag = false;
        //从数据库动态获取执行周期
        TaskPeriod period;
        // 如果数据库里没有设置，那么就使用默认的年
        if (getCurrentTaskPeriod() == null) {
            period = TaskPeriod.yearly;
        } else {
            period = getCurrentTaskPeriod().getCronExpression();
        }
        String cron = period.getCron();
        future = threadPoolTaskScheduler.schedule(this::autoCheck, new CronTrigger(cron));
        if (future != null) {
            flag = true;
            LocalDate nextTaskDate = getNextTaskDate();
            log.info("[核查模块] 自动核查计划任务开启成功!周期为 {},下一次执行时间为 {}", period.name(), nextTaskDate);
        } else {
            log.error("[核查模块] 自动核查计划任务开启失败...");
        }
        return flag;
    }

    /**
     * 关闭自动核查计划任务
     *
     * @return 任务是否关闭成功
     */
    @Override
    public boolean stopAutoCheckCron() {
        boolean flag = false;
        if (future != null) {
            boolean cancel = future.cancel(true);
            if (cancel) {
                flag = true;
                log.info("[核查模块] 自动核查计划任务停止成功！");
            } else {
                log.error("[核查模块] 自动核查计划任务停止失败！");
            }
        } else {
            flag = true;
            log.info("[核查模块] 自动核查计划任务已经停止了..");
        }
        return flag;
    }

    /**
     * @param deviceCheckBills DeviceCheckBill异步上链
     */
    @Override
    @Async("taskScheduler")
    public void sendHash1(List<DeviceCheckBill> deviceCheckBills) {
        deviceCheckBills.forEach(deviceCheckBill -> {
            BcHash bcText = blockChainUtil.sendHash(1000, JacksonUtil.toJSon(deviceCheckBill));
            String recordId = bcText.getData().getRecordID();
            deviceCheckBill.setRecordId(recordId);
            deviceCheckBillDao.save(deviceCheckBill);
        });
    }

    /**
     * @param deviceCheckDetails DeviceCheckDetail异步上链
     */
    @Override
    @Async("taskScheduler")
    public void sendHash2(List<DeviceCheckDetail> deviceCheckDetails) {
        deviceCheckDetails.forEach(deviceCheckDetail -> {
            BcHash bcText = blockChainUtil.sendHash(1000, JacksonUtil.toJSon(deviceCheckDetail));
            String recordId = bcText.getData().getRecordID();
            deviceCheckDetail.setRecordId(recordId);
            detailRepo.save(deviceCheckDetail);
        });
    }

    /**
     * @param deviceCheckStats DeviceCheckStat异步上链
     */
    @Override
    @Async("taskScheduler")
    public void sendHash3(List<DeviceCheckStat> deviceCheckStats) {
        deviceCheckStats.forEach(deviceCheckStat -> {
            BcHash bcText = blockChainUtil.sendHash(1000, JacksonUtil.toJSon(deviceCheckStat));
            String recordId = bcText.getData().getRecordID();
            deviceCheckStat.setRecordId(recordId);
            statDao.save(deviceCheckStat);
        });
    }

    /**
     * 关键字过滤器
     *
     * @param vo      要过滤的对象
     * @param keyword 匹配的关键字
     * @return 该对象是否通过过滤器
     */
    private boolean keywordFilter(CheckStatTableVo vo, String keyword) {
        if (StringUtils.isEmpty(keyword)) {
            return true;
        } else {
            return vo.getQueryField().contains(keyword);
        }
    }

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

    /**
     * 项目开启时启动
     */
    @Override
    public void run(String... args) throws Exception {
        //开启计划任务
//        startAutoCheckCron();
//        log.info("[核查模块] 初始化开启任务成功");

    }
}
