package org.dromara.rfid.helper;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.utils.SpringUtils;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * RFID读取记录分表工具类
 * <p>
 * 按日期分表，表名格式：rfid_read_record_yyyyMMdd
 * </p>
 *
 * @author zch
 * @date 2025-11-26
 */
@Slf4j
public class RfidReadRecordTableHelper {

    /**
     * 基础表名
     */
    public static final String BASE_TABLE_NAME = "rfid_read_record";

    /**
     * 日期格式
     */
    private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");

    /**
     * 表存在缓存（存在的表缓存为 true，不存在的不缓存以便下次重新检查）
     * <p>
     * 使用 ConcurrentHashMap 保证线程安全，避免频繁查询数据库元数据
     * </p>
     */
    private static final Map<String, Boolean> TABLE_EXISTS_CACHE = new ConcurrentHashMap<>(64);

    /**
     * 缓存刷新时间戳（用于定期刷新缓存）
     */
    private static volatile long lastCacheRefreshTime = System.currentTimeMillis();

    /**
     * 缓存有效期（5分钟）
     */
    private static final long CACHE_EXPIRE_MS = 5 * 60 * 1000L;

    /**
     * 私有构造器
     */
    private RfidReadRecordTableHelper() {
    }

    /**
     * 清除表存在缓存
     * <p>
     * 当新建分表后可调用此方法刷新缓存
     * </p>
     */
    public static void clearTableExistsCache() {
        TABLE_EXISTS_CACHE.clear();
        lastCacheRefreshTime = System.currentTimeMillis();
        log.debug("分表缓存已清除");
    }

    /**
     * 根据日期获取表名
     *
     * @param date 日期
     * @return 表名，如 rfid_read_record_20251126
     */
    public static String getTableName(Date date) {
        if (date == null) {
            date = new Date();
        }
        String dateSuffix = DateUtil.format(date, "yyyyMMdd");
        return BASE_TABLE_NAME + "_" + dateSuffix;
    }

    /**
     * 根据 LocalDate 获取表名
     *
     * @param localDate 日期
     * @return 表名
     */
    public static String getTableName(LocalDate localDate) {
        if (localDate == null) {
            localDate = LocalDate.now();
        }
        return BASE_TABLE_NAME + "_" + localDate.format(DATE_FORMATTER);
    }

    /**
     * 获取当天的表名
     *
     * @return 当天表名
     */
    public static String getTodayTableName() {
        return getTableName(LocalDate.now());
    }

    /**
     * 根据日期范围获取所有涉及的表名列表
     * <p>
     * 用于跨日期范围查询场景
     * </p>
     *
     * @param beginDate 开始日期（含）
     * @param endDate   结束日期（含）
     * @return 表名列表
     */
    public static List<String> getTableNames(Date beginDate, Date endDate) {
        List<String> tableNames = new ArrayList<>();

        if (beginDate == null && endDate == null) {
            // 无日期范围，返回当天表
            tableNames.add(getTodayTableName());
            return tableNames;
        }

        // 处理单边日期的情况
        LocalDate start = beginDate != null
            ? DateUtil.toLocalDateTime(beginDate).toLocalDate()
            : LocalDate.now();
        LocalDate end = endDate != null
            ? DateUtil.toLocalDateTime(endDate).toLocalDate()
            : LocalDate.now();

        // 确保 start <= end
        if (start.isAfter(end)) {
            LocalDate temp = start;
            start = end;
            end = temp;
        }

        // 遍历日期范围生成表名
        LocalDate current = start;
        while (!current.isAfter(end)) {
            tableNames.add(getTableName(current));
            current = current.plusDays(1);
        }

        return tableNames;
    }

    /**
     * 从表名解析日期后缀
     *
     * @param tableName 表名
     * @return 日期字符串，如 20251126
     */
    public static String parseDateSuffix(String tableName) {
        if (StrUtil.isBlank(tableName) || !tableName.startsWith(BASE_TABLE_NAME + "_")) {
            return null;
        }
        return tableName.substring((BASE_TABLE_NAME + "_").length());
    }

    /**
     * 检查表是否存在（带缓存）
     * <p>
     * 使用缓存减少数据库元数据查询，存在的表会被缓存，不存在的表不缓存以便下次重新检查
     * </p>
     *
     * @param tableName 表名
     * @return 是否存在
     */
    public static boolean checkTableExists(String tableName) {
        // 检查缓存是否过期
        long now = System.currentTimeMillis();
        if (now - lastCacheRefreshTime > CACHE_EXPIRE_MS) {
            TABLE_EXISTS_CACHE.clear();
            lastCacheRefreshTime = now;
        }

        // 从缓存获取
        Boolean cached = TABLE_EXISTS_CACHE.get(tableName);
        if (cached != null) {
            return cached;
        }

        // 查询数据库
        boolean exists = doCheckTableExists(tableName);

        // 只缓存存在的表（不存在的表可能后续会创建）
        if (exists) {
            TABLE_EXISTS_CACHE.put(tableName, Boolean.TRUE);
        }

        return exists;
    }

    /**
     * 实际检查表是否存在（使用 information_schema 查询，比 JDBC 元数据更快）
     */
    private static boolean doCheckTableExists(String tableName) {
        try {
            DataSource dataSource = SpringUtils.getBean(DataSource.class);
            try (Connection conn = dataSource.getConnection()) {
                // 使用 information_schema 查询，性能优于 DatabaseMetaData
                String sql = "SELECT 1 FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = ? LIMIT 1";
                try (PreparedStatement stmt = conn.prepareStatement(sql)) {
                    stmt.setString(1, tableName);
                    try (ResultSet rs = stmt.executeQuery()) {
                        return rs.next();
                    }
                }
            }
        } catch (Exception e) {
            log.warn("检查表 {} 是否存在时出错: {}", tableName, e.getMessage());
            return false;
        }
    }

    /**
     * 根据日期范围获取所有实际存在的表名列表
     * <p>
     * 只返回数据库中实际存在的分表
     * </p>
     *
     * @param beginDate 开始日期（含）
     * @param endDate   结束日期（含）
     * @return 实际存在的表名列表
     */
    public static List<String> getExistingTableNames(Date beginDate, Date endDate) {
        List<String> allTableNames = getTableNames(beginDate, endDate);
        return allTableNames.stream()
            .filter(RfidReadRecordTableHelper::checkTableExists)
            .collect(Collectors.toList());
    }
}
