package com.ecep.contract;

import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
import java.util.Properties;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ScheduledExecutorService;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationHook;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup;
import org.springframework.boot.context.metrics.buffering.StartupTimeline;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.metrics.StartupStep;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;

import com.ecep.contract.util.UITools;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;

@SpringBootApplication(exclude = {
        org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration.class,
        org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration.class,
        org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration.class,
        org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration.class
})
@EnableScheduling
@EnableAsync
@EnableCaching
public class SpringApp {
    private static final Logger logger = LoggerFactory.getLogger(SpringApp.class);

    public static SpringApplication application;
    public static ConfigurableApplicationContext context;

    public static <T> T getBean(Class<T> requiredType) throws BeansException {
        return context.getBean(requiredType);
    }

    public static void launch(Properties properties, MessageHolder holder) {
        application = new SpringApplication(SpringApp.class);
        BufferingApplicationStartup startup = new BufferingApplicationStartup(2000);
        application.setApplicationStartup(startup);
        //
        holder.debug("应用程序环境准备中...");
        SpringApplication.withHook(new Hook(holder), () -> {
            // 动态地注册或修改这些组件和配置
            application.addBootstrapRegistryInitializer(registry -> {
                //
                System.out.println("registry = " + registry);

            });
            application.addListeners(event -> {
                logger.debug("SpringApp.launch ApplicationListener, event:{}", event);
            });

            application.addInitializers(app -> {
                logger.debug("SpringApp.launch ApplicationContextInitializer");
                ConfigurableEnvironment environment = app.getEnvironment();
                logger.debug("environment = {}", environment);
                PropertySource<?> dynamicProperties = environment.getPropertySources().get("dynamicProperties");
                if (dynamicProperties != null) {
                    logger.debug("dynamicProperties = {}", dynamicProperties);
                }
                environment.getPropertySources().addLast(new PropertiesPropertySource("dynamicProperties", properties));
                // app.getBeanFactory().registerSingleton("dataSource", dataSource());
                logger.debug("app = {}", app);
            });

            startup.start("");
            context = application.run();
            logger.debug("SpringApp.launch application.run().");
            Duration between = Duration.between(startup.getBufferedTimeline().getStartTime(), Instant.now());
            holder.info("应用程序环境加载完成... " + between);
        });
        CompletableFuture.runAsync(() -> {
            // 在这里调用 startup 性能分析
            analyzeStartupPerformance(startup);
        }, Desktop.instance.getExecutorService());
    }

    /**
     * 分析启动性能数据并输出到日志
     */
    private static void analyzeStartupPerformance(BufferingApplicationStartup startup) {
        // 获取所有记录的事件
        StartupTimeline timeline = startup.getBufferedTimeline();
        if (timeline == null || timeline.getEvents().isEmpty()) {
            logger.warn("StartupTimeline 为空或没有事件！");
            return;
        }
        logger.info("总共有 {} 个事件", timeline.getEvents().size());

        // 找出与 Bean 初始化相关的步骤
        timeline.getEvents().stream()
                .filter(event -> event.getStartupStep().getName().startsWith("spring.beans."))
                .sorted((a, b) -> Long.compare(b.getDuration().toMillis(), a.getDuration().toMillis()))
                .limit(30)
                .forEach(event -> {
                    String name = event.getStartupStep().getName();
                    long duration = event.getDuration().toMillis();
                    logger.info("Bean 初始化阶段: {} - 耗时: {} ms", name, duration);

                    for (StartupStep.Tag tag : event.getStartupStep().getTags()) {
                        if ("beanName".equals(tag.getKey())) {
                            logger.info("  └── Bean 名称: {}", tag.getValue());
                        }
                    }
                });
    }

    public static String getMessage(String code, Object[] args, Locale locale) {
        return context.getMessage(code, args, locale);
    }

    public static void shutdown() {
        System.out.println("SpringApp.shutdown");
        if (logger.isDebugEnabled()) {
            logger.debug("shutdown");
        }
        if (context != null) {
            if (context.isRunning()) {
                context.close();
            }
        }
    }

    public static boolean isRunning() {
        return context != null && context.isRunning();
    }

    static class Hook implements SpringApplicationHook, SpringApplicationRunListener {
        MessageHolder holder;

        Hook(MessageHolder holder) {
            this.holder = holder;
        }

        public void debug(String msg) {
            holder.debug(msg);
        }

        @Override
        public void starting(ConfigurableBootstrapContext bootstrapContext) {
            logger.debug("Desktop.starting");
            debug("Spring Application 启动中...");
        }

        @Override
        public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext,
                ConfigurableEnvironment environment) {
            logger.debug("Desktop.environmentPrepared");
            debug("初始化 Environment 中，请稍后...");
        }

        @Override
        public void contextPrepared(ConfigurableApplicationContext context) {
            logger.debug("Desktop.contextPrepared");
            debug("Spring Application Context 预处理中，请稍后...");
        }

        @Override
        public void contextLoaded(ConfigurableApplicationContext context) {
            logger.debug("Desktop.contextLoaded");
            debug("Spring Application Context 初始化完毕，请稍后...");
        }

        @Override
        public void started(ConfigurableApplicationContext context, Duration timeTaken) {
            logger.debug("Desktop.started");
            debug("Spring Application 启动完毕.");
        }

        @Override
        public void ready(ConfigurableApplicationContext context, Duration timeTaken) {
            logger.debug("Desktop.ready");
            debug("Spring Application ready.");
        }

        @Override
        public void failed(ConfigurableApplicationContext context, Throwable exception) {
            logger.error("Desktop.failed", exception);
            holder.error("Spring Application 启动失败(" + exception.getMessage() + ").");
            UITools.showExceptionAndWait("启动失败", exception);
        }

        @Override
        public SpringApplicationRunListener getRunListener(SpringApplication springApplication) {
            return this;
        }
    }

    @EventListener
    public void handleClosedEvent(ContextClosedEvent event) {
        if (logger.isDebugEnabled()) {
            logger.debug("handleClosedEvent={}", event);
        }
        Desktop.shutdown();
    }

    @Bean
    public CacheManager cacheManager() {
        // return new ConcurrentMapCacheManager("myCache");
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        cacheManager.setAsyncCacheMode(true);
        return cacheManager;
    }

    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();
        JavaTimeModule javaTimeModule = new JavaTimeModule();
        // LocalDate
        javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ISO_LOCAL_DATE));
        javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ISO_LOCAL_DATE));
        // LocalTime
        javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ISO_LOCAL_TIME));
        javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ISO_LOCAL_TIME));
        // LocalDateTime
        javaTimeModule.addSerializer(LocalDateTime.class,
                new LocalDateTimeSerializer(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
        javaTimeModule.addDeserializer(LocalDateTime.class,
                new LocalDateTimeDeserializer(DateTimeFormatter.ISO_LOCAL_DATE_TIME));

        objectMapper.registerModule(javaTimeModule);
        return objectMapper;
    }

    @Bean
    public ScheduledExecutorService scheduledExecutorService() {
        return Desktop.instance.getExecutorService();
    }

}
