package io.github.liuht777.scheduler;

import io.github.liuht777.scheduler.constant.DefaultConstants;
import io.github.liuht777.scheduler.core.IScheduleTask;
import io.github.liuht777.scheduler.core.ISchedulerServer;
import io.github.liuht777.scheduler.core.ScheduleServer;
import io.github.liuht777.scheduler.core.ScheduledMethodRunnable;
import io.github.liuht777.scheduler.core.Task;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;

import java.lang.reflect.Method;
import java.util.Date;
import java.util.concurrent.ScheduledFuture;


/**
 * 定时任务生成器
 * 继承 {@link ThreadPoolTaskScheduler}, Spring Task 定时任务的默认实现
 *
 * @author liuht
 */
@Slf4j
public class ThreadPoolTaskGenerator extends ThreadPoolTaskScheduler implements ApplicationContextAware {

    private static final long serialVersionUID = 8048640374020873814L;

    private ApplicationContext applicationcontext;

    private IScheduleTask scheduleTask;

    private ISchedulerServer schedulerServer;

    public ThreadPoolTaskGenerator(int poolsize,
                                   IScheduleTask scheduleTask,
                                   ISchedulerServer schedulerServer) {
        this.setPoolSize(poolsize);
        this.scheduleTask = scheduleTask;
        this.schedulerServer = schedulerServer;
    }

    /**
     * task runnable封装
     *
     * @param runnable
     * @return
     */
    private Runnable taskWrapper(final Runnable runnable) {
        return () -> {
            Task task = resolveTaskName(runnable);
            String name = task.stringKey();
            if (StringUtils.isNotEmpty(name)) {
                boolean isOwner;
                boolean isRunning;
                try {
                    isOwner = schedulerServer.isOwner(name, ScheduleServer.getInstance().getUuid());
                    isRunning = scheduleTask.isRunning(name);
                    if (isOwner && isRunning) {
                        String errorMsg = null;
                        try {
                            runnable.run();
                            log.debug("任务[" + name + "] 成功触发!");
                        } catch (Exception e) {
                            errorMsg = e.getLocalizedMessage();
                        }
                        scheduleTask.saveRunningInfo(name, ScheduleServer.getInstance().getUuid(), errorMsg);
                    } else {
                        if (!isOwner) {
                            log.debug("任务[" + name + "] 触发失败, 不属于当前server[" + ScheduleServer.getInstance().getUuid() + "]");
                        }
                        if (!isRunning) {
                            log.debug("任务[" + name + "] 触发失败, 任务被暂停了");
                        }
                    }
                } catch (Exception e) {
                    log.error("Check task owner error.", e);
                }
            }
        };
    }

    /**
     * 根据Runnable 返回task相关信息
     *
     * @param runnable Runnable
     * @return Task
     */
    private Task resolveTaskName(final Runnable runnable) {
        Method targetMethod;
        Task task = new Task();
        if (runnable instanceof ScheduledMethodRunnable) {
            ScheduledMethodRunnable scheduledMethodRunnable = (ScheduledMethodRunnable) runnable;
            targetMethod = scheduledMethodRunnable.getMethod();
            task.setType(DefaultConstants.TYPE_TAROCO_TASK);
            if (StringUtils.isNotBlank(scheduledMethodRunnable.getExtKeySuffix())) {
                task.setExtKeySuffix(scheduledMethodRunnable.getExtKeySuffix());
            }
            if (StringUtils.isNotEmpty(scheduledMethodRunnable.getParams())) {
                task.setParams(scheduledMethodRunnable.getParams());
            }
        } else {
            org.springframework.scheduling.support.ScheduledMethodRunnable springScheduledMethodRunnable = (org.springframework.scheduling.support.ScheduledMethodRunnable) runnable;
            targetMethod = springScheduledMethodRunnable.getMethod();
            task.setType(DefaultConstants.TYPE_SPRING_TASK);
        }
        String[] beanNames = applicationcontext.getBeanNamesForType(targetMethod.getDeclaringClass());
        if (StringUtils.isNotEmpty(beanNames[0])) {
            task.setTargetBean(beanNames[0]);
            task.setTargetMethod(targetMethod.getName());
        }
        return task;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationcontext) throws BeansException {
        this.applicationcontext = applicationcontext;
    }

    @Override
    public ScheduledFuture<?> scheduleAtFixedRate(Runnable runnable, long period) {
        ScheduledFuture scheduledFuture = null;
        try {
            Task task = resolveTaskName(runnable);
            if (task.getType().equals(DefaultConstants.TYPE_SPRING_TASK)) {
                // Spring 本地任务需要先添加到集群管理, 由集群统一分配后再执行
                if (!scheduleTask.isExistsTask(task)) {
                    task.setStartTime(new Date(System.currentTimeMillis()));
                    task.setPeriod(period);
                    scheduleTask.addTask(task);
                }
            } else {
                // 动态任务直接执行
                scheduledFuture = super.scheduleAtFixedRate(taskWrapper(runnable), period);
                log.info("添加 Taroco 动态任务[" + task.stringKey() + "]");
            }

        } catch (Exception e) {
            log.error("update task error", e);
        }
        return scheduledFuture;
    }

    @Override
    public ScheduledFuture<?> schedule(Runnable runnable, Trigger trigger) {
        ScheduledFuture scheduledFuture = null;
        try {
            Task task = resolveTaskName(runnable);
            if (task.getType().equals(DefaultConstants.TYPE_SPRING_TASK)) {
                // Spring 本地任务需要先添加到集群管理, 由集群统一分配后再执行
                if (!scheduleTask.isExistsTask(task)) {
                    task.setStartTime(new Date(System.currentTimeMillis()));
                    String cronEx = trigger.toString();
                    int index = cronEx.indexOf(":");
                    if (index >= 0) {
                        cronEx = cronEx.substring(index + 1);
                        task.setCronExpression(cronEx.trim());
                    }
                    scheduleTask.addTask(task);
                }
            } else {
                // 动态任务直接执行
                scheduledFuture = super.schedule(taskWrapper(runnable), trigger);
                log.info("添加 Taroco 动态任务[" + task.stringKey() + "]");
            }
        } catch (Exception e) {
            log.error("update task error", e);
        }
        return scheduledFuture;
    }

    @Override
    public ScheduledFuture<?> schedule(Runnable runnable, Date startTime) {
        ScheduledFuture scheduledFuture = null;
        try {
            Task task = resolveTaskName(runnable);
            if (task.getType().equals(DefaultConstants.TYPE_SPRING_TASK)) {
                // Spring 本地任务需要先添加到集群管理, 由集群统一分配后再执行
                if (!scheduleTask.isExistsTask(task)) {
                    task.setStartTime(startTime);
                    scheduleTask.addTask(task);
                }
            } else {
                // 动态任务直接执行
                scheduledFuture = super.schedule(taskWrapper(runnable), startTime);
                log.info("添加 Taroco 动态任务[" + task.stringKey() + "]");
            }
        } catch (Exception e) {
            log.error("update task error", e);
        }
        return scheduledFuture;
    }

    @Override
    public ScheduledFuture<?> scheduleAtFixedRate(Runnable runnable, Date startTime, long period) {
        ScheduledFuture scheduledFuture = null;
        try {
            Task task = resolveTaskName(runnable);
            if (task.getType().equals(DefaultConstants.TYPE_SPRING_TASK)) {
                // Spring 本地任务需要先添加到集群管理, 由集群统一分配后再执行
                if (!scheduleTask.isExistsTask(task)) {
                    task.setStartTime(startTime);
                    task.setPeriod(period);
                    scheduleTask.addTask(task);
                }
            } else {
                // 动态任务直接执行
                scheduledFuture = super.scheduleAtFixedRate(taskWrapper(runnable), startTime, period);
                log.info("添加 Taroco 动态任务[" + task.stringKey() + "]");
            }
        } catch (Exception e) {
            log.error("update task error", e);
        }
        return scheduledFuture;
    }

    @Override
    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable runnable, Date startTime, long delay) {
        ScheduledFuture scheduledFuture = null;
        try {
            Task task = resolveTaskName(runnable);
            if (task.getType().equals(DefaultConstants.TYPE_SPRING_TASK)) {
                // Spring 本地任务需要先添加到集群管理, 由集群统一分配后再执行
                if (!scheduleTask.isExistsTask(task)) {
                    task.setStartTime(startTime);
                    task.setPeriod(delay);
                    scheduleTask.addTask(task);
                }
            } else {
                // 动态任务直接执行
                scheduledFuture = super.scheduleWithFixedDelay(taskWrapper(runnable), startTime, delay);
                log.info("添加 Taroco 动态任务[" + task.stringKey() + "]");
            }
        } catch (Exception e) {
            log.error("update task error", e);
        }
        return scheduledFuture;
    }

    @Override
    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable runnable, long delay) {
        ScheduledFuture scheduledFuture = null;
        try {
            Task task = resolveTaskName(runnable);
            if (task.getType().equals(DefaultConstants.TYPE_SPRING_TASK)) {
                // Spring 本地任务需要先添加到集群管理, 由集群统一分配后再执行
                if (!scheduleTask.isExistsTask(task)) {
                    task.setStartTime(new Date(System.currentTimeMillis()));
                    task.setPeriod(delay);
                    scheduleTask.addTask(task);
                }
            } else {
                // 动态任务直接执行
                scheduledFuture = super.scheduleWithFixedDelay(taskWrapper(runnable), delay);
                log.info("添加 Taroco 动态任务[" + task.stringKey() + "]");
            }
        } catch (Exception e) {
            log.error("update task error", e);
        }
        return scheduledFuture;
    }

    public ISchedulerServer getSchedulerServer() {
        return schedulerServer;
    }

    public IScheduleTask getScheduleTask() {
        return scheduleTask;
    }
}