package com.easyagents.scheduler.quartz;

import com.easyagents.scheduler.ScheduleErrorCode;
import com.easyagents.scheduler.ScheduleException;
import com.easyagents.scheduler.ScheduleExecutionListener;
import com.easyagents.scheduler.ScheduleHandler;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.impl.SchedulerRepository;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.utils.DBConnectionManager;

import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Properties;

/**
 * 使用调用方 DataSource 创建独立 Quartz Scheduler 的工厂。
 */
public final class QuartzSchedulerFactory {

    /** 禁止实例化工厂。 */
    private QuartzSchedulerFactory() {
    }

    /**
     * 创建采用 Quartz JDBC JobStore 的调度服务。
     *
     * <p>返回的服务尚未启动，调用方应在应用生命周期就绪时调用
     * {@link QuartzScheduleService#start()}。</p>
     *
     * @param dataSource 调用方管理生命周期的 DataSource
     * @param config Quartz Scheduler 配置
     * @param handlers 当前节点注册的 Handler
     * @param listeners 当前节点注册的监听器
     * @return 尚未启动的调度服务
     * @throws ScheduleException 结构校验或 Scheduler 初始化失败时抛出
     */
    public static synchronized QuartzScheduleService createJdbc(
        DataSource dataSource,
        QuartzSchedulerConfig config,
        Collection<? extends ScheduleHandler> handlers,
        Collection<? extends ScheduleExecutionListener> listeners
    ) {
        Objects.requireNonNull(dataSource, "dataSource must not be null");
        Objects.requireNonNull(config, "config must not be null");
        if (config.validateSchema()) {
            try {
                QuartzSchemaValidator.validate(dataSource, config.tablePrefix());
            } catch (ScheduleException exception) {
                throw new ScheduleException(
                    exception.errorCode(),
                    "Quartz schema validation failed for scheduler " + config.schedulerName()
                        + " and prefix " + config.tablePrefix() + ": "
                        + exception.getMessage(),
                    exception
                );
            }
        }
        rejectDuplicateSchedulerName(config.schedulerName());

        // Quartz 的全局连接注册表不支持移除，稳定名称可避免应用内重建时持续增长。
        String dataSourceName = "easyAgentsScheduler-" + config.schedulerName();
        DBConnectionManager connectionManager = DBConnectionManager.getInstance();
        connectionManager.addConnectionProvider(
            dataSourceName,
            new DataSourceConnectionProvider(dataSource)
        );
        Scheduler scheduler = null;
        try {
            scheduler = new StdSchedulerFactory(properties(config, dataSourceName)).getScheduler();
            return attach(
                scheduler,
                config.waitForJobsToCompleteOnShutdown(),
                config.shutdownWaitTimeoutMillis(),
                handlers,
                listeners
            );
        } catch (Exception exception) {
            cleanupAfterFailure(scheduler, dataSourceName, exception);
            if (exception instanceof ScheduleException scheduleException) {
                throw scheduleException;
            }
            throw new ScheduleException(
                ScheduleErrorCode.PROVIDER_FAILURE,
                "failed to initialize Quartz scheduler " + config.schedulerName(),
                exception
            );
        }
    }

    /**
     * 将 Handler 运行时装配到已有 Scheduler，供无数据库的单元测试复用。
     *
     * @param scheduler 已创建且未关闭的 Scheduler
     * @param handlers 当前节点 Handler
     * @param listeners 当前节点监听器
     * @return 尚未启动的调度服务
     * @throws ScheduleException 运行时装配失败时抛出
     */
    static QuartzScheduleService attach(
        Scheduler scheduler,
        Collection<? extends ScheduleHandler> handlers,
        Collection<? extends ScheduleExecutionListener> listeners
    ) {
        return attach(scheduler, true, 30_000L, handlers, listeners);
    }

    /**
     * 使用显式关闭策略装配已有 Scheduler。
     *
     * @param scheduler 已创建的 Scheduler
     * @param waitForJobsToCompleteOnShutdown 是否等待在途任务
     * @param shutdownWaitTimeoutMillis 最长等待毫秒数
     * @param handlers 当前节点 Handler
     * @param listeners 当前节点监听器
     * @return 尚未启动的调度服务
     */
    static QuartzScheduleService attach(
        Scheduler scheduler,
        boolean waitForJobsToCompleteOnShutdown,
        long shutdownWaitTimeoutMillis,
        Collection<? extends ScheduleHandler> handlers,
        Collection<? extends ScheduleExecutionListener> listeners
    ) {
        try {
            QuartzRuntime runtime = new QuartzRuntime(
                handlers == null ? List.of() : handlers,
                listeners == null ? List.of() : listeners
            );
            scheduler.getContext().put(QuartzRuntime.SCHEDULER_CONTEXT_KEY, runtime);
            scheduler.getListenerManager().addTriggerListener(new QuartzMisfireListener(runtime));
            return new QuartzScheduleService(
                scheduler,
                waitForJobsToCompleteOnShutdown,
                shutdownWaitTimeoutMillis
            );
        } catch (SchedulerException exception) {
            throw new ScheduleException(
                ScheduleErrorCode.PROVIDER_FAILURE,
                "failed to attach Easy Agents scheduler runtime",
                exception
            );
        }
    }

    /**
     * 构造隔离的原生 Quartz 配置。
     *
     * @param config Provider 配置
     * @param dataSourceName Quartz 内部 DataSource 名称
     * @return Quartz 属性
     */
    static Properties properties(
        QuartzSchedulerConfig config,
        String dataSourceName
    ) {
        Properties properties = new Properties();
        properties.setProperty("org.quartz.scheduler.instanceName", config.schedulerName());
        properties.setProperty("org.quartz.scheduler.instanceId", config.instanceId());
        properties.setProperty(
            "org.quartz.threadPool.class",
            "org.quartz.simpl.SimpleThreadPool"
        );
        properties.setProperty(
            "org.quartz.threadPool.threadCount",
            Integer.toString(config.threadCount())
        );
        properties.setProperty(
            "org.quartz.threadPool.threadPriority",
            Integer.toString(config.threadPriority())
        );
        properties.setProperty(
            "org.quartz.scheduler.batchTriggerAcquisitionMaxCount",
            Integer.toString(config.batchTriggerAcquisitionMaxCount())
        );
        properties.setProperty(
            "org.quartz.scheduler.batchTriggerAcquisitionFireAheadTimeWindow",
            Long.toString(config.batchTriggerAcquisitionFireAheadTimeWindowMillis())
        );
        properties.setProperty(
            "org.quartz.jobStore.class",
            "org.quartz.impl.jdbcjobstore.JobStoreTX"
        );
        properties.setProperty(
            "org.quartz.jobStore.driverDelegateClass",
            config.driverDelegateClass()
        );
        properties.setProperty("org.quartz.jobStore.useProperties", "true");
        properties.setProperty(
            "org.quartz.jobStore.acquireTriggersWithinLock",
            Boolean.toString(config.batchTriggerAcquisitionMaxCount() > 1)
        );
        properties.setProperty("org.quartz.jobStore.dataSource", dataSourceName);
        properties.setProperty("org.quartz.jobStore.tablePrefix", config.tablePrefix());
        properties.setProperty(
            "org.quartz.jobStore.isClustered",
            Boolean.toString(config.clustered())
        );
        properties.setProperty(
            "org.quartz.jobStore.clusterCheckinInterval",
            Long.toString(config.clusterCheckinIntervalMillis())
        );
        properties.setProperty(
            "org.quartz.jobStore.misfireThreshold",
            Long.toString(config.misfireThresholdMillis())
        );
        properties.setProperty("org.quartz.scheduler.interruptJobsOnShutdown", "true");
        return properties;
    }

    /**
     * 拒绝同 JVM 中仍存活的同名 Scheduler。
     *
     * @param schedulerName Scheduler Name
     */
    private static void rejectDuplicateSchedulerName(String schedulerName) {
        Scheduler existing = SchedulerRepository.getInstance().lookup(schedulerName);
        if (existing == null) {
            return;
        }
        try {
            if (existing.isShutdown()) {
                SchedulerRepository.getInstance().remove(schedulerName);
                return;
            }
        } catch (SchedulerException exception) {
            throw new ScheduleException(
                ScheduleErrorCode.PROVIDER_FAILURE,
                "failed to inspect existing Quartz scheduler " + schedulerName,
                exception
            );
        }
        throw new ScheduleException(
            ScheduleErrorCode.PROVIDER_FAILURE,
            "Quartz scheduler name is already in use in this JVM: " + schedulerName
        );
    }

    /**
     * 清理初始化失败留下的 Scheduler 与 DataSource 引用。
     *
     * @param scheduler 已创建的 Scheduler，可为空
     * @param dataSourceName Quartz 内部 DataSource 名称
     * @param originalFailure 原始失败
     */
    private static void cleanupAfterFailure(
        Scheduler scheduler,
        String dataSourceName,
        Exception originalFailure
    ) {
        if (scheduler != null) {
            try {
                if (!scheduler.isShutdown()) {
                    scheduler.shutdown(false);
                }
            } catch (SchedulerException cleanupFailure) {
                originalFailure.addSuppressed(cleanupFailure);
            }
        }
        try {
            DBConnectionManager.getInstance().shutdown(dataSourceName);
        } catch (SQLException cleanupFailure) {
            originalFailure.addSuppressed(cleanupFailure);
        }
    }
}
