package org.wcs.quartzJob;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.wcs.business.stacker.StackerStatusMonitor;
import org.wcs.business.stacker.intf.IExecuteStackerTaskService;
import org.wcs.business.stock.intf.IStockSingleTaskExecuteEvent;
import org.wcs.constant.enums.common.TrueOrFalseEnum;
import org.wcs.constant.enums.plc.StackerControllerModelEnum;
import org.wcs.constant.enums.plc.StackerDeviceStatusEnum;
import org.wcs.constant.enums.plc.StackerTaskTypeEnum;
import org.wcs.mapper.intf.AppStackerInfoService;
import org.wcs.mapper.intf.AppStockSingleTaskService;
import org.wcs.model.bo.tuple.Tuple2;
import org.wcs.model.po.app.AppStackerInfo;
import org.wcs.model.po.app.AppStockSingleTask;
import org.wcs.plugin.plc.PlcCommunicationFactory;
import org.wcs.plugin.plc.constant.PlcStackerTaskStatusEnum;
import org.wcs.plugin.plc.model.PlcStackerTaskStatus;
import org.wcs.plugin.plc.model.StackerStatus;
import org.wcs.utils.AppStringUtils;
import org.wcs.utils.AppThreadUtils;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 堆垛机定时任务
 */
@Slf4j
@DisallowConcurrentExecution
@RequiredArgsConstructor
public class StackerJob implements Job {

    private final IExecuteStackerTaskService executeStackerTaskService; // 堆垛机任务执行
    private final AppStackerInfoService stackerInfoDao; // 堆垛机基础信息表操作类
    private final AppStockSingleTaskService stockSingleTaskService;
    private final IStockSingleTaskExecuteEvent stockSingleTaskEvent;
    private final StackerStatusMonitor stackerStatusMonitor; // 堆垛机状态监控

    private static Map<Integer, Integer> nextStackerTaskTypeIndexMap = new ConcurrentHashMap<>(); // 堆垛机下一个执行的任务类型的索引
    private static Map<Integer, LocalDateTime> nextStackerTaskTimeMap = new ConcurrentHashMap<>(); // 堆垛机下一次执行任务时间


    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        List<AppStackerInfo> stackerInfos = stackerInfoDao.queryStackerInfoByStatus(TrueOrFalseEnum.TRUE.getCode());
        if(stackerInfos == null || stackerInfos.isEmpty()) {
            return;
        }
        executeStackerTask(stackerInfos); // 执行堆垛机任务
        checkCount(stackerInfos); // 过账
    }

    /**
     * 执行堆垛机任务
     * @param stackerInfos 堆垛机列表
     */
    private void executeStackerTask(List<AppStackerInfo> stackerInfos) {
        List<Runnable> runnableList = new ArrayList<>();
        for (AppStackerInfo stackerInfo : stackerInfos) {
            Tuple2<String, StackerStatus> stackerStatusResult = PlcCommunicationFactory.getPlcCommunicationByPlcId(stackerInfo.getPlcId()).readStackerStatus(stackerInfo.getStackerId());
            if(AppStringUtils.isNotEmpty(stackerStatusResult.item1) || stackerStatusResult.item2 == null) {
                continue; // 读取堆垛机状态失败
            }
            stackerStatusMonitor.setStackerStatus(stackerInfo, stackerStatusResult.item2);
            if(stackerInfo.getStackerId() == null || !isStackerTaskTime(stackerInfo.getStackerId())) {
                continue; // 堆垛机任务时间未到
            }
            StackerStatus stackerStatus = stackerStatusResult.item2;
            if(StackerControllerModelEnum.AUTO.getCode().compareTo(stackerStatus.getControlModel().intValue()) != 0 || stackerStatus.getErrCode().compareTo((short) 0) != 0 ) {
                continue; // 堆垛机不联机或者正在报警
            }
            if(StackerDeviceStatusEnum.FREE.getCode().compareTo(stackerStatus.getDeviceStatus().intValue()) == 0) { // 堆垛机空闲
                StackerTaskTypeEnum nextTaskType = getNextTaskType(stackerInfo.getStackerId(), stackerInfo.getActionBeat());
                if(nextTaskType == null) {
                    continue; // 没有获取到堆垛机下一个执行任务类型
                }
                runnableList.add(() -> {
                    boolean isExecute = switch (nextTaskType) {
                        case IN -> executeStackerTaskService.executeStackerInTask(stackerInfo);
                        case OUT -> executeStackerTaskService.executeStackerOutTask(stackerInfo);
                        case MOVE -> executeStackerTaskService.executeStackerMoveTask(stackerInfo);
                        default -> false;
                    };
                    if(isExecute) {
                        nextStackerTaskTimeMap.put(stackerInfo.getStackerId(), LocalDateTime.now().plusSeconds(10)); // 如果执行了任务就十秒钟之后再检查堆垛机
                    }
                });
            }
            // 待开发，重复入库的任务下发
        }
        if(runnableList.isEmpty()) {
            return;
        }
        AppThreadUtils.waitAllTaskComplete(runnableList);
    }

    /**
     * 堆垛机过账
     * @param stackerInfos 堆垛机列表
     */
    private void checkCount(List<AppStackerInfo> stackerInfos) {
        List<Runnable> runnableList = new ArrayList<>();
        for (AppStackerInfo stackerInfo : stackerInfos) {
            if(stackerInfo.getStackerId() == null) {
                continue;
            }
            runnableList.add(() -> {
                Map<Integer, PlcStackerTaskStatus> integerPlcStackerTaskStatusMap = PlcCommunicationFactory.getPlcCommunicationByPlcId(stackerInfo.getPlcId()).readStackerTaskStatus(stackerInfo.getStackerId());
                if(integerPlcStackerTaskStatusMap == null || integerPlcStackerTaskStatusMap.isEmpty()) {
                    return; // 读取堆垛机任务状态失败
                }
                integerPlcStackerTaskStatusMap.forEach((index, taskStatus) -> {
                    if(taskStatus == null || taskStatus.getTaskId() == 0) {
                        return;
                    }
                    if(taskStatus.getStackerTaskStatusEnum().equals(PlcStackerTaskStatusEnum.UNKNOWN)) {
                        log.info("堆垛机过账任务状态未知：堆垛机ID：{}，堆垛机任务ID：{}", stackerInfo.getStackerId(), taskStatus.getTaskId());
                        String clearResult = PlcCommunicationFactory.getPlcCommunicationByPlcId(stackerInfo.getPlcId()).clearStackerTaskStatus(stackerInfo.getStackerId(), index);
                        log.info("堆垛机清除过账任务状态结果：{}，堆垛机ID：{}，堆垛机任务ID：{}", clearResult, stackerInfo.getStackerId(), taskStatus.getTaskId());
                        return;
                    }
                    if(taskStatus.getStackerTaskStatusEnum().equals(PlcStackerTaskStatusEnum.COMPLETED)) {
                        // 任务完成
                        List<AppStockSingleTask> stockSingleTasks = stockSingleTaskService.queryByPlcTaskId(taskStatus.getTaskId());
                        if(stockSingleTasks == null) {
                            log.info("堆垛机任务完成，查询任务信息失败：堆垛机ID：{}，堆垛机任务ID：{}", stackerInfo.getStackerId(), taskStatus.getTaskId());
                            return;
                        }
                        if(stockSingleTasks.isEmpty()) {
                            log.info("堆垛机任务完成，查询任务信息为空：堆垛机ID：{}，堆垛机任务ID：{}", stackerInfo.getStackerId(), taskStatus.getTaskId());
                            return;
                        }
                        stockSingleTaskEvent.taskFinish(stockSingleTasks.getFirst(), "堆垛机上报完成");
                        String clearResult = PlcCommunicationFactory.getPlcCommunicationByPlcId(stackerInfo.getPlcId()).clearStackerTaskStatus(stackerInfo.getStackerId(), index);
                        log.info("堆垛机任务完成清除过账任务状态结果：{}，堆垛机ID：{}，堆垛机任务ID：{}", clearResult, stackerInfo.getStackerId(), taskStatus.getTaskId());
                        return;
                    }
                    if(taskStatus.getStackerTaskStatusEnum().equals(PlcStackerTaskStatusEnum.CANCELED)) {
                        // 任务取消
                        List<AppStockSingleTask> stockSingleTasks = stockSingleTaskService.queryByPlcTaskId(taskStatus.getTaskId());
                        if(stockSingleTasks == null) {
                            log.info("堆垛机任务取消，查询任务信息失败：堆垛机ID：{}，堆垛机任务ID：{}", stackerInfo.getStackerId(), taskStatus.getTaskId());
                            return;
                        }
                        if(stockSingleTasks.isEmpty()) {
                            log.info("堆垛机任务取消，查询任务信息为空：堆垛机ID：{}，堆垛机任务ID：{}", stackerInfo.getStackerId(), taskStatus.getTaskId());
                            return;
                        }
                        stockSingleTaskEvent.taskCancel(stockSingleTasks.getFirst(), "堆垛机上报取消");
                        String clearResult = PlcCommunicationFactory.getPlcCommunicationByPlcId(stackerInfo.getPlcId()).clearStackerTaskStatus(stackerInfo.getStackerId(), index);
                        log.info("堆垛机任务取消清除过账任务状态结果：{}，堆垛机ID：{}，堆垛机任务ID：{}", clearResult, stackerInfo.getStackerId(), taskStatus.getTaskId());
                    }
                    // 待完善，空出库和重复入库处理
                });
            });
        }
        if(runnableList.isEmpty()) {
            return;
        }
        AppThreadUtils.waitAllTaskComplete(runnableList);
    }

    /**
     * 获取堆垛机下一个执行任务类型
     * @param stackerId 堆垛机ID
     * @param taskBeat 堆垛机任务执行节拍
     * @return 下一个执行的任务类型
     */
    private StackerTaskTypeEnum getNextTaskType(Integer stackerId, String taskBeat) {
        if(AppStringUtils.isEmpty(taskBeat)) {
            return null;
        }
        Integer nexStackerTaskTypeIndex = nextStackerTaskTypeIndexMap.getOrDefault(stackerId, 0);
        if(nexStackerTaskTypeIndex >= taskBeat.length()) {
            nexStackerTaskTypeIndex = 0;
        }
        StackerTaskTypeEnum stackerTaskTypeEnum = StackerTaskTypeEnum.getByCode(AppStringUtils.toIntegerOrDefault(String.valueOf(taskBeat.charAt(nexStackerTaskTypeIndex)), 0));
        nexStackerTaskTypeIndex++;
        nextStackerTaskTypeIndexMap.put(stackerId, nexStackerTaskTypeIndex);
        return stackerTaskTypeEnum;
    }

    /**
     * 判断堆垛机下次可执行任务的时间
     * @param stackerId 堆垛机ID
     * @return 是否是堆垛机任务时间
     */
    private boolean isStackerTaskTime(Integer stackerId) {
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime nextStackerTaskTime = nextStackerTaskTimeMap.get(stackerId);
        if(nextStackerTaskTime == null) {
            return true;
        }
        return !now.isBefore(nextStackerTaskTime);
    }
}
