package com.njzscloud.common.sichen.config;

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.njzscloud.common.core.thread.ThreadPool;
import com.njzscloud.common.sichen.contant.ScheduleType;
import com.njzscloud.common.sichen.contant.TaskLogLevel;
import com.njzscloud.common.sichen.controller.TaskController;
import com.njzscloud.common.sichen.controller.TaskExecuteLogController;
import com.njzscloud.common.sichen.controller.TaskScheduleRecodeController;
import com.njzscloud.common.sichen.executor.SichenExecutor;
import com.njzscloud.common.sichen.scheduler.SichenScheduler;
import com.njzscloud.common.sichen.service.*;
import com.njzscloud.common.sichen.support.Task;
import com.njzscloud.common.sichen.support.TaskInfo;
import com.njzscloud.common.sichen.support.TaskStore;
import lombok.extern.slf4j.Slf4j;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.event.EventListener;

import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.concurrent.ThreadPoolExecutor;

@Slf4j
@Configuration
@DependsOn("dataSource")
@MapperScan("com.njzscloud.common.sichen.mapper")
@ConditionalOnBooleanProperty(prefix = "sichen.task", name = "enable")
@EnableConfigurationProperties(TaskProperties.class)
public class TaskAutoConfiguration {

    @Bean("dbTaskService")
    @ConditionalOnBooleanProperty(prefix = "sichen.task", name = "enable-database", matchIfMissing = true)
    public DbTaskService dbTaskService() {
        return new DbTaskService();
    }

    @Bean("memoryTaskStoreService")
    public MemoryTaskStoreService memoryTaskStoreService() {
        return new MemoryTaskStoreService();
    }

    @Bean("combineStoreService")
    public CombineStoreService combineStoreService(MemoryTaskStoreService memoryTaskStoreService, @Autowired(required = false) DbTaskService dbTaskService) {
        return new CombineStoreService(memoryTaskStoreService, dbTaskService);
    }

    @Bean
    public TaskController taskController(CombineStoreService combineStoreService) {
        return new TaskController(combineStoreService);
    }

    @Bean
    public TaskScheduleRecodeController taskScheduleRecodeController(TaskScheduleRecodeService taskScheduleRecodeService) {
        return new TaskScheduleRecodeController(taskScheduleRecodeService);
    }

    @Bean
    public TaskExecuteLogController taskExecuteLogController(TaskExecuteLogService taskExecuteLogService) {
        return new TaskExecuteLogController(taskExecuteLogService);
    }

    @Bean
    public TaskExecuteLogService taskExecuteLogService() {
        return new TaskExecuteLogService();
    }

    @Bean(destroyMethod = "stop")
    public TaskScheduleRecodeService taskScheduleRecodeService(TaskProperties taskProperties, TaskExecuteLogService taskExecuteLogService) {
        return new TaskScheduleRecodeService(taskProperties, taskExecuteLogService);
    }

    @Bean(destroyMethod = "stop")
    public SichenExecutor sichenExecutor(TaskProperties taskProperties, TaskScheduleRecodeService taskScheduleRecodeService) {
        TaskProperties.Executor executor = taskProperties.getExecutor();
        ThreadPoolExecutor threadPool;
        if (executor.isVirtual()) {
            threadPool = ThreadPool.createVirtualThreadPool(executor.getPoolName(), null);
        } else {
            threadPool = ThreadPool.createPlatformThreadPool(executor.getPoolName(),
                executor.getCorePoolSize(), executor.getMaxPoolSize(),
                executor.getKeepAliveSeconds(), executor.getWindowCapacity(), executor.getStandbyCapacity(), null);
        }
        return new SichenExecutor(threadPool, taskScheduleRecodeService);
    }

    @Bean(destroyMethod = "stop")
    public SichenScheduler sichenScheduler(CombineStoreService combineStoreService, TaskProperties taskProperties) {
        TaskProperties.Scheduler scheduler = taskProperties.getScheduler();
        return new SichenScheduler(combineStoreService, scheduler.getScanPeriod(), scheduler.getCompensation(), scheduler.getPoolName(), scheduler.isVirtual());
    }

    @EventListener(ApplicationReadyEvent.class)
    public void onApplicationReadyEvent() {
        ApplicationContext applicationContext = SpringUtil.getApplicationContext();
        String[] beanNames = applicationContext.getBeanDefinitionNames();
        CombineStoreService combineStoreService = applicationContext.getBean(CombineStoreService.class);
        for (String beanName : beanNames) {
            Object bean = applicationContext.getBean(beanName);
            process(bean, combineStoreService);
        }
    }

    public void process(Object bean, CombineStoreService combineStoreService) throws BeansException {
        Class<?> clazz = bean.getClass();
        Method[] methods;
        String className;
        boolean proxy = AopUtils.isCglibProxy(bean);
        if (proxy) {
            Class<?> parentClazz = clazz.getSuperclass();
            methods = parentClazz.getMethods();
            className = parentClazz.getCanonicalName();
        } else {
            methods = clazz.getMethods();
            className = clazz.getCanonicalName();
        }
        HashSet<Long> ids = new HashSet<>();
        for (Method method : methods) {
            Task task = method.getAnnotation(Task.class);
            if (task != null) {
                String methodName = method.getName();

                if (proxy) {
                    try {
                        method = clazz.getMethod(methodName, method.getParameterTypes());
                    } catch (NoSuchMethodException e) {
                        continue;
                    }
                }

                String fn = className + "#" + methodName;

                String name = task.name();
                name = StrUtil.isBlank(name) ? fn : name;

                TaskStore.registerFn(fn, bean, method);

                int period = task.period();
                String cron = task.cron();
                if (StrUtil.isNotBlank(cron) || period > 0) {
                    String scheduleConf = StrUtil.isNotBlank(cron) ? cron : String.valueOf(period);
                    ScheduleType scheduleType = StrUtil.isNotBlank(cron) ? ScheduleType.Cron : ScheduleType.Fixed;
                    String memo = task.memo();
                    TaskLogLevel taskLogLevel = task.logLevel();
                    long id = task.id();
                    if (id <= 0) {
                        log.warn("任务 Id 应大于 0，现在将使用默认生成的 Id，任务名称：{}", name);
                        id = IdUtil.getSnowflakeNextId();
                    }
                    if (ids.contains(id)) {
                        log.warn("任务 Id 重复，已跳过");
                        continue;
                    } else {
                        ids.add(id);
                    }
                    combineStoreService.memoryTaskStoreService.registerTask(new TaskInfo()
                        .setId(id)
                        .setTaskName(name)
                        .setMemo(memo)
                        .setFn(fn)
                        .setScheduleType(scheduleType)
                        .setScheduleConf(scheduleConf)
                        .setLogLevel(taskLogLevel));
                    log.info("已注册任务：任务名：{}，Id：{}", name, id);
                }
            }
        }
    }
}
