package com.easyagents.scheduler.spring.boot;

import com.easyagents.scheduler.ScheduleErrorCode;
import com.easyagents.scheduler.ScheduleException;
import com.easyagents.scheduler.ScheduleExecutionListener;
import com.easyagents.scheduler.ScheduleHandler;
import com.easyagents.scheduler.ScheduleService;
import com.easyagents.scheduler.quartz.QuartzScheduleService;
import com.easyagents.scheduler.quartz.QuartzSchedulerConfig;
import com.easyagents.scheduler.quartz.QuartzSchedulerFactory;
import org.quartz.impl.StdSchedulerFactory;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.sql.init.dependency.DependsOnDatabaseInitialization;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;

/**
 * Easy Agents 独立调度器自动配置。
 */
@Configuration(proxyBeanMethods = false)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
@ConditionalOnClass({ScheduleService.class, StdSchedulerFactory.class, DataSource.class})
@ConditionalOnProperty(
    prefix = "easy-agents.scheduler",
    name = "enabled",
    havingValue = "true"
)
@EnableConfigurationProperties(EasyAgentsSchedulerProperties.class)
public class EasyAgentsSchedulerAutoConfiguration {

    /** Easy Agents Quartz Scheduler 的明确 Bean 名称。 */
    public static final String SCHEDULER_BEAN_NAME = "easyAgentsQuartzScheduleService";

    /**
     * 创建调度器自动配置。
     */
    public EasyAgentsSchedulerAutoConfiguration() {
    }

    /**
     * 创建与调用方其他 Quartz Scheduler 隔离的调度服务。
     *
     * @param properties 调度器配置
     * @param beanFactory 用于明确选择 DataSource
     * @param handlers 调度 Handler Bean
     * @param listeners 调度监听器 Bean
     * @return 尚未启动、由 Spring 生命周期启动的调度服务
     * @throws ScheduleException DataSource 选择或 Provider 初始化失败时抛出
     */
    @Bean(name = SCHEDULER_BEAN_NAME, initMethod = "start", destroyMethod = "close")
    @ConditionalOnMissingBean(ScheduleService.class)
    @DependsOnDatabaseInitialization
    public QuartzScheduleService easyAgentsQuartzScheduleService(
        EasyAgentsSchedulerProperties properties,
        ListableBeanFactory beanFactory,
        ObjectProvider<ScheduleHandler> handlers,
        ObjectProvider<ScheduleExecutionListener> listeners
    ) {
        DataSource dataSource = selectDataSource(
            beanFactory,
            properties.getDataSourceBeanName()
        );
        EasyAgentsSchedulerProperties.Quartz quartz = properties.getQuartz();
        if (quartz == null) {
            throw providerFailure("easy-agents.scheduler.quartz must not be null");
        }
        QuartzSchedulerConfig config = new QuartzSchedulerConfig(
            quartz.getSchedulerName(),
            quartz.getInstanceId(),
            quartz.getTablePrefix(),
            quartz.getDriverDelegateClass(),
            quartz.isClustered(),
            quartz.getThreadCount(),
            quartz.getThreadPriority(),
            quartz.getBatchTriggerAcquisitionMaxCount(),
            quartz.getBatchTriggerAcquisitionFireAheadTimeWindowMillis(),
            quartz.getClusterCheckinIntervalMillis(),
            quartz.getMisfireThresholdMillis(),
            quartz.isWaitForJobsToCompleteOnShutdown(),
            quartz.getShutdownWaitTimeoutMillis(),
            quartz.isValidateSchema()
        );
        return QuartzSchedulerFactory.createJdbc(
            dataSource,
            config,
            handlers.orderedStream().toList(),
            listeners.orderedStream().toList()
        );
    }

    private static DataSource selectDataSource(
        ListableBeanFactory beanFactory,
        String requestedBeanName
    ) {
        if (requestedBeanName != null && !requestedBeanName.isBlank()) {
            String normalizedName = requestedBeanName.trim();
            try {
                return beanFactory.getBean(normalizedName, DataSource.class);
            } catch (RuntimeException exception) {
                throw new ScheduleException(
                    ScheduleErrorCode.PROVIDER_FAILURE,
                    "configured scheduler DataSource bean is unavailable: " + normalizedName,
                    exception
                );
            }
        }
        Map<String, DataSource> candidates = BeanFactoryUtils.beansOfTypeIncludingAncestors(
            beanFactory,
            DataSource.class,
            true,
            false
        );
        Collection<DataSource> uniqueCandidates = candidates.values().stream().distinct().toList();
        if (uniqueCandidates.size() == 1) {
            return uniqueCandidates.iterator().next();
        }
        String names = Arrays.toString(candidates.keySet().toArray(String[]::new));
        if (uniqueCandidates.isEmpty()) {
            throw providerFailure(
                "scheduler is enabled but no DataSource bean exists; configure "
                    + "easy-agents.scheduler.data-source-bean-name or provide one DataSource"
            );
        }
        throw providerFailure(
            "multiple DataSource beans found " + names + "; configure "
                + "easy-agents.scheduler.data-source-bean-name"
        );
    }

    private static ScheduleException providerFailure(String message) {
        return new ScheduleException(ScheduleErrorCode.PROVIDER_FAILURE, message);
    }
}
