package com.spot.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.spot.entity.ConfigSymbolSlippage;
import com.spot.mapper.ConfigSymbolSlippageMapper;
import com.spot.service.ConfigSymbolSlippageService;
import com.spot.utils.Constant;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * 交易对滑点配置服务实现类
 * <p>
 * 启动时全量加载所有启用的滑点配置到内存，每 10 分钟定时刷新。
 * 查询时先走内存，内存未命中再查库并回填，保证撮合热路径不依赖数据库。
 *
 * @author jean
 * @date 2026-05-08
 */
@Slf4j
@Service
public class ConfigSymbolSlippageServiceImpl
        extends ServiceImpl<ConfigSymbolSlippageMapper, ConfigSymbolSlippage>
        implements ConfigSymbolSlippageService {

    /** symbol → 启用的滑点区间列表（按 sort 升序），全量内存缓存 */
    private volatile Map<String, List<ConfigSymbolSlippage>> localCache = new ConcurrentHashMap<>();

    // ------------------------------------------------------------------ 缓存生命周期

    /**
     * 启动时全量加载滑点配置到内存
     */
    @PostConstruct
    public void init() {
        loadAll();
    }

    /**
     * 每 10 分钟定时刷新内存缓存
     */
    @Scheduled(fixedRate = 10 * 60 * 1000L)
    public void refresh() {
        log.info("定时刷新滑点配置缓存...");
        loadAll();
    }

    // ------------------------------------------------------------------ 接口实现

    /**
     * 查询指定交易对的所有滑点区间配置（包含禁用项），按 sort 升序返回
     *
     * @param symbol 交易对符号（如：BTCUSDT）
     * @return 滑点配置列表，无数据时返回空列表
     */
    @Override
    public List<ConfigSymbolSlippage> listBySymbol(String symbol) {
        return list(new LambdaQueryWrapper<ConfigSymbolSlippage>()
                .eq(ConfigSymbolSlippage::getSymbol, symbol)
                .orderByAsc(ConfigSymbolSlippage::getSort));
    }

    /**
     * 根据交易对和成交额查询匹配的滑点比例
     * 先走内存缓存，内存未命中再查库回填；按 sort 升序遍历，取第一个满足区间的配置
     *
     * @param symbol      交易对符号（如：BTCUSDT）
     * @param tradeAmount 成交额（计价币）
     * @return 滑点比例，无匹配时返回 ZERO
     */
    @Override
    public BigDecimal getSlippage(String symbol, BigDecimal tradeAmount) {
        if (symbol == null || tradeAmount == null) {
            return BigDecimal.ZERO;
        }

        // 1. 先查内存
        List<ConfigSymbolSlippage> configs = localCache.get(symbol);

        // 2. 内存未命中：查库并回填
        if (configs == null) {
            configs = loadFromDb(symbol);
            if (configs != null && !configs.isEmpty()) {
                localCache.put(symbol, configs);
            }
        }

        if (configs == null || configs.isEmpty()) {
            return BigDecimal.ZERO;
        }

        // 3. 遍历区间，取第一个匹配的
        for (ConfigSymbolSlippage config : configs) {
            if (matchesRange(config, tradeAmount)) {
                return config.getSlippage();
            }
        }
        return BigDecimal.ZERO;
    }

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

    /**
     * 全量加载所有启用的滑点配置，按 symbol 分组后替换内存缓存
     */
    private void loadAll() {
        try {
            List<ConfigSymbolSlippage> all = list(new LambdaQueryWrapper<ConfigSymbolSlippage>()
                    .eq(ConfigSymbolSlippage::getIsOpen, Constant.IS_OPEN_ENABLED)
                    .orderByAsc(ConfigSymbolSlippage::getSymbol)
                    .orderByAsc(ConfigSymbolSlippage::getSort));

            // 按 symbol 分组
            Map<String, List<ConfigSymbolSlippage>> newCache = all.stream()
                    .collect(Collectors.groupingBy(ConfigSymbolSlippage::getSymbol));

            localCache = new ConcurrentHashMap<>(newCache);
            log.info("滑点配置缓存加载完成，共 {} 个交易对，{} 条配置", localCache.size(), all.size());
        } catch (Exception e) {
            log.error("加载滑点配置失败", e);
        }
    }

    /**
     * 从数据库查询指定交易对的启用配置（按 sort 升序）
     *
     * @param symbol 交易对符号
     * @return 滑点配置列表，无数据时返回空列表
     */
    private List<ConfigSymbolSlippage> loadFromDb(String symbol) {
        try {
            return list(new LambdaQueryWrapper<ConfigSymbolSlippage>()
                    .eq(ConfigSymbolSlippage::getSymbol, symbol)
                    .eq(ConfigSymbolSlippage::getIsOpen, Constant.IS_OPEN_ENABLED)
                    .orderByAsc(ConfigSymbolSlippage::getSort));
        } catch (Exception e) {
            log.error("从库查询滑点配置失败: symbol={}", symbol, e);
            return Collections.emptyList();
        }
    }

    /**
     * 判断成交额是否落在滑点区间内
     * 规则：amount_min <= tradeAmount < amount_max（amount_max 为 null 或 -1 表示无上限）
     *
     * @param config      滑点配置
     * @param tradeAmount 成交额
     * @return 是否匹配
     */
    private boolean matchesRange(ConfigSymbolSlippage config, BigDecimal tradeAmount) {
        // 下限：tradeAmount >= amount_min
        if (tradeAmount.compareTo(config.getAmountMin()) < 0) {
            return false;
        }
        // 上限：amount_max 为 null 或 -1 表示无上限，否则 tradeAmount < amount_max
        BigDecimal amountMax = config.getAmountMax();
        if (amountMax != null && amountMax.compareTo(Constant.SLIPPAGE_AMOUNT_MAX_UNLIMITED) != 0
                && tradeAmount.compareTo(amountMax) >= 0) {
            return false;
        }
        return true;
    }
}
