package com.market.service;

import com.baomidou.dynamic.datasource.annotation.DS;
import com.market.entity.Kline;
import com.market.mapper.SpotKlineMapper;
import com.market.model.binance.SpotKlineMessage;
import com.market.utils.SpotConstant;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.Collections;
import java.util.List;

/**
 * 现货 K 线数据落库服务（使用 spot_kline 数据源）
 *
 * @author system
 * @date 2026-05-07
 */
@Service
public class SpotKlineRecordService {

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

    private final SpotKlineMapper klineMapper;

    public SpotKlineRecordService(SpotKlineMapper klineMapper) {
        this.klineMapper = klineMapper;
    }

    /**
     * 保存或更新 K 线数据（upsert）
     *
     * @param symbol   交易对（小写）
     * @param klineMsg 币安 K 线消息
     */
    @DS("market")
    public void saveKlineData(String symbol, SpotKlineMessage.Kline klineMsg) {
        try {
            String tableName = "kline_" + symbol.toLowerCase().replace("-", "");
            String interval = klineMsg.getInterval();
            String period = SpotConstant.KLINE_PERIOD_MAP.get(interval);
            if (period == null) {
                return;
            }

            // 1. 确保表存在
            ensureTableExists(tableName);

            // 2. 构建 K 线实体
            Kline kline = buildSpotKline(klineMsg, period);

            // 3. 判断新增还是更新
            Kline existing = klineMapper.selectByPeriodAndBeginTime(tableName, period, klineMsg.getStartTime());
            if (existing != null) {
                kline.setId(existing.getId());
                klineMapper.updateKline(tableName, kline);
            } else {
                try {
                    klineMapper.insertKline(tableName, kline);
                } catch (DuplicateKeyException dup) {
                    // 唯一键冲突：多节点并发写入同一根 K 线，另一节点已写入，内存已更新，直接跳过
                    log.debug("现货 K 线多节点重复写入跳过: symbol={}, interval={}, beginTime={}",
                            symbol, klineMsg.getInterval(), klineMsg.getStartTime());
                }
            }
        } catch (Exception e) {
            log.error("保存现货 K 线数据失败: symbol={}, interval={}", symbol, klineMsg.getInterval(), e);
        }
    }

    /**
     * 直接按 period + beginTime 更新 K 线（无 SELECT，用于实时同周期 5s 节流写库）
     *
     * @param symbol   交易对（小写）
     * @param klineMsg 币安 K 线消息
     */
    @DS("market")
    public void updateKlineDirect(String symbol, SpotKlineMessage.Kline klineMsg) {
        try {
            String tableName = "kline_" + symbol.toLowerCase().replace("-", "");
            String interval  = klineMsg.getInterval();
            String period    = SpotConstant.KLINE_PERIOD_MAP.get(interval);
            if (period == null) {
                return;
            }
            Kline kline = buildSpotKline(klineMsg, period);
            klineMapper.updateKlineByTime(tableName, kline);
        } catch (Exception e) {
            log.error("直接更新现货 K 线失败: symbol={}, interval={}", symbol, klineMsg.getInterval(), e);
        }
    }

    /**
     * 查询指定 symbol 在某一时间点的 K 线记录（按 period + beginTime 精确匹配）
     * <p>
     * 主要用于获取 24 小时前的 1m 开盘价，供行情机器人 ticker 构建使用。
     *
     * @param symbol    交易对（小写）
     * @param period    数据库周期字符串（如 "1MIN"）
     * @param beginTime 开始时间戳（毫秒）
     * @return K 线实体，不存在或查询失败返回 null
     */
    @DS("market")
    public Kline queryKlineByBeginTime(String symbol, String period, long beginTime) {
        try {
            String tableName = "kline_" + symbol.toLowerCase().replace("-", "");
            return klineMapper.selectByPeriodAndBeginTime(tableName, period, beginTime);
        } catch (Exception e) {
            log.warn("查询24h前K线失败: symbol={}, period={}, beginTime={}", symbol, period, beginTime, e);
            return null;
        }
    }

    /**
     * 查询指定 symbol 和 period 的最新一条 K 线记录（按 beginTime 倒序取第一条）
     * <p>
     * 主要用于机器人启动时预热缓存，使新建 kline 的 open 与历史 close 保持连续。
     *
     * @param symbol 交易对（小写）
     * @param period 数据库周期字符串（如 "1MIN"）
     * @return 最新 K 线实体，不存在或查询失败返回 null
     */
    @DS("market")
    public Kline queryLatestKline(String symbol, String period) {
        try {
            String tableName = "kline_" + symbol.toLowerCase().replace("-", "");
            List<Kline> list = klineMapper.selectByPeriodAndTimeRange(tableName, period, null, null, 1);
            return (list != null && !list.isEmpty()) ? list.get(0) : null;
        } catch (Exception e) {
            log.warn("查询最新K线失败: symbol={}, period={}", symbol, period, e);
            return null;
        }
    }

    // ------------------------------------------------------------------ private

    /**
     * 查询指定 symbol 在某时间点之前的 K 线列表（按 beginTime 倒序）
     * <p>
     * 主要用于 24h 合成 K 线，取当前小时之前的 23 根 1h 历史线。
     *
     * @param symbol   交易对（小写）
     * @param period   数据库周期字符串（如 "1HOUR"）
     * @param beforeMs 时间上限（毫秒，不含），即 beginTime &lt; beforeMs
     * @param limit    最多返回条数
     * @return K 线列表（倒序），查询失败返回空列表
     */
    @DS("market")
    public List<Kline> queryKlinesBefore(String symbol, String period, long beforeMs, int limit) {
        try {
            String tableName = "kline_" + symbol.toLowerCase().replace("-", "");
            return klineMapper.selectByPeriodAndTimeRange(tableName, period, null, beforeMs - 1, limit);
        } catch (Exception e) {
            log.warn("查询历史K线失败: symbol={}, period={}, beforeMs={}", symbol, period, beforeMs, e);
            return Collections.emptyList();
        }
    }

    /** 构建 Kline 实体 */
    private Kline buildSpotKline(SpotKlineMessage.Kline klineMsg, String period) {
        Kline kline = new Kline();
        kline.setPeriod(period);
        kline.setOpen(new BigDecimal(klineMsg.getOpen()));
        kline.setClose(new BigDecimal(klineMsg.getClose()));
        kline.setHigh(new BigDecimal(klineMsg.getHigh()));
        kline.setLow(new BigDecimal(klineMsg.getLow()));
        kline.setVolume(new BigDecimal(klineMsg.getVolume()));
        kline.setTurnover(new BigDecimal(klineMsg.getQuoteAssetVolume()));
        kline.setBeginTime(klineMsg.getStartTime());
        kline.setEndTime(klineMsg.getEndTime());
        return kline;
    }

    /**
     * 确保指定 symbol 的 K 线分表存在，可供外部预建表调用
     *
     * @param symbol 交易对（大小写均可）
     */
    @DS("market")
    public void ensureKlineTable(String symbol) {
        String tableName = "kline_" + symbol.toLowerCase().replace("-", "");
        ensureTableExists(tableName);
    }

    /** 确保 K 线表存在（使用 Caffeine 缓存避免重复检查） */
    @DS("market")
    private void ensureTableExists(String tableName) {
        try {
            Boolean cached = SpotConstant.KLINE_TABLE_EXISTS_CACHE.getIfPresent(tableName);
            if (Boolean.TRUE.equals(cached)) {
                return;
            }
            int count = klineMapper.checkTableExists(tableName);
            if (count == 0) {
                klineMapper.createKlineTable(tableName);
                log.info("创建现货 K 线表: {}", tableName);
            }
            SpotConstant.KLINE_TABLE_EXISTS_CACHE.put(tableName, true);
        } catch (Exception e) {
            log.error("确保现货 K 线表存在失败: {}", tableName, e);
        }
    }
}
