package com.qmth.boot.data.upgrade.config;

import com.qmth.boot.data.upgrade.annotation.DataUpgradeVersion;
import com.qmth.boot.data.upgrade.service.DataUpgradeService;
import com.qmth.boot.data.upgrade.utils.VersionComparator;
import com.zaxxer.hikari.HikariDataSource;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.event.ApplicationPreparedEvent;
import org.springframework.boot.jdbc.DatabaseDriver;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.ClassUtils;

import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 数据升级监听类
 *
 * @author: Deason
 * @since: 2025/3/3
 */
public class DataUpgradeListener implements ApplicationListener<ApplicationPreparedEvent> {

    private static final Logger log = LoggerFactory.getLogger(DataUpgradeListener.class);

    private static final String DB_URL = "com.qmth.datasource.url";

    private static final String DB_USERNAME = "com.qmth.datasource.username";

    private static final String DB_PASSWORD = "com.qmth.datasource.password";

    private static final String APP_VERSION = "com.qmth.solar.app-version";

    private static final String BASE_PACKAGE = "com.qmth.data.upgrade.base-package";

    @Override
    public void onApplicationEvent(ApplicationPreparedEvent event) {
        ConfigurableEnvironment environment = event.getApplicationContext().getEnvironment();
        try (HikariDataSource dataSource = this.initDataSource(environment)) {
            JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
            TransactionTemplate transactionTemplate = new TransactionTemplate(new DataSourceTransactionManager(dataSource));

            String appVersion = environment.getProperty(APP_VERSION, String.class);
            if (StringUtils.isBlank(appVersion)) {
                throw new RuntimeException(APP_VERSION + " 配置值不能为空！");
            }

            // 检查当前版本是否需要数据升级
            String currentVersion = this.queryCurrentVersion(jdbcTemplate);
            if (new VersionComparator().compare(currentVersion, appVersion) >= 0) {
                log.warn("跳过数据升级！currentVersion:{} appVersion:{}", currentVersion, appVersion);
                return;
            }

            String basePackage = environment.getProperty(BASE_PACKAGE, String.class);
            if (StringUtils.isBlank(basePackage)) {
                basePackage = "com.qmth";// 未配置扫描包名，赋默认值
            }

            List<DataUpgradeService> services = this.matchServices(basePackage, currentVersion, appVersion);
            if (!services.isEmpty()) {
                log.warn("*************** data upgrade start ***************");
                for (DataUpgradeService service : services) {
                    service.process(jdbcTemplate, transactionTemplate);
                }
            } else {
                log.warn("未找到数据升级的具体实现！{}:{}", BASE_PACKAGE, basePackage);
            }

            // 数据升级完成后修改为当前版本号
            updateCurrentVersion(appVersion, jdbcTemplate);
            log.warn("*************** data upgrade finish ***************");
        } catch (Exception e) {
            log.error("数据升级异常，终止运行。{}", e.getMessage(), e);
            System.exit(1);
        }
    }

    /**
     * 获取数据升级的具体实现（按版本号由小到大排序、支持跨版本）
     * 1、出现同个版本的重复实现，则异常终止
     * 2、未扫描到任何实现 或 所有实现都与待升级目标版本的版本区间不匹配，则忽略
     */
    private List<DataUpgradeService> matchServices(String basePackage, String currentVersion, String appVersion) {
        List<Class<?>> implClasses = this.findInterfaceImpls(DataUpgradeService.class, basePackage);
        if (implClasses.isEmpty()) {
            return new ArrayList<>();
        }

        VersionComparator vc = new VersionComparator();
        List<String> keys = new ArrayList<>();
        Map<String, DataUpgradeService> serviceMaps = new HashMap<>();

        for (Class<?> clazz : implClasses) {
            DataUpgradeVersion annotation = clazz.getAnnotation(DataUpgradeVersion.class);
            if (annotation == null) {
                // 跳过未声明“数据升级版本”注解的实现
                continue;
            }

            String value = annotation.value();
            if (vc.compare(value, currentVersion) > 0 && vc.compare(value, appVersion) <= 0) {
                // log.info("{} {}", clazz.getSimpleName(), value);
                if (serviceMaps.containsKey(value)) {
                    throw new RuntimeException("同个版本数据升级的实现出现重复！@DataUpgradeVersion:" + value);
                }

                try {
                    Constructor<?> constructor = ClassUtils.getConstructorIfAvailable(clazz);
                    if (constructor != null) {
                        serviceMaps.put(value, (DataUpgradeService) constructor.newInstance());
                        keys.add(value);
                    }
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }

        List<DataUpgradeService> result = new ArrayList<>();
        if (!keys.isEmpty()) {
            // 按版本号由小到大排序
            keys.sort(vc);
            for (String key : keys) {
                result.add(serviceMaps.get(key));
            }
        }
        return result;
    }

    private String queryCurrentVersion(JdbcTemplate jdbcTemplate) {
        try {
            jdbcTemplate.execute("create table if not exists app_version (current_version varchar(10) not null, unique key(current_version))");
            String currentVersion = jdbcTemplate.queryForObject("select max(current_version) from app_version", String.class);
            // 未定义时，默认值为0，即最小版本号从0开始
            return StringUtils.isNotBlank(currentVersion) ? currentVersion : "0";
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private void updateCurrentVersion(String appVersion, JdbcTemplate jdbcTemplate) {
        if (StringUtils.isBlank(appVersion)) {
            throw new RuntimeException("appVersion的值不能为空！");
        }

        try {
            jdbcTemplate.update("replace into app_version (current_version) values (?)", appVersion);
            jdbcTemplate.update("delete from app_version where current_version != ?", appVersion);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private HikariDataSource initDataSource(ConfigurableEnvironment environment) {
        String dbUrl = environment.getProperty(DB_URL, String.class);
        if (StringUtils.isBlank(dbUrl)) {
            throw new RuntimeException(DB_URL + " 配置值不能为空！");
        }
        String dbUsername = environment.getProperty(DB_USERNAME, String.class);
        if (StringUtils.isBlank(dbUsername)) {
            throw new RuntimeException(DB_USERNAME + " 配置值不能为空！");
        }
        String dbPassword = environment.getProperty(DB_PASSWORD, String.class);
        if (StringUtils.isBlank(dbPassword)) {
            throw new RuntimeException(DB_PASSWORD + " 配置值不能为空！");
        }
        String driverClassName = DatabaseDriver.fromJdbcUrl(dbUrl).getDriverClassName();

        HikariDataSource dataSource = new HikariDataSource();
        dataSource.setPoolName("dataUpgradeDataSource");
        dataSource.setDriverClassName(driverClassName);
        dataSource.setJdbcUrl(dbUrl);
        dataSource.setUsername(dbUsername);
        dataSource.setPassword(dbPassword);
        return dataSource;
    }

    private List<Class<?>> findInterfaceImpls(Class<?> interfaceClass, String basePackage) {
        ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
        scanner.addIncludeFilter(new AssignableTypeFilter(interfaceClass));
        List<Class<?>> impls = new ArrayList<>();
        scanner.findCandidateComponents(basePackage).forEach(bean -> {
            try {
                Class<?> clazz = ClassUtils.forName(bean.getBeanClassName(), ClassUtils.getDefaultClassLoader());
                // 排除接口和抽象类
                if (!clazz.isInterface() && !Modifier.isAbstract(clazz.getModifiers())) {
                    impls.add(clazz);
                }
            } catch (Exception e) {
                log.warn("{} class load fail. {}", bean.getBeanClassName(), e.getMessage());
            }
        });
        return impls;
    }

}
