package quant.rich.emoney.config;

import java.io.File;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;

import javax.sql.DataSource;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import com.zaxxer.hikari.HikariDataSource;

import lombok.extern.slf4j.Slf4j;
import quant.rich.emoney.EmoneyAutoApplication;

@Slf4j
@Configuration
@MapperScan(basePackages = "quant.rich.emoney.mapper.sqlite", sqlSessionTemplateRef = "sqliteSqlSessionTemplate")
public class SqliteMybatisConfig {
	
	private static final String RESOURCE_PATH = "database.db";
	
    public static final String SQLITE_TRANSACTION_MANAGER = "sqliteTransactionManager";


    @Value("${mybatis-plus.mapper-locations}")
    private String[] mapperLocations;
    
    /**
     * 配置数据库连接
     * <ul>
     * <li>如果非打包状态，则直接选取当前项目内数据库位置</li>
     * <li>如果打包状态，以 JDBC 链接位置为主，如果位置不存在则覆盖</li>
     * </ul>
     * @param dataSource
     */
    public void initSQLiteLocation(DataSource dataSource) {
    	// 指定 sqlite 路径
    	if (dataSource instanceof HikariDataSource hikariDataSource) {
    		
    		String filePath = hikariDataSource.getJdbcUrl();
    		if (filePath == null || !filePath.startsWith("jdbc:sqlite:")) {
    			log.warn(
			        "无法在 SQLite HikariDataSource 中找到合法 SQLite JDBC url, "
			        + "数据库可能会加载失败。合法的 url 需在 application.yml(properties) "
			        + "中配置，以 jdbc:sqlite: 开头。当前获取到的 jdbc-url: {}", filePath);
    			return;
    		}
    		filePath = filePath.substring("jdbc:sqlite:".length()).trim();
    		
    		ClassPathResource original = new ClassPathResource(RESOURCE_PATH);
    		if (!original.exists()) {
                log.warn("未找到 SQLite 资源: {}", RESOURCE_PATH);
                return;
    		}
    		String protocol = EmoneyAutoApplication.class.getProtectionDomain().getCodeSource().getLocation().getProtocol();
    	    boolean isJar = "jar".equals(protocol);
    	    
    	    if (isJar) {
    	    	// 复制到外部 yml 指定路径，已存在则不复制
    	    	File dest = new File(filePath), parentDir = dest.getParentFile();
    	    	String destAbsolutePath = dest.getAbsolutePath();
        		if (!parentDir.exists() && !parentDir.mkdirs()) {
        			log.warn("无法创建放置 SQLite 文件的目录: {}", parentDir.getAbsolutePath());
        			return;
        		}
        		
        		if (dest.exists()) {
        			// 已存在
        			log.warn("目标资源 {} 已存在，忽略", destAbsolutePath);
        			return;
        		}
        		
    			try (InputStream in = getClass().getClassLoader().getResourceAsStream(RESOURCE_PATH)) {
    	            if (in == null) {
    	                log.warn("无法读取 SQLite 资源: {}", RESOURCE_PATH);
    	                return;
    	            }
    	            Files.copy(in, dest.toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING);
    	            log.info("SQLite 数据库文件已复制至：{}", destAbsolutePath);
    	        } catch (Exception e) {
	                log.warn("复制 SQLite 数据库文件失败", e);
    	        }
    	    }
    	    else {
    	    	// 使用当前绝对路径
    	    	Path path = Path.of("src/main/resources", RESOURCE_PATH);
    	    	hikariDataSource.setJdbcUrl("jdbc:sqlite:" + path.toAbsolutePath().toString());
    	    }
    	}
    }

    @Bean("sqliteSqlSessionFactory")
    public SqlSessionFactory sqliteSqlSessionFactory(
        @Qualifier("sqliteDataSource") DataSource dataSource) throws Exception {
    	
    	initSQLiteLocation(dataSource);
    	
        MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
        factory.setDataSource(dataSource);
        
        
        List<Resource> resources = new ArrayList<>();
        for (String mapperLocation : mapperLocations) {
            for (Resource resource : new PathMatchingResourcePatternResolver().getResources(mapperLocation)) {
                resources.add(resource);
            }
        }
        factory.setMapperLocations(
            resources.toArray(new Resource[0])
        );

        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.SQLITE));
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        factory.setPlugins(interceptor);
        
        return factory.getObject();
        
    }
    
    @Bean("sqliteSqlSessionTemplate")
    public SqlSessionTemplate sqliteSqlSessionTemplate(@Qualifier("sqliteSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
    
    @Bean(SQLITE_TRANSACTION_MANAGER)
    public DataSourceTransactionManager postgreTransacetionManager(@Qualifier("sqliteDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}
