package study.batchquartz;

import javax.sql.DataSource;

import com.zaxxer.hikari.HikariDataSource;
import org.junit.jupiter.api.Test;
import org.quartz.Scheduler;
import org.springframework.batch.core.configuration.support.DefaultBatchConfiguration;
import org.springframework.batch.core.launch.JobOperator;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.support.ResourcelessJobRepository;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.batch.autoconfigure.BatchAutoConfiguration;
import org.springframework.boot.batch.autoconfigure.BatchJobLauncherAutoConfiguration;
import org.springframework.boot.batch.jdbc.autoconfigure.BatchJdbcAutoConfiguration;
import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration;
import org.springframework.boot.jdbc.autoconfigure.JdbcTemplateAutoConfiguration;
import org.springframework.boot.quartz.autoconfigure.QuartzAutoConfiguration;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.support.JdbcTransactionManager;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;

import static org.assertj.core.api.Assertions.assertThat;

/// U46 判定实验之三：Boot 4.0.9 的 Batch/Quartz 自动配置条件与默认值。用 ApplicationContextRunner
/// 在真实 DataSource 上装配真实自动配置类，核对“有 DataSource/无 DataSource”“MEMORY/JDBC”的差别。
class BootAutoConfigurationTest {

    private final ApplicationContextRunner batchRunner = new ApplicationContextRunner()
            .withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class,
                    JdbcTemplateAutoConfiguration.class, BatchAutoConfiguration.class,
                    BatchJdbcAutoConfiguration.class, BatchJobLauncherAutoConfiguration.class));

    private final ApplicationContextRunner quartzRunner = new ApplicationContextRunner()
            .withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class,
                    JdbcTemplateAutoConfiguration.class, QuartzAutoConfiguration.class));

    @Test
    void batchDefaultsToResourcelessRepositoryWithoutDataSource() {
        new ApplicationContextRunner()
                .withConfiguration(AutoConfigurations.of(BatchAutoConfiguration.class))
                .run(context -> {
                    assertThat(context).hasSingleBean(JobRepository.class);
                    assertThat(context.getBean(JobRepository.class))
                            .isInstanceOf(ResourcelessJobRepository.class);
                    assertThat(context).hasSingleBean(JobOperator.class);
                });
    }

    @Test
    void batchWithDataSourceUsesJdbcRepositoryAndCreatesSchema() throws Exception {
        try (LabDatabase db = new LabDatabase()) {
            batchRunner.withUserConfiguration(DataSourceConfig.class)
                    .withPropertyValues("spring.batch.jdbc.initialize-schema=always")
                    .withBean("labDataSource", DataSource.class, () -> dataSourceChecked(db))
                    .run(context -> {
                        assertThat(context).hasNotFailed();
                        assertThat(context).hasSingleBean(JobRepository.class);
                        assertThat(context.getBean(JobRepository.class))
                                .isNotInstanceOf(ResourcelessJobRepository.class);
                        assertThat(context).hasSingleBean(JobOperator.class);
                        assertThat(context).hasSingleBean(DefaultBatchConfiguration.class);
                        assertThat(db.count("SELECT COUNT(*) FROM information_schema.tables"
                                + " WHERE table_schema=? AND table_name LIKE 'BATCH%'", db.name))
                                .isGreaterThanOrEqualTo(6L);
                        System.out.println("U46 bootBatchJdbc resourceless=false batchTables="
                                + db.count("SELECT COUNT(*) FROM information_schema.tables"
                                        + " WHERE table_schema=? AND table_name LIKE 'BATCH%'", db.name));
                    });
        }
    }

    @Test
    void quartzDefaultsToMemoryStoreAndJdbcRequiresExplicitProperty() throws Exception {
        // 每个 ApplicationContextRunner 各自一个库：Spring 会把作为 Bean 的 DataSource 在
        // 上下文关闭时一并关闭，复用同一个 HikariDataSource 会让第二个上下文拿到已关闭的池。
        try (LabDatabase memoryDb = new LabDatabase()) {
            // 默认 MEMORY：即使给了 DataSource，也不会用 JDBC JobStore，QRTZ 表不存在。
            new ApplicationContextRunner()
                    .withConfiguration(AutoConfigurations.of(QuartzAutoConfiguration.class))
                    .withBean(DataSource.class, () -> dataSourceChecked(memoryDb))
                    .withBean(PlatformTransactionManager.class, () -> memoryDb.transactionManager())
                    .run(context -> {
                        assertThat(context).hasSingleBean(SchedulerFactoryBean.class);
                        SchedulerFactoryBean factory = context.getBean(SchedulerFactoryBean.class);
                        assertThat(factory.getObject().getMetaData().getJobStoreClass().getName())
                                .isEqualTo("org.quartz.simpl.RAMJobStore");
                        assertThat(memoryDb.count("SELECT COUNT(*) FROM information_schema.tables"
                                + " WHERE table_schema=? AND table_name LIKE 'QRTZ%'", memoryDb.name)).isZero();
                    });
        }
        try (LabDatabase db = new LabDatabase()) {
            // 显式 job-store-type=jdbc：换 LocalDataSourceJobStore，并可用 Boot 初始化脚本建表。
            new ApplicationContextRunner()
                    .withConfiguration(AutoConfigurations.of(QuartzAutoConfiguration.class))
                    // 关键版本事实：DataSource 以 Bean 形式提供（没有 spring.datasource.url 属性）时，
                    // Boot 4.0.9 的 Quartz 建表初始化器无法推断 @@platform@@，必须显式给 schema 或 platform；
                    // 这正是“自动装配不等于交付保证”的一个真实条件。
                    .withPropertyValues("spring.quartz.job-store-type=jdbc",
                            "spring.quartz.jdbc.initialize-schema=always",
                            "spring.quartz.jdbc.schema=classpath:org/quartz/impl/jdbcjobstore/tables_mysql_innodb.sql")
                    .withBean(DataSource.class, () -> dataSourceChecked(db))
                    .withBean(PlatformTransactionManager.class, () -> db.transactionManager())
                    .run(context -> {
                        assertThat(context).hasSingleBean(SchedulerFactoryBean.class);
                        SchedulerFactoryBean factory = context.getBean(SchedulerFactoryBean.class);
                        Scheduler scheduler = factory.getObject();
                        assertThat(scheduler.getMetaData().getJobStoreClass().getName())
                                .isEqualTo("org.springframework.scheduling.quartz.LocalDataSourceJobStore");
                        assertThat(scheduler.getMetaData().isJobStoreClustered()).isFalse();
                        assertThat(db.count("SELECT COUNT(*) FROM information_schema.tables"
                                + " WHERE table_schema=? AND table_name LIKE 'QRTZ%'", db.name))
                                .isGreaterThanOrEqualTo(11L);
                        System.out.println("U46 bootQuartz jdbcStore="
                                + scheduler.getMetaData().getJobStoreClass().getSimpleName()
                                + " clustered=" + scheduler.getMetaData().isJobStoreClustered()
                                + " qrtzTables=" + db.count("SELECT COUNT(*) FROM information_schema.tables"
                                        + " WHERE table_schema=? AND table_name LIKE 'QRTZ%'", db.name));
                    });
        }
    }

    private static DataSource dataSourceChecked(LabDatabase db) {
        return db.dataSource();
    }

    @Configuration(proxyBeanMethods = false)
    static class DataSourceConfig {
        @Bean
        PlatformTransactionManager transactionManager(DataSource dataSource) {
            return new JdbcTransactionManager(dataSource);
        }
    }
}
