package com.docus.server.common;

import com.docus.core.util.DateUtil;
import com.docus.core.util.Func;
import com.docus.core.util.json.JSON;
import com.docus.infrastructure.redis.service.RedisOps;
import com.docus.server.api.taskdistribute.TaskDistributeApi;
import com.docus.server.common.netty.server.TerminalToChannelCacheMap;
import com.docus.server.dto.scheduling.management.schcollector.task.ReportDownTwoDTO;
import com.docus.server.dto.scheduling.management.schcollector.task.ReportTaskTwoDTO;
import com.docus.server.dto.scheduling.management.schcollector.task.SchCollectorTaskDTO;
import com.docus.server.dto.scheduling.management.schterminator.CommMsgDTO;
import com.docus.server.dto.scheduling.management.schterminator.NettyTerminatorDTO;
import com.docus.server.entity.scheduling.management.SchCollectRecord;
import com.docus.server.entity.scheduling.management.SchCollectRecordRetryLog;
import com.docus.server.entity.scheduling.management.SchCollector;
import com.docus.server.enums.BusyStateEnum;
import com.docus.server.enums.RetryTaskEnum;
import com.docus.server.service.ICommMsgService;
import com.docus.server.service.ISchCollectRecordRetryLogService;
import com.docus.server.service.ISchCollectRecordService;
import com.docus.server.service.ISchCollectorService;
import com.docus.server.service.ISchTerminatorService;
import com.google.common.collect.Lists;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

@Component
@Slf4j
public class SchCollectorTask {
    @Resource
    private TerminalToChannelCacheMap channelRepository;
    @Resource
    private TaskDistributeApi taskDistributeApi;
    @Resource
    private ISchCollectRecordService iSchCollectRecordService;
    @Resource
    private ISchCollectRecordRetryLogService iSchCollectRecordRetryLogService;
    @Resource
    private ISchTerminatorService iSchTerminatorService;
    @Resource
    private ISchCollectorService iSchCollectorService;
    @Resource
    private RedisOps redisOps;
    @Resource
    private ICommMsgService iCommMsgService;

    private BlockingQueue<ReportDownTwoDTO> retryTaskQueue = new LinkedBlockingQueue<>();

    //定时任务
    // 5 * * * * ? 在每分钟的5秒执行
    @Scheduled(cron = "0/1 * * * * ?")
    public void runTask() {
        try {
            log.info("定时任务： 开始执行");

            //在线并且空闲的终端
            List<NettyTerminatorDTO> terminalList = channelRepository.getAvailTerminator();

            //没有可用的通道
            if (Func.isEmpty(terminalList)) {
                return;
            }

            //:todo 任务平台需要修改发布任务策略
            List<ReportDownTwoDTO> reportDownTwoDTOList = getTask(terminalList.size());

            if (Func.isEmpty(reportDownTwoDTOList) || Func.isBlank(reportDownTwoDTOList.get(0).getPatientId())) {
                return;
            }

            //只采集，有优先级的
            for (NettyTerminatorDTO terminal : terminalList) {
                for (ReportDownTwoDTO report : reportDownTwoDTOList) {
                    //先找出有只采集的任务。
                    ReportTaskTwoDTO reportTaskTwoDto = report.getTasks().get(0);
                    if (!CollectionUtils.isEmpty(terminal.getOnlyCollectorIds()) && terminal.getOnlyCollectorIds().contains(reportTaskTwoDto.getCollectorId())
                        && BusyStateEnum.IDLE.equals(terminal.getBusyState())) {
                        //把这个任务派给这个终端,并且把这个终端设置成繁忙
                        if (!CollectionUtils.isEmpty(terminal.getPriorityCollectorIds()) && terminal.getPriorityCollectorIds().contains(reportTaskTwoDto.getCollectorId())) {
                            //把这个任务派给这个终端
                            terminal.setBusyState(BusyStateEnum.BUSY);
                            iSchTerminatorService.saveOrUpdate(terminal.getTerminatorIp(), terminal);

                            tcpToClient(terminal, report);
                        }
                        return;
                    }
                }
            }

            //只采集没有优先级
            for (NettyTerminatorDTO terminal : terminalList) {
                //把刚才已经分配任务过的采集器排除
                if (BusyStateEnum.BUSY.equals(terminal.getBusyState())) {
                    continue;
                }

                for (ReportDownTwoDTO report : reportDownTwoDTOList) {
                    //先找出有只采集的任务。
                    ReportTaskTwoDTO reportTaskTwoDto = report.getTasks().get(0);
                    if (!CollectionUtils.isEmpty(terminal.getOnlyCollectorIds()) && terminal.getOnlyCollectorIds().contains(reportTaskTwoDto.getCollectorId())
                        && BusyStateEnum.IDLE.equals(terminal.getBusyState())) {
                        //把这个任务派给这个终端,并且把这个终端设置成繁忙
                        terminal.setBusyState(BusyStateEnum.BUSY);
                        iSchTerminatorService.saveOrUpdate(terminal.getTerminatorIp(), terminal);

                        tcpToClient(terminal, report);
                        return;
                    }
                }
            }


            //无只采集，有优先级
            for (NettyTerminatorDTO terminal : terminalList) {

                //把刚才已经分配任务过的采集器排除
                if (BusyStateEnum.BUSY.equals(terminal.getBusyState())) {
                    continue;
                }

                for (ReportDownTwoDTO report : reportDownTwoDTOList) {
                    //先找出有只采集的任务。
                    ReportTaskTwoDTO reportTaskTwoDto = report.getTasks().get(0);
                    if (!CollectionUtils.isEmpty(terminal.getPriorityCollectorIds()) && terminal.getPriorityCollectorIds().contains(reportTaskTwoDto.getCollectorId())
                        && BusyStateEnum.IDLE.equals(terminal.getBusyState())) {
                        //把这个任务派给这个终端
                        terminal.setBusyState(BusyStateEnum.BUSY);
                        iSchTerminatorService.saveOrUpdate(terminal.getTerminatorIp(), terminal);

                        tcpToClient(terminal, report);
                        return;
                    }
                }
            }


            //无只采集，无优先级
            for (NettyTerminatorDTO terminal : terminalList) {

                //把刚才已经分配任务过的采集器排除
                if (BusyStateEnum.BUSY.equals(terminal.getBusyState())) {
                    continue;
                }

                for (ReportDownTwoDTO report : reportDownTwoDTOList) {
                    //先找出有只采集的任务。
                    //把这个任务派给这个终端
                    ReportTaskTwoDTO reportTaskTwoDto = report.getTasks().get(0);
                    terminal.setBusyState(BusyStateEnum.BUSY);
                    iSchTerminatorService.saveOrUpdate(terminal.getTerminatorIp(), terminal);

                    tcpToClient(terminal, report);
                    return;
                }
            }

            //只采集，空闲的。
            //获取只采集的任务，并且进行分配。
            for (NettyTerminatorDTO terminal : terminalList) {
                //把刚才已经分配任务过的采集器排除
                if (BusyStateEnum.BUSY.equals(terminal.getBusyState())) {
                    continue;
                }

                List<ReportDownTwoDTO> onlyTaskInfos = getOnlyTaskInfos(terminal.getOnlyCollectorIds());


                if (CollectionUtils.isEmpty(onlyTaskInfos) || Func.isBlank(onlyTaskInfos.get(0).getPatientId())) {
                    return;
                }

                for (ReportDownTwoDTO report : onlyTaskInfos) {
                    //将这条任务分配这个这个终端
                    //下发
                    terminal.setBusyState(BusyStateEnum.BUSY);
                    iSchTerminatorService.saveOrUpdate(terminal.getTerminatorIp(), terminal);

                    tcpToClient(terminal, report);

                    return;
                }
            }
            log.info("定时任务： 执行完毕");
        } catch (
            Exception e) {
            log.error("定时任务执行出错", e);
        }

    }

    private void tcpToClient(NettyTerminatorDTO terminal, ReportDownTwoDTO reportDownTwoDTO) {

//        hasValidCollector(terminal, reportDownTwoDTO);

        Channel channel = channelRepository.get(terminal.getTerminatorIp());

        Map<String, Object> params = reportDownTwoDTO.getParams();

        SchCollectorTaskDTO messageContent = new SchCollectorTaskDTO();

        if (CollectionUtils.isEmpty(params)) {
            //不是重试任务
            SchCollectRecord schCollectRecord = iSchCollectRecordService.saveOrUpdateRecord(terminal, reportDownTwoDTO);

            messageContent.setCollectorRecordId(schCollectRecord.getId());
            messageContent.setIsRetry(String.valueOf(RetryTaskEnum.NO_RETRY_TASK.getValue()));
            messageContent.setTaskInfo(reportDownTwoDTO);
        } else {
            //重试任务
            SchCollectRecordRetryLog schCollectRecordRetryLog = iSchCollectRecordRetryLogService.saveOrUpdateRecordRetryLog(terminal, reportDownTwoDTO);

            messageContent.setCollectorRecordId(schCollectRecordRetryLog.getId());
            messageContent.setIsRetry(String.valueOf(RetryTaskEnum.RETRY_TASK.getValue()));
            messageContent.setTaskInfo(reportDownTwoDTO);
        }

        CommMsg commMsg = CommMsg.builder()
            .messageType(MsgConstants.SCH_DISTRIBUTE_TASKS)
            .messageTime(DateUtil.formatDateTime(new Date()))
            .content(JSON.toJSON(messageContent))
            .build();

        //tcp 下发任务到终端
        if (channel != null) {
            System.out.println(JSON.toJSON(commMsg));
            channel.writeAndFlush(Unpooled.copiedBuffer(JSON.toJSON(commMsg), CharsetUtil.UTF_8));
        }

        String isRetry = messageContent.getIsRetry();
        Long collectorRecordId = messageContent.getCollectorRecordId();
        String collectorId = messageContent.getTaskInfo().getTasks().get(0).getCollectorId();
        SchCollector schCollector = iSchCollectorService.findByCollectorId(collectorId);

        if (RetryTaskEnum.NO_RETRY_TASK.equals(isRetry)) {
            redisOps.setEx(String.format("schCollectorRecord:noRetryTask:expireKey:%s", collectorRecordId), String.valueOf(collectorRecordId), schCollector.getTaskTimeout());
        } else {
            redisOps.setEx(String.format("schCollectorRecord:isRetryTask:expireKey:%s", collectorRecordId), String.valueOf(collectorRecordId), schCollector.getTaskTimeout());
        }


    }

    private void hasValidCollector(NettyTerminatorDTO terminal, ReportDownTwoDTO reportDownTwoDTO) {
        /**
         * 终端虚拟机配置了任务偏好，根据任务偏好计算出下发的任务，下发之前，先发一个命令给终端，让终端去找采集器进程和心跳，全部正常就下发这条任务，
         * 反之，没有找到，就回报给调度器，调度器下发下载指令给终端，终端收到命令，去下载部署采集器，并运行打开采集器进程，建立心跳，正常后，就回报调度器，调度器下发这条任务
         */

        //1、下发任务之前，询问终端是否有采集器进程和心跳
        //2、若没有，下发部署采集器指令给终端
        //3、若有，下发任务给终端
        String collectorId = reportDownTwoDTO.getTasks().get(0).getCollectorId();

        CommMsgDTO commMsgDTO = CommMsgDTO.builder()
            .terminatorIp(terminal.getTerminatorIp())
            .messageType(MsgConstants.HAS_VALID_COLLECTOR)
            .content(collectorId)
            .build();

        iCommMsgService.clientCommand(commMsgDTO);

    }


    public void addRetryTask(ReportDownTwoDTO reportDownTwoDTO) {
        this.retryTaskQueue.add(reportDownTwoDTO);
    }


    //根据有效终端，一次获取一批任务，例如10个终端，获取10个不同类型任务
    public List<ReportDownTwoDTO> getTask(int size) throws InterruptedException {
//        return taskDistributeApi.getTask(String.valueOf(size));

        //mock

        String collectorId1 = "{\n" +
            "  \"createTime\": \"2022-12-03 12:39:30\",\n" +
            "  \"hospitals\": [\n" +
            "    {\n" +
            "      \"admissDate\": \"2023-12-31 01:01:01\",\n" +
            "      \"admissId\": \"amid_999901\",\n" +
            "      \"admissTimes\": 1,\n" +
            "      \"disDate\": \"2023-12-31 01:01:01\",\n" +
            "      \"disDeptName\": \"22222\",\n" +
            "      \"patientId\": \"758878610105573376\"\n" +
            "    }\n" +
            "  ],\n" +
            "  \"jzh\": \"jzh_999901\",\n" +
            "  \"patient\": {\n" +
            "    \"inpatientNo\": \"999901\",\n" +
            "    \"name\": \"ceshi\",\n" +
            "    \"patientId\": \"758878610105573376\"\n" +
            "  },\n" +
            "  \"patientId\": \"758878610105573376\",\n" +
            "  \"recordType\": \"1\",\n" +
            "  \"tasks\": [\n" +
            "    {\n" +
            "      \"collectorId\": \"1\",\n" +
            "      \"patientId\": \"758878610105573376\",\n" +
            "      \"taskId\": 834292710565826560\n" +
            "    }\n" +
            "  ]\n" +
            "}";
        ReportDownTwoDTO reportDownTwoDTO1 = JSON.fromJSON(collectorId1, ReportDownTwoDTO.class);


        String collectorId2 = "{\n" +
            "  \"createTime\": \"2022-12-03 12:39:30\",\n" +
            "  \"hospitals\": [\n" +
            "    {\n" +
            "      \"admissDate\": \"2023-12-31 01:01:01\",\n" +
            "      \"admissId\": \"amid_999901\",\n" +
            "      \"admissTimes\": 1,\n" +
            "      \"disDate\": \"2023-12-31 01:01:01\",\n" +
            "      \"disDeptName\": \"22222\",\n" +
            "      \"patientId\": \"758878610105573376\"\n" +
            "    }\n" +
            "  ],\n" +
            "  \"jzh\": \"jzh_999901\",\n" +
            "  \"patient\": {\n" +
            "    \"inpatientNo\": \"999901\",\n" +
            "    \"name\": \"ceshi\",\n" +
            "    \"patientId\": \"758878610105573376\"\n" +
            "  },\n" +
            "  \"patientId\": \"758878610105573376\",\n" +
            "  \"recordType\": \"1\",\n" +
            "  \"tasks\": [\n" +
            "    {\n" +
            "      \"collectorId\": \"2\",\n" +
            "      \"patientId\": \"758878610105573376\",\n" +
            "      \"taskId\": 834292712465846272\n" +
            "    }\n" +
            "  ]\n" +
            "}";
        ReportDownTwoDTO reportDownTwoDTO2 = JSON.fromJSON(collectorId2, ReportDownTwoDTO.class);


        String collectorId3 = "{\n" +
            "  \"createTime\": \"2023-01-09 19:26:11\",\n" +
            "  \"hospitals\": [\n" +
            "    {\n" +
            "      \"admissDate\": \"2023-12-31 01:01:01\",\n" +
            "      \"admissId\": \"amid_297974\",\n" +
            "      \"admissTimes\": 21,\n" +
            "      \"disDate\": \"2023-12-31 01:01:01\",\n" +
            "      \"disDeptName\": \"普外二科(甲乳胸烧伤整形)\",\n" +
            "      \"patientId\": \"772389719349678080\"\n" +
            "    }\n" +
            "  ],\n" +
            "  \"jzh\": \"jzh_297974\",\n" +
            "  \"patient\": {\n" +
            "    \"inpatientNo\": \"297974\",\n" +
            "    \"name\": \"曾美英\",\n" +
            "    \"patientId\": \"772389719349678080\"\n" +
            "  },\n" +
            "  \"patientId\": \"772389719349678080\",\n" +
            "  \"recordType\": \"1\",\n" +
            "  \"tasks\": [\n" +
            "    {\n" +
            "      \"collectorId\": \"3\",\n" +
            "      \"patientId\": \"772389719349678080\",\n" +
            "      \"taskId\": 838201379426750464\n" +
            "    }\n" +
            "  ]\n" +
            "}";
        ReportDownTwoDTO reportDownTwoDTO3 = JSON.fromJSON(collectorId3, ReportDownTwoDTO.class);

        List<ReportDownTwoDTO> allTaskList = Lists.newArrayList(reportDownTwoDTO1, reportDownTwoDTO2, reportDownTwoDTO3);
//        List<ReportDownTwoDTO> allTaskList = new ArrayList<>();

        if (!CollectionUtils.isEmpty(this.retryTaskQueue)) {
            ReportDownTwoDTO retryTask = (ReportDownTwoDTO) this.retryTaskQueue.take();
            //重试任务
            allTaskList.add(retryTask);
        }

        return allTaskList;

    }

    //根据采集器id类型，一次获取一批采集器类型任务
    private List<ReportDownTwoDTO> getOnlyTaskInfos(List<String> collectorIds) {
//        return taskDistributeApi.getTask(collectorIds.get(0));

        //mock


        String collectorId2 = "{\n" +
            "  \"createTime\": \"2022-12-03 12:39:30\",\n" +
            "  \"hospitals\": [\n" +
            "    {\n" +
            "      \"admissDate\": \"2023-12-31 01:01:01\",\n" +
            "      \"admissId\": \"amid_999901\",\n" +
            "      \"admissTimes\": 1,\n" +
            "      \"disDate\": \"2023-12-31 01:01:01\",\n" +
            "      \"disDeptName\": \"22222\",\n" +
            "      \"patientId\": \"758878610105573376\"\n" +
            "    }\n" +
            "  ],\n" +
            "  \"jzh\": \"jzh_999901\",\n" +
            "  \"patient\": {\n" +
            "    \"inpatientNo\": \"999901\",\n" +
            "    \"name\": \"ceshi\",\n" +
            "    \"patientId\": \"758878610105573376\"\n" +
            "  },\n" +
            "  \"patientId\": \"758878610105573376\",\n" +
            "  \"recordType\": \"1\",\n" +
            "  \"tasks\": [\n" +
            "    {\n" +
            "      \"collectorId\": \"2\",\n" +
            "      \"patientId\": \"758878610105573376\",\n" +
            "      \"taskId\": 834292712465846272\n" +
            "    }\n" +
            "  ]\n" +
            "}";
        ReportDownTwoDTO reportDownTwoDTO2 = JSON.fromJSON(collectorId2, ReportDownTwoDTO.class);


        String collectorId3 = "{\n" +
            "  \"createTime\": \"2022-12-03 12:39:30\",\n" +
            "  \"hospitals\": [\n" +
            "    {\n" +
            "      \"admissDate\": \"2023-12-31 01:01:01\",\n" +
            "      \"admissId\": \"amid_999901\",\n" +
            "      \"admissTimes\": 1,\n" +
            "      \"disDate\": \"2023-12-31 01:01:01\",\n" +
            "      \"disDeptName\": \"22222\",\n" +
            "      \"patientId\": \"758878610105573376\"\n" +
            "    }\n" +
            "  ],\n" +
            "  \"jzh\": \"jzh_999901\",\n" +
            "  \"patient\": {\n" +
            "    \"inpatientNo\": \"999901\",\n" +
            "    \"name\": \"ceshi\",\n" +
            "    \"patientId\": \"758878610105573376\"\n" +
            "  },\n" +
            "  \"patientId\": \"758878610105573376\",\n" +
            "  \"recordType\": \"1\",\n" +
            "  \"tasks\": [\n" +
            "    {\n" +
            "      \"collectorId\": \"3\",\n" +
            "      \"patientId\": \"758878610105573376\",\n" +
            "      \"taskId\": 834292883635392512\n" +
            "    }\n" +
            "  ]\n" +
            "}";
        ReportDownTwoDTO reportDownTwoDTO3 = JSON.fromJSON(collectorId3, ReportDownTwoDTO.class);

        return Lists.newArrayList(reportDownTwoDTO2, reportDownTwoDTO3);
    }
}

