package com.nokia.tsl_data.scheduling.service;

import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;

import javax.annotation.PreDestroy;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import com.nokia.tsl_data.scheduling.entity.RunnableTask;

import lombok.extern.slf4j.Slf4j;

/**
 * 马上调度或者延时调度
 */
@Slf4j
@Service
public class TaskScheduleService {
    /**
     * 存储任务调度信息
     */
    private ConcurrentHashMap<RunnableTask, ScheduledFuture<?>> tasksSchedulingMap = new ConcurrentHashMap<>();

    @Autowired
    private ThreadPoolTaskScheduler taskScheduler;

    @Autowired
    private ApplicationContext applicationContext;

    /**
     * 调度任务
     */
    public void schedule(RunnableTask task) {
        // 检查任务是否合法
        checkRunnableTask(task);
        // 检查是否已调度过
        if (tasksSchedulingMap.containsKey(task)) {
            ScheduledFuture<?> future = tasksSchedulingMap.get(task);
            if (!future.isDone()) {
                log.error("任务 {} 已调度但未完成，不要重复调度...", task);
                throw new RuntimeException(String.format("任务 %s 已调度但未完成，不要重复调度...", task));
            } else {
                log.debug("任务 {} 已完成，将重新调度...", task);
            }
        }
        // 调度任务
        Long delay = task.getDelay();
        if (delay != null) {
            // 延时调度
            tasksSchedulingMap.put(task, taskScheduler.schedule(task, Instant.now().plus(Duration.ofSeconds(delay))));
        } else {
            // 马上执行
            tasksSchedulingMap.put(task, taskScheduler.schedule(task, Instant.now()));
        }
    }

    /**
     * 删除任务
     */
    public void remove(RunnableTask task) {
        if (tasksSchedulingMap.contains(task)) {
            ScheduledFuture<?> future = tasksSchedulingMap.remove(task);
            if (future.isDone()) {
                log.debug("要删除的任务 {} 已完成...", task);
            } else {
                log.debug("要删除的任务 {} 尚未完成...", task);
                future.cancel(true);
            }
        } else {
            log.warn("尝试删除不存在的任务 {} ...", task);
        }
    }

    /**
     * 获取正在调度的任务
     */
    public List<RunnableTask> listTasksScheduling() {
        tasksSchedulingMap.forEach((task, future) -> {
            if (future.isDone()) {
                log.debug(" {} 任务已完成...", task);
                tasksSchedulingMap.remove(task);
            }
        });
        return new ArrayList<>(tasksSchedulingMap.keySet());
    }

    /**
     * 周期更新任务状态 每10分钟更新任务状态
     */
    @Scheduled(cron = "0 0/10 * * * *")
    public void cronUpdate() {
        log.debug("开始调度周期更新任务状态...");
        tasksSchedulingMap.forEach((task, future) -> {
            if (future.isDone()) {
                log.debug(" {} 任务已完成...", task);
                tasksSchedulingMap.remove(task);
            }
        });
        log.debug("周期更新任务状态已完成...");
    }

    /**
     * 在退出时结束全部任务
     * 这里不做记录，也就是说不支持重新加载未完成的任务
     */
    @PreDestroy
    public void cancelAll() {
        tasksSchedulingMap.forEach((task, future) -> {
            if (future.isDone()) {
                log.debug("退出时任务 {} 已完成...", task);
            } else {
                log.warn("退出时任务 {} 尚未完成...", task);
                future.cancel(true);
            }
        });
    }

    /**
     * 检查任务
     */
    public void checkRunnableTask(RunnableTask task) {
        try {
            Object bean = applicationContext.getBean(task.getBeanName());
            if (bean == null) {
                throw new RuntimeException(String.format("%s 不存在...", task.getBeanName()));
            }
            if (!StringUtils.hasLength(task.getMethodParameter())) {
                bean.getClass().getMethod(task.getMethodName());
            } else {
                bean.getClass().getMethod(task.getMethodName(), String.class);
            }
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(String.format("未找到 %s.%s() ...", task.getBeanName(), task.getMethodName()));
        } catch (SecurityException e) {
            throw new RuntimeException(
                    String.format("%s.%s() 出错: %s", task.getBeanName(), task.getMethodName(), e.getMessage()));
        }
    }
}
