package com.njzscloud.common.sichen.scheduler;

import cn.hutool.core.thread.ThreadUtil;
import com.njzscloud.common.sichen.service.CombineStoreService;
import com.njzscloud.common.sichen.support.TaskInfo;
import com.njzscloud.common.sichen.util.TaskUtil;
import lombok.extern.slf4j.Slf4j;

import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

@Slf4j
public class SichenScheduler {
    private final Map<Long, List<TaskInfo>> todolist = new ConcurrentHashMap<>();
    private final CombineStoreService taskStore;
    private final Thread scanThread;
    private final Thread dripThread;

    /**
     * 任务扫描频率（毫秒）
     */
    private final int scanPeriod;
    /**
     * 补偿时间（秒），0-->忽略，等待下次调度、N-->N 秒内错过的任务将立即触发一次
     */
    private final int compensation;

    public SichenScheduler(CombineStoreService taskStore, int scanPeriod, int compensation, String poolName, boolean virtual) {
        log.info("任务调度器启动中...");
        this.taskStore = taskStore;
        this.scanPeriod = scanPeriod * 1000;
        this.compensation = compensation;
        scanThread = createWorkThread(virtual, poolName + "-任务扫描", this::scanTask);
        dripThread = createWorkThread(virtual, poolName + "-时间轮", this::drip);
        log.info("任务调度器已启动");
    }

    private Thread createWorkThread(boolean virtual, String threadName, Runnable task) {
        return virtual ? Thread.ofVirtual()
            .name(threadName)
            .start(task)
            : Thread.ofPlatform()
            .name(threadName)
            .daemon(true)
            .start(task);
    }

    public void stop() {
        log.info("正在停止任务调度器...");
        ThreadUtil.interrupt(scanThread, true);
        ThreadUtil.interrupt(dripThread, true);
        log.info("任务调度器已停止");
    }

    /**
     * 任务扫描
     */
    private void scanTask() {
        long cost = 0;
        while (true) {
            long timeout = cost == 0 ?
                (1000 - System.currentTimeMillis() % 1000) * 5 :
                scanPeriod - cost;
            if (timeout > 0) {
                try {
                    TimeUnit.MILLISECONDS.sleep(timeout);
                } catch (InterruptedException e) {
                    log.info("任务扫描线程被中断");
                    return;
                }
            }

            long now = System.currentTimeMillis();
            long soon = now + scanPeriod;

            try {
                // 从数据库或内存读取任务
                long soon_s = soon / 1000;
                List<TaskInfo> taskInfos = taskStore.loadTask(soon_s);
                if (taskInfos != null && !taskInfos.isEmpty()) {
                    schedule(now / 1000, soon_s, taskInfos);
                }
            } catch (Exception e) {
                log.error("任务调度失败", e);
            }

            cost = System.currentTimeMillis() - now;
        }
    }

    /**
     * 任务调度
     *
     * @param now       当前时间（秒）
     * @param soon      未来时间（秒）
     * @param taskInfos 任务列表
     */
    private void schedule(long now, long soon, List<TaskInfo> taskInfos) {
        try {
            List<TaskInfo> updateList = new LinkedList<>();
            for (TaskInfo taskInfo : taskInfos) {
                try {
                    long lastCriticalTiming = taskInfo.getCriticalTiming();
                    long delta = lastCriticalTiming - now;
                    if (delta == 0) {
                        // 已到时间，立即执行
                        TaskUtil.execute(taskInfo);
                    } else if (delta > 0) {
                        // 快到时间
                        todolist.compute(lastCriticalTiming % 60, (k, v) -> {
                            if (v == null) v = new LinkedList<>();
                            v.add(taskInfo);
                            return v;
                        });
                    } else {
                        // 错过的任务，仅执行相差 5 秒的任务，其他忽略等待下次调度
                        if (compensation > 0 && -delta <= compensation) TaskUtil.execute(taskInfo);
                        // 修正调度时间
                        lastCriticalTiming = now;
                    }
                    long nextCriticalTiming = lastCriticalTiming;
                    for (int i = 0; i < 5; i++) {
                        // 下次执行时间
                        lastCriticalTiming = TaskUtil.computedNextTiming(lastCriticalTiming, taskInfo);
                        // 计算失败或刚调度过或无需执行的任务，忽略
                        if (lastCriticalTiming == 0 || nextCriticalTiming == lastCriticalTiming) continue;
                        // 即将执行
                        if (lastCriticalTiming > now && lastCriticalTiming <= soon) {
                            todolist.compute(lastCriticalTiming % 60, (k, v) -> {
                                if (v == null) v = new LinkedList<>();
                                v.add(taskInfo);
                                return v;
                            });
                            nextCriticalTiming = lastCriticalTiming;
                        } else if (lastCriticalTiming > soon) {
                            // 未来执行
                            nextCriticalTiming = lastCriticalTiming;
                            break;
                        } else {
                            log.warn("下次执行时间计算失败，执行时间小于当前时间，任务：{}，当前时间：{}", taskInfo, now);
                            nextCriticalTiming = now;
                        }
                    }
                    // 更新下次触发时间
                    updateList.add(new TaskInfo(taskInfo).setCriticalTiming(nextCriticalTiming));
                } catch (Exception e) {
                    log.error("任务调度失败", e);
                }
            }
            for (TaskInfo taskInfo : updateList) {
                taskStore.updateCriticalTiming(taskInfo.isBuiltin(), taskInfo.getId(), taskInfo.getCriticalTiming());
            }
        } catch (Exception e) {
            log.error("任务调度失败", e);
        }
    }

    /**
     * 时间轮
     */
    private void drip() {
        while (true) {
            try {
                TimeUnit.MILLISECONDS.sleep(1000 - System.currentTimeMillis() % 1000);
            } catch (InterruptedException e) {
                log.info("计时器线程被中断");
                return;
            }

            long now = System.currentTimeMillis() / 1000;
            todolist.compute(now % 60, (k, v) -> {
                if (v == null || v.isEmpty()) return v;
                TaskUtil.execute(v);
                v.clear();
                return v;
            });
        }
    }
}
