package com.njcn.relational.service;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.njcn.relational.mapper.DynamicSyncMapper;
import com.njcn.relational.mapper.SyncTableConfigMapper;
import com.njcn.relational.pojo.bo.DownloadResult;
import com.njcn.relational.pojo.po.SyncTableConfig;
import com.njcn.relational.pojo.po.SyncTableLogs;
import com.njcn.relational.utils.SftpUploadUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import java.io.*;
import java.nio.file.*;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Slf4j
@Service
public class SyncThreeDmParseService {

    @Autowired
    private DynamicSyncMapper dynamicSyncMapper;

    @Autowired
    private SyncTableConfigMapper syncTableConfigMapper;

    @Autowired
    private SyncTableLogsService syncTableLogsService;

    // 缓存当前Schema
    private String currentSchema;

    @Value("${sync.import.localPath}")
    private String localPath;

    @Value("${sync.import.remotePath}")
    private String remotePath;

    @Value("${sync.ip}")
    private String ip;

    @Value("${sync.port:22}")
    private Integer port;

    @Value("${sync.username}")
    private String username;

    @Value("${sync.password}")
    private String password;

    @Autowired
    private JdbcTemplate targetJdbcTemplate;

    private static final int BATCH_SIZE = 10000;
    private static final DateTimeFormatter FILE_DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");

    // JSON解析工具
    private final ObjectMapper objectMapper = new ObjectMapper()
            .registerModule(new JavaTimeModule())
            .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
            .setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));  // 格式化输出，可读性好;

    @PostConstruct
    public void init() {
        currentSchema = dynamicSyncMapper.getCurrentUser();
        log.info("当前数据库Schema: {}", currentSchema);
    }

    Map<String, String> tableBlobCache = new ConcurrentHashMap<>();
    Map<String, String> tableClobCache = new ConcurrentHashMap<>();
    private void syncData(){
        // 加载表配置到缓存
        tableBlobCache.clear();
        tableClobCache.clear();

        LambdaQueryWrapper<SyncTableConfig> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(SyncTableConfig::getEnabled, 1);
        List<SyncTableConfig> configs = syncTableConfigMapper.selectList(wrapper);
        for (SyncTableConfig config : configs) {
            String tableUpper = config.getTableName().toUpperCase();
            if(StrUtil.isNotBlank(config.getBlobColumns())) {
                tableBlobCache.put(tableUpper, config.getBlobColumns());
            }
            if(StrUtil.isNotBlank(config.getClobColumns())){
                tableClobCache.put(tableUpper, config.getClobColumns());
            }
        }
        log.info("加载表配置BLOB缓存完成，共 {} 张表", tableBlobCache.size());

        log.info("加载表配置CLOB缓存完成，共 {} 张表", tableClobCache.size());

    }

    /**
     * 批量同步所有表的数据文件（使用批量下载并删除）
     */
    public void batchSyncAllTables(String dataStr) {
        log.info("========== 开始批量同步数据{} ==========",LocalDateTime.now());

        // 缓存表配置
        syncData();

        // 确保本地目录存在
        ensureLocalDirectory();

        boolean localFlag =true;
        boolean resultFlag = true;

        try {
            // 1. 获取待下载的文件列表
            List<String> remoteFilePaths = getRemoteFilePaths(dataStr);

            if (remoteFilePaths.isEmpty()) {
                log.info("没有从横向隔离设备找到匹配的文件");
                return;
            }

            log.info("找到 {} 个文件待处理", remoteFilePaths.size());

            // 2. 批量下载并删除远程文件
            List<DownloadResult> downloadResults = SftpUploadUtil.batchDownloadAndDelete(
                    ip, port, username, password, remoteFilePaths, localPath, true  // true = 下载后删除远程文件
            );

            // 3. 统计下载结果
            long downloadSuccessCount = downloadResults.stream().filter(DownloadResult::isSuccess).count();
            long downloadFailCount = downloadResults.size() - downloadSuccessCount;
            log.info("批量下载完成 - 成功: {}, 失败: {}", downloadSuccessCount, downloadFailCount);

            // 记录下载失败的文件
            for (DownloadResult result : downloadResults) {
                if (!result.isSuccess()) {
                    localFlag = false;
                    log.error("下载失败: {}, 原因: {}", result.getRemoteFilePath(), result.getErrorMessage());
                }
            }

            // 4. 处理下载成功的文件（导入数据）
            List<DownloadResult> successfulDownloads = downloadResults.stream()
                    .filter(DownloadResult::isSuccess)
                    .collect(Collectors.toList());

            if (successfulDownloads.isEmpty()) {
                log.error("没有成功下载的文件，停止处理");
                localFlag = false;
                return;
            }

            // 5. 批量导入数据
            int importSuccessCount = 0;
            int importFailCount = 0;

            for (DownloadResult downloadResult : successfulDownloads) {
                try {
                    String tableName = extractTableNameFromFile(downloadResult.getRemoteFilePath());
                    String localFilePath = downloadResult.getLocalFilePath();

                    log.info("开始导入表: {}, 文件: {}", tableName, localFilePath);
                    importJsonToTable(tableName, localFilePath);

                    importSuccessCount++;
                    log.info("表 {} 导入成功", tableName);

                    // 导入成功后删除本地文件
                    deleteLocalFile(localFilePath);

                } catch (Exception e) {
                    resultFlag = false;
                    importFailCount++;
                    log.error("导入失败: {}", downloadResult.getRemoteFilePath(), e);
                }
            }

            log.info("批量导入完成 - 成功: {}, 失败: {}", importSuccessCount, importFailCount);
            log.info("========== 数据批量同步完成 ==========");

        } catch (Exception e) {
            log.error("批量同步失败", e);
            resultFlag = false;
        }

        SyncTableLogs syncTableLogs = new SyncTableLogs();
        syncTableLogs.setLocalResult(localFlag?1:0);
        syncTableLogs.setResult((resultFlag&&localFlag) ? 1:0);
        syncTableLogs.setTimeId(LocalDate.parse(dataStr, DateTimeFormatter.ofPattern("yyyyMMdd")));
        syncTableLogs.setCreateTime(LocalDateTime.now());
        syncTableLogsService.saveOrUpdate(syncTableLogs);
    }

    /**
     * 获取远程文件路径列表
     */
    private List<String> getRemoteFilePaths(String dateStr) throws Exception {
        // 获取远程目录下所有文件
        Vector<?> files = SftpUploadUtil.listFiles(ip, port, username, password, remotePath);

        // 列出远程目录下所有匹配的文件
        String remoteFilePattern = buildRemoteFilePattern("FULL", dateStr);
        List<String> matchedFiles = listRemoteFiles(remoteFilePattern,files);
        String remoteFilePatternInc = buildRemoteFilePattern("INC", dateStr);
        List<String> matchedFilesInc = listRemoteFiles(remoteFilePatternInc,files);

        // 转换为完整路径
        List<String> remoteFilePaths = new ArrayList<>();
        for (String fileName : matchedFiles) {
            remoteFilePaths.add(remotePath + (remotePath.endsWith("/") ? "" : "/") + fileName);
        }
        for (String fileName : matchedFilesInc) {
            remoteFilePaths.add(remotePath + (remotePath.endsWith("/") ? "" : "/") + fileName);
        }
        return remoteFilePaths;
    }

    /**
     * 全量同步
     */
    public void syncFullTables(String dataStr) {
        batchSyncAllTables(dataStr);
    }



    /**
     * 根据同步模式获取日期字符串
     */
    private String getDateStrBySyncMode(String syncMode) {
        if ("FULL".equals(syncMode)) {
            // 全量同步使用昨天的日期
            return LocalDate.now().minusDays(1).format(FILE_DATE_FORMATTER);
        } else {
            // 增量同步使用今天的日期
            return LocalDate.now().format(FILE_DATE_FORMATTER);
        }
    }

    /**
     * 构建远程文件名匹配模式
     */
    private String buildRemoteFilePattern(String syncMode, String dateStr) {
        return String.format(".*_%s_%s\\.txt$", syncMode, dateStr);
    }

    /**
     * 列出远程目录下所有匹配的文件
     */
    private List<String> listRemoteFiles(String pattern,Vector<?> files) throws Exception {
        List<String> matchedFiles = new ArrayList<>();

        try {
            Pattern regexPattern = Pattern.compile(pattern);
            for (Object obj : files) {
                if (obj instanceof com.jcraft.jsch.ChannelSftp.LsEntry) {
                    com.jcraft.jsch.ChannelSftp.LsEntry entry = (com.jcraft.jsch.ChannelSftp.LsEntry) obj;
                    String fileName = entry.getFilename();

                    // 跳过目录和特殊文件
                    if (entry.getAttrs().isDir() || fileName.equals(".") || fileName.equals("..")) {
                        continue;
                    }

                    // 匹配文件模式
                    if (regexPattern.matcher(fileName).matches()) {
                        matchedFiles.add(fileName);
                        log.debug("匹配到文件: {}", fileName);
                    }
                }
            }

        } catch (Exception e) {
            log.error("匹配远程文件失败: {}", pattern, e);
            throw e;
        }

        return matchedFiles;
    }

    /**
     * 确保本地目录存在
     */
    private void ensureLocalDirectory() {
        File dir = new File(localPath);
        if (!dir.exists()) {
            dir.mkdirs();
            log.info("创建本地目录: {}", localPath);
        }
    }

    /**
     * 从文件路径提取表名
     * 文件名格式: 表名_FULL_20260127.txt 或 表名_INC_20260127.txt
     */
    private String extractTableNameFromFile(String filePath) {
        String fileName = filePath.substring(filePath.lastIndexOf("/") + 1);
        Pattern pattern = Pattern.compile("^(.+?)_(FULL|INC)_\\d+\\.txt$");
        Matcher matcher = pattern.matcher(fileName);

        if (matcher.find()) {
            return matcher.group(1);
        }

        // 如果没有匹配到，直接去掉扩展名
        int extIndex = fileName.lastIndexOf(".");
        if (extIndex > 0) {
            return fileName.substring(0, extIndex);
        }

        return fileName;
    }

    /**
     * 判断是否全量导入（从文件名）
     */
    private boolean isFullImport(String fileName) {
        return fileName.contains("_FULL");
    }

    /**
     * 删除本地文件
     */
    private void deleteLocalFile(String localFilePath) {
        try {
            File localFile = new File(localFilePath);
            if (localFile.exists()) {
                localFile.delete();
                log.debug("已删除本地文件: {}", localFilePath);
            }
        } catch (Exception e) {
            log.warn("删除本地文件失败: {}", localFilePath, e);
        }
    }

    /**
     * 从配置缓存中获取BLOB字段列表
     */
    private List<String> getBlobColumnsFromCache(String tableName) {
        String blobColumnsStr = tableBlobCache.get(tableName.toUpperCase());
        if (blobColumnsStr == null || blobColumnsStr.trim().isEmpty()) {
            return new ArrayList<>();
        }

        // 按逗号分隔，并转换为大写
        String[] blobArray = blobColumnsStr.split(",");
        List<String> blobColumns = new ArrayList<>();
        for (String blob : blobArray) {
            blobColumns.add(blob.trim().toUpperCase());
        }

        if (!blobColumns.isEmpty()) {
            log.info("表 {} 配置的BLOB字段: {}", tableName, blobColumns);
        }
        return blobColumns;
    }

    /**
     * 导入JSON数据到表（支持BLOB字段）
     */
    @Transactional
    public void importJsonToTable(String tableName, String filePath) throws Exception {
        log.info("开始导入表: {}, 文件: {}", tableName, filePath);

        File dataFile = new File(filePath);
        if (!dataFile.exists()) {
            throw new RuntimeException("数据文件不存在: " + filePath);
        }

        // 从文件名判断是否全量导入
        String fileName = dataFile.getName();
        boolean isFull = isFullImport(fileName);

        // 读取JSON数据
        List<Map<String, Object>> dataList = objectMapper.readValue(
                dataFile,
                new TypeReference<List<Map<String, Object>>>() {}
        );

        if (dataList.isEmpty()) {
            log.info("表 {} 无数据，跳过导入", tableName);
            return;
        }

        log.info("表 {} 共 {} 条数据，{} 导入", tableName, dataList.size(), isFull ? "全量" : "增量(存在则更新)");

        // 如果是全量导入，先清空表
        if (isFull) {
            log.info("全量导入，清空表: {}", tableName);
            dynamicSyncMapper.truncateTable(tableName);
        }

        // 获取目标表的列信息
        List<String> columns = dynamicSyncMapper.getTableColumns(tableName.toUpperCase(), currentSchema);

        if (columns == null || columns.isEmpty()) {
            throw new RuntimeException("表 " + tableName + " 不存在或没有列");
        }

        // 获取时间字段列表
        List<String> timeColumns = getTimeColumns(tableName.toUpperCase());

        // 【从缓存配置中获取BLOB字段列表】
        List<String> blobColumns = getBlobColumnsFromCache(tableName);

        // 转换数据：Map key转大写，并将时间字段转换为Timestamp，BLOB字段转换为byte[]
        List<Map<String, Object>> convertedData = new ArrayList<>();
        for (Map<String, Object> row : dataList) {
            Map<String, Object> convertedRow = new LinkedHashMap<>();
            for (Map.Entry<String, Object> entry : row.entrySet()) {
                String key = entry.getKey().toUpperCase();
                Object value = entry.getValue();

                // 如果是BLOB字段且值为字符串，转换为byte[]
                if (blobColumns.contains(key) && value instanceof String) {
                    String blobStr = (String) value;
                    if (blobStr != null && !blobStr.isEmpty()) {
                        // 检查是否是 Base64 字符串（不是占位符）
                        if (!blobStr.startsWith("[BLOB_")) {
                            byte[] bytes = convertBase64ToBytes(blobStr);
                            if (bytes != null && bytes.length > 0) {
                                convertedRow.put(key, bytes);
                                log.debug("BLOB字段 {} 已转换为byte[]", key);
                            } else {
                                convertedRow.put(key, null);
                                log.warn("BLOB字段 {} 转换失败，设置为null", key);
                            }
                        } else {
                            convertedRow.put(key, null);
                            log.debug("BLOB字段 {} 为占位符，设置为null", key);
                        }
                    } else {
                        convertedRow.put(key, null);
                    }
                }
                // 如果是时间字段且值为字符串，转换为Timestamp
                else if (timeColumns.contains(key) && value instanceof String) {
                    String timeStr = (String) value;
                    if (timeStr != null && !timeStr.isEmpty()) {
                        value = convertToTimestamp(timeStr);
                    }
                }
                convertedRow.put(key, value);
            }
            convertedData.add(convertedRow);
        }

        // 仅CLOB配置表 + 增量文件 才走先删当日再插入；全量保留原有truncate逻辑
        String tableUpper = tableName.toUpperCase();
        if (tableClobCache.containsKey(tableUpper) && !isFull) {
            log.info("表 {} 专用先删后插流程开始执行", tableName);
            String syncDate = extractDateFromFileName(fileName);
            // 从缓存拿当前表配置的timeColumn
            SyncTableConfig cfg = getTableConfig(tableName);
            String delTimeCol = cfg.getTimeColumn();
            deleteDayThenInsert(tableName, columns, convertedData, syncDate, delTimeCol);
            log.info("表 {} 专用先删后插流程执行完毕，删除时间字段：{}，直接返回", tableName, delTimeCol);
            return;
        }

        // ==============================================
        // 核心修改：增量使用MERGE INTO(存在更新/不存在插入)
        // ==============================================
        int totalCount = 0;
        if (isFull) {
            // 全量：批量INSERT
            String insertSql = buildInsertSql(tableName, columns);
            List<Map<String, Object>> batchList = new ArrayList<>();

            for (Map<String, Object> row : convertedData) {
                batchList.add(row);
                if (batchList.size() >= BATCH_SIZE) {
                    totalCount += executeBatchInsert(insertSql, columns, batchList);
                    batchList.clear();
                    log.info("表 {} 已导入 {} 条记录", tableName, totalCount);
                }
            }
            if (!batchList.isEmpty()) {
                totalCount += executeBatchInsert(insertSql, columns, batchList);
            }
        } else {
            // 增量：达梦MERGE INTO 存在则更新，不存在则插入
            List<String> primaryKeys = getTablePrimaryKeys(tableName);
            if (primaryKeys.isEmpty()) {
                throw new RuntimeException("增量同步必须有主键！表：" + tableName + " 未找到主键");
            }
            String mergeSql = buildMergeSql(tableName, columns, primaryKeys);
            List<Map<String, Object>> batchList = new ArrayList<>();

            for (Map<String, Object> row : convertedData) {
                batchList.add(row);
                if (batchList.size() >= BATCH_SIZE) {
                    totalCount += executeBatchMerge(mergeSql, columns, primaryKeys, batchList);
                    batchList.clear();
                    log.info("表 {} 已同步(更新/插入) {} 条记录", tableName, totalCount);
                }
            }
            if (!batchList.isEmpty()) {
                totalCount += executeBatchMerge(mergeSql, columns, primaryKeys, batchList);
            }
        }

        log.info("表 {} 导入完成，共 {} 条记录", tableName, totalCount);
    }

    private SyncTableConfig getTableConfig(String tableName) {
        LambdaQueryWrapper<SyncTableConfig> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(SyncTableConfig::getTableName, tableName.toUpperCase());
        SyncTableConfig config = syncTableConfigMapper.selectOne(wrapper);
        if (config == null || StrUtil.isBlank(config.getTimeColumn())) {
            throw new RuntimeException("表[" + tableName + "]未配置timeColumn，无法执行按日期删除增量逻辑");
        }
        return config;
    }

    /**
     * 将Base64字符串转换为byte[]
     */
    private byte[] convertBase64ToBytes(String base64Str) {
        if (base64Str == null || base64Str.isEmpty()) {
            return null;
        }
        try {
            // 去除可能的引号
            String cleaned = base64Str.trim();
            if (cleaned.startsWith("\"") && cleaned.endsWith("\"")) {
                cleaned = cleaned.substring(1, cleaned.length() - 1);
            }

            log.debug("Base64 字符串长度: {}, 前50字符: {}", cleaned.length(),
                    cleaned.length() > 50 ? cleaned.substring(0, 50) : cleaned);

            byte[] bytes = java.util.Base64.getDecoder().decode(cleaned);
            log.debug("解码后字节数组长度: {}", bytes.length);
            return bytes;
        } catch (Exception e) {
            log.warn("Base64解码失败: {}, 字符串前100字符: {}", e.getMessage(),
                    base64Str.length() > 100 ? base64Str.substring(0, 100) : base64Str);
            return null;
        }
    }

    /**
     * 获取表主键（达梦）
     */
    private List<String> getTablePrimaryKeys(String tableName) {
        String sql = "SELECT COLUMN_NAME FROM ALL_CONS_COLUMNS A " +
                "JOIN ALL_CONSTRAINTS B ON A.CONSTRAINT_NAME = B.CONSTRAINT_NAME AND A.OWNER = B.OWNER " +
                "WHERE B.OWNER = ? AND B.TABLE_NAME = ? AND B.CONSTRAINT_TYPE = 'P' " +
                "ORDER BY A.POSITION";
        try {
            List<String> pks = targetJdbcTemplate.queryForList(sql, String.class, currentSchema, tableName);
            log.info("表 {} 主键: {}", tableName, pks);
            return pks != null ? pks : new ArrayList<>();
        } catch (Exception e) {
            log.error("获取主键失败", e);
            return new ArrayList<>();
        }
    }

    /**
     * 构建达梦MERGE INTO SQL（存在更新，不存在插入）
     * 根据表配置CLOB_COLUMN动态使用TO_CLOB(?)，不再硬编码后缀_OVERTIME
     */
    /**
     * 构建达梦MERGE INTO SQL（存在更新，不存在插入）
     */
    private String buildMergeSql(String tableName, List<String> columns, List<String> primaryKeys) {
        StringBuilder sb = new StringBuilder();
        sb.append("MERGE INTO ").append(tableName).append(" T1 ");
        sb.append("USING (SELECT ");
        for (int i = 0; i < columns.size(); i++) {
            if (i > 0) sb.append(",");
            sb.append("? AS ").append(columns.get(i));
        }
        sb.append(" FROM DUAL) T2 ");
        sb.append("ON (");
        for (int i = 0; i < primaryKeys.size(); i++) {
            if (i > 0) sb.append(" AND ");
            sb.append("T1.").append(primaryKeys.get(i)).append(" = T2.").append(primaryKeys.get(i));
        }
        sb.append(") ");
        sb.append("WHEN MATCHED THEN UPDATE SET ");
        List<String> nonPkColumns = new ArrayList<>();
        for (String col : columns) {
            if (!primaryKeys.contains(col)) nonPkColumns.add(col);
        }
        for (int i = 0; i < nonPkColumns.size(); i++) {
            if (i > 0) sb.append(",");
            sb.append("T1.").append(nonPkColumns.get(i)).append(" = T2.").append(nonPkColumns.get(i));
        }
        sb.append(" WHEN NOT MATCHED THEN INSERT (");
        for (int i = 0; i < columns.size(); i++) {
            if (i > 0) sb.append(",");
            sb.append(columns.get(i));
        }
        sb.append(") VALUES (");
        for (int i = 0; i < columns.size(); i++) {
            if (i > 0) sb.append(",");
            sb.append("T2.").append(columns.get(i));
        }
        sb.append(")");
        return sb.toString();
    }
    /**
     * 批量执行MERGE
     */
    private int executeBatchMerge(String sql, List<String> columns, List<String> primaryKeys, List<Map<String, Object>> batchList) {
        List<Object[]> batchArgs = new ArrayList<>();
        for (Map<String, Object> row : batchList) {
            Object[] args = new Object[columns.size()];
            for (int i = 0; i < columns.size(); i++) {
                args[i] = row.get(columns.get(i));
            }
            batchArgs.add(args);
        }
        int[] results = targetJdbcTemplate.batchUpdate(sql, batchArgs);
        return Arrays.stream(results).sum();
    }

    /**
     * 获取表中的时间类型字段
     */
    private List<String> getTimeColumns(String tableName) {
        String sql = "SELECT COLUMN_NAME FROM ALL_TAB_COLUMNS " +
                "WHERE TABLE_NAME = ? AND OWNER = ? " +
                "AND DATA_TYPE IN ('DATE', 'TIMESTAMP','DATETIME')";
        try {
            List<String> timeColumns = targetJdbcTemplate.queryForList(sql, String.class, tableName, currentSchema);
            log.debug("表 {} 的时间字段: {}", tableName, timeColumns);
            return timeColumns != null ? timeColumns : new ArrayList<>();
        } catch (Exception e) {
            log.warn("获取时间字段失败: {}", e.getMessage());
            return new ArrayList<>();
        }
    }

    /**
     * 将字符串转换为 java.sql.Timestamp（增强版）
     */
    private Timestamp convertToTimestamp(String timeStr) {
        if (timeStr == null || StrUtil.isBlank(timeStr)) {
            log.warn("时间字符串为空，使用当日零点兜底");
            return Timestamp.valueOf(LocalDate.now(java.time.ZoneId.of("Asia/Shanghai")).atStartOfDay());
        }
        timeStr = timeStr.trim();
        if (timeStr.startsWith("\"") && timeStr.endsWith("\"")) {
            timeStr = timeStr.substring(1, timeStr.length() - 1);
        }

        // 1. 带时区 ISO 2026-06-10T14:53:23.853+08:00
        try {
            java.time.OffsetDateTime offsetDateTime = java.time.OffsetDateTime.parse(timeStr);
            LocalDateTime ldt = offsetDateTime.atZoneSameInstant(java.time.ZoneId.of("Asia/Shanghai")).toLocalDateTime();
            return Timestamp.valueOf(ldt);
        } catch (Exception ignored) {}

        // 2. 本地带T 2026-06-10T14:53:23.853
        try {
            java.time.LocalDateTime localDateTime = java.time.LocalDateTime.parse(timeStr);
            return Timestamp.valueOf(localDateTime);
        } catch (Exception ignored) {}

        // 3. yyyy-MM-dd HH:mm:ss.SSS
        try {
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
            LocalDateTime ldt = LocalDateTime.parse(timeStr, formatter);
            return Timestamp.valueOf(ldt);
        } catch (Exception ignored) {}

        // 4. yyyy-MM-dd HH:mm:ss
        try {
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
            LocalDateTime ldt = LocalDateTime.parse(timeStr, formatter);
            return Timestamp.valueOf(ldt);
        } catch (Exception ignored) {}

        // 5. 纯日期 yyyy-MM-dd
        try {
            LocalDate ld = LocalDate.parse(timeStr, DateTimeFormatter.ISO_LOCAL_DATE);
            return Timestamp.valueOf(ld.atStartOfDay());
        } catch (Exception ignored) {}

        // 6. 纯时分秒 HH:mm:ss（解决MINS_TIME数组内时间）
        try {
            DateTimeFormatter timeFmt = DateTimeFormatter.ofPattern("HH:mm:ss");
            LocalTime lt = LocalTime.parse(timeStr, timeFmt);
            LocalDate today = LocalDate.now(java.time.ZoneId.of("Asia/Shanghai"));
            LocalDateTime ldt = LocalDateTime.of(today, lt);
            return Timestamp.valueOf(ldt);
        } catch (Exception e) {
            log.error("所有格式均无法解析时间字符串:[{}]，使用当日零点兜底", timeStr, e);
            // 兜底返回当日零点，防止NOT NULL字段插入null报错
            return Timestamp.valueOf(LocalDate.now(java.time.ZoneId.of("Asia/Shanghai")).atStartOfDay());
        }
    }


    /**
     * 构建INSERT语句
     */
    private String buildInsertSql(String tableName, List<String> columns) {
        StringBuilder sql = new StringBuilder("INSERT INTO ");
        sql.append(tableName).append(" (");
        for (int i = 0; i < columns.size(); i++) {
            if (i > 0) sql.append(", ");
            sql.append(columns.get(i));
        }
        sql.append(") VALUES (");
        for (int i = 0; i < columns.size(); i++) {
            if (i > 0) sql.append(", ");
            sql.append("?");
        }
        sql.append(")");
        return sql.toString();
    }


    /**
     * 执行批量插入
     */
    private int executeBatchInsert(String sql, List<String> columns, List<Map<String, Object>> batchList) {
        List<Object[]> batchArgs = new ArrayList<>();
        for (Map<String, Object> row : batchList) {
            Object[] args = new Object[columns.size()];
            for (int i = 0; i < columns.size(); i++) {
                args[i] = row.get(columns.get(i));
            }
            batchArgs.add(args);
        }
        int[] results = targetJdbcTemplate.batchUpdate(sql, batchArgs);
        return Arrays.stream(results).sum();
    }


    /**
     * 专用：带动态时间字段，先删当日再批量插入
     * @param tableName 表名
     * @param columns 全表字段
     * @param dataList 待入库数据
     * @param fileDate 文件名内日期 yyyyMMdd
     * @param timeColumn 配置的时间字段（如TIME_ID）
     */
    private void deleteDayThenInsert(String tableName, List<String> columns, List<Map<String, Object>> dataList, String fileDate, String timeColumn) throws Exception {
        // 读取本表CLOB字段配置
        String clobText = tableClobCache.get(tableName.toUpperCase());
        List<String> clobFields = Arrays.stream(clobText.split(","))
                .map(String::trim)
                .map(String::toUpperCase)
                .collect(Collectors.toList());

        // 动态拼接删除SQL，使用配置的timeColumn
        String deleteSql = String.format("DELETE FROM %s WHERE TRUNC(%s) = TO_DATE(?, 'yyyyMMdd')", tableName, timeColumn);

        // 2、INSERT SQL 纯?占位符，无TO_CLOB
        StringBuilder colSb = new StringBuilder();
        StringBuilder valSb = new StringBuilder();
        for (String col : columns) {
            if (colSb.length() > 0) {
                colSb.append(",");
                valSb.append(",");
            }
            colSb.append(col);
            valSb.append("?");
        }
        String insertSql = "INSERT INTO " + tableName + "(" + colSb + ") VALUES(" + valSb + ")";

        Connection conn = targetJdbcTemplate.getDataSource().getConnection();
        conn.setAutoCommit(false);
        PreparedStatement delStmt = null;
        PreparedStatement insStmt = null;
        final int BATCH_COMMIT = 1000;
        int batchCnt = 0;

        try {
            // 执行删除当日数据
            delStmt = conn.prepareStatement(deleteSql);
            delStmt.setString(1, fileDate);
            int delRows = delStmt.executeUpdate();
            log.info("专用入库：根据字段{}删除{}日原有{}条记录", timeColumn, fileDate, delRows);

            insStmt = conn.prepareStatement(insertSql);
            for (Map<String, Object> row : dataList) {
                for (int i = 0; i < columns.size(); i++) {
                    String col = columns.get(i);
                    Object val = row.get(col);
                    int paramIndex = i + 1;
                    // CLOB文本使用原生Clob完整存储，不截断
                    if (clobFields.contains(col) && val instanceof String) {
                        Clob clob = conn.createClob();
                        clob.setString(1, (String) val);
                        insStmt.setClob(paramIndex, clob);
                    } else {
                        insStmt.setObject(paramIndex, val);
                    }
                }
                insStmt.addBatch();
                batchCnt++;
                // 分段提交
                if (batchCnt >= BATCH_COMMIT) {
                    insStmt.executeBatch();
                    batchCnt = 0;
                }
            }
            // 提交剩余批次
            if (batchCnt > 0) {
                insStmt.executeBatch();
            }
            conn.commit();
            log.info("专用入库完成，共插入{}条数据", dataList.size());
        } catch (Exception e) {
            conn.rollback();
            log.error("{} 入库失败，事务回滚", tableName, e);
            throw new Exception("入库异常", e);
        } finally {
            if (delStmt != null) delStmt.close();
            if (insStmt != null) insStmt.close();
            if (conn != null) conn.close();
        }
    }
    /**
     * 从文件名提取同步日期 yyyyMMdd
     */
    private String extractDateFromFileName(String fileName) {
        Pattern pattern = Pattern.compile("_(FULL|INC)_(\\d{8})\\.txt");
        Matcher matcher = pattern.matcher(fileName);
        if (matcher.find()) {
            return matcher.group(2);
        }
        throw new RuntimeException("文件无法提取同步日期：" + fileName);
    }


    /**
     * 手动导入指定的远程文件（单个文件处理）
     */
    public void importRemoteFile(String remoteFileName) throws Exception {
        String remoteFilePath = remotePath + remoteFileName;
        String localFilePath = localPath + remoteFileName;

        log.info("手动导入远程文件: {}", remoteFilePath);

        // 下载并删除远程文件
        List<String> singleFileList = Collections.singletonList(remoteFilePath);
        List<DownloadResult> results = SftpUploadUtil.batchDownloadAndDelete(
                ip, port, username, password,
                singleFileList, localPath, true
        );

        if (results.isEmpty() || !results.get(0).isSuccess()) {
            throw new Exception("下载失败: " + remoteFilePath);
        }

        DownloadResult result = results.get(0);
        String tableName = extractTableNameFromFile(remoteFileName);

        try {
            // 缓存表配置
            syncData();

            // 导入数据
            importJsonToTable(tableName, result.getLocalFilePath());

            // 导入成功后删除本地文件
            deleteLocalFile(result.getLocalFilePath());

            log.info("手动导入完成: {}", remoteFileName);
        } catch (Exception e) {
            log.error("手动导入失败: {}", remoteFileName, e);
            throw e;
        }
    }

    /**
     * 获取远程目录下所有待处理文件列表
     */
    public List<String> getPendingFiles() {
        List<String> matchedFiles = new ArrayList<>();
        Vector<?> files;
        try {
            // 列目录也纳入异常捕获
            files = SftpUploadUtil.listFiles(ip, port, username, password, remotePath);
            for (Object obj : files) {
                if (obj instanceof com.jcraft.jsch.ChannelSftp.LsEntry) {
                    com.jcraft.jsch.ChannelSftp.LsEntry entry = (com.jcraft.jsch.ChannelSftp.LsEntry) obj;
                    String fileName = entry.getFilename();
                    matchedFiles.add(fileName);
                }
            }
        } catch (Exception e) {
            log.error("获取待处理文件列表失败，远端目录:{}", remotePath, e);
            return new ArrayList<>();
        }
        return matchedFiles;
    }

    /**
     * 清理本地已处理过的文件
     */
    public void cleanLocalFiles(int daysToKeep) {
        File localDir = new File(localPath);
        if (!localDir.exists()) {
            return;
        }

        long cutoffTime = System.currentTimeMillis() - (daysToKeep * 24L * 60 * 60 * 1000);
        File[] files = localDir.listFiles();

        if (files != null) {
            int deletedCount = 0;
            for (File file : files) {
                if (file.isFile() && file.lastModified() < cutoffTime) {
                    file.delete();
                    deletedCount++;
                }
            }
            log.info("清理了服务器 {} 七天前本地txt文件", deletedCount);
        }
    }


    /**
     * 判断当前字段是否配置为CLOB大文本字段
     * @param col 字段名（大写）
     * @param tableName 表名（大写）
     * @param tableConfigCache 表配置缓存 key=表名大写 value=逗号分隔CLOB字段大写
     */
    private boolean isClobColumn(String col, String tableName, Map<String, String> tableConfigCache) {
        String clobColStr = tableConfigCache.get(tableName);
        if (StrUtil.isBlank(clobColStr)) {
            return false;
        }
        // 分割配置的CLOB字段列表，统一大写匹配
        List<String> clobCols = Arrays.stream(clobColStr.split(","))
                .map(String::trim)
                .map(String::toUpperCase)
                .collect(Collectors.toList());
        return clobCols.contains(col.toUpperCase());
    }

    /**
     * 测试方法
     */
    public void testR() {
        try {
            String remoteFilePath = remotePath + "test.txt";
            String localFilePath = localPath + "test.txt";

            SftpUploadUtil.downloadFile(ip, port, username, password, remoteFilePath, localFilePath,true);

            File file = new File(localFilePath);
            if (file.exists()) {
                String content = new String(Files.readAllBytes(Paths.get(localFilePath)));
                log.info("文件内容: {}", content);
            }

        } catch (Exception e) {
            log.error("获取文件失败", e);
        }
    }
}
