package com.market.service;

import com.baomidou.dynamic.datasource.annotation.DS;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.market.constant.SpotRedisKey;
import com.market.entity.Kline;
import com.market.mapper.SpotKlineMapper;
import com.market.model.SpotBaseMessage;
import com.market.utils.SpotConstant;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * 现货数据查询服务（响应前端 req 请求）
 * <p>
 * 支持：K线历史、深度数据、成交记录、多日行情查询。
 *
 * @author system
 * @date 2026-05-07
 */
@Service
public class SpotDataQueryService {

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

    private final StringRedisTemplate redisTemplate;
    private final SpotKlineMapper klineMapper;
    private final ObjectMapper objectMapper;

    public SpotDataQueryService(StringRedisTemplate redisTemplate,
                                SpotKlineMapper klineMapper) {
        this.redisTemplate = redisTemplate;
        this.klineMapper   = klineMapper;
        this.objectMapper  = new ObjectMapper();
    }

    /**
     * 根据 channel 类型查询对应数据
     *
     * @param channel   channel 名（小写）
     * @param message   原始请求消息（含参数）
     * @param isFlutter 是否 Flutter 客户端
     * @return 响应 Map，查询失败返回 null
     */
    public Map<String, Object> queryData(String channel, SpotBaseMessage message, boolean isFlutter) {
        try {
            long timestamp = System.currentTimeMillis();
            String symbol  = message.getSymbol();
            if (symbol == null || symbol.isEmpty()) {
                return null;
            }

            Map<String, Object> response = new HashMap<>();
            response.put(SpotConstant.RESPONSE_FIELD_CHANNEL, channel);
            response.put(SpotConstant.RESPONSE_FIELD_TS, timestamp);
            response.put(SpotConstant.RESPONSE_FIELD_EVENT_REP, SpotConstant.EVENT_REP);

            // 1. K线历史
            if (channel.contains(SpotConstant.CHANNEL_TYPE_KLINE)) {
                return queryKlineData(channel, message, response, symbol);
            }
            // 2. 深度数据
            if (channel.contains(SpotConstant.CHANNEL_TYPE_DEPTH)) {
                response.put(SpotConstant.RESPONSE_FIELD_STATUS, SpotConstant.STATUS_OK);
                response.put(SpotConstant.RESPONSE_FIELD_EVENT, SpotConstant.EVENT_REQ);
                return queryDepthData(channel, response, symbol);
            }
            // 3. 成交记录
            if (channel.contains(SpotConstant.CHANNEL_TYPE_TRADE_TICKER)) {
                response.put(SpotConstant.RESPONSE_FIELD_STATUS, SpotConstant.STATUS_OK);
                response.put(SpotConstant.RESPONSE_FIELD_EVENT, SpotConstant.EVENT_REQ);
                return queryTradeData(channel, message, response, symbol);
            }
            // 4. 多日行情
            if (channel.contains(SpotConstant.CHANNEL_TYPE_DETAIL_TRADE)) {
                response.put(SpotConstant.RESPONSE_FIELD_STATUS, SpotConstant.STATUS_OK);
                response.put(SpotConstant.RESPONSE_FIELD_EVENT, SpotConstant.EVENT_REQ);
                return queryDetailTradeData(channel, response, symbol);
            }

            return response;
        } catch (Exception e) {
            log.error("查询数据失败: channel={}", channel, e);
            return null;
        }
    }

    // ------------------------------------------------------------------ K线历史

    private Map<String, Object> queryKlineData(String channel, SpotBaseMessage message,
                                               Map<String, Object> response, String symbol) {
        try {
            String[] channelArr = channel.split("_");
            if (channelArr.length < SpotConstant.CHANNEL_MIN_PARTS_LENGTH) {
                return response;
            }
            String flutterPeriod = channelArr[3];
            String dbPeriod = SpotConstant.FLUTTER_KLINE_TO_DB_PERIOD.get(flutterPeriod);
            if (dbPeriod == null) {
                return response;
            }

            int  pageSize = SpotConstant.DEFAULT_KLINE_PAGE_SIZE;
            long endIdx   = 0;
            if (message.getParams() != null) {
                if (message.getParams().containsKey(SpotConstant.PARAM_FIELD_PAGE_SIZE)) {
                    pageSize = ((Number) message.getParams().get(SpotConstant.PARAM_FIELD_PAGE_SIZE)).intValue();
                }
                if (message.getParams().containsKey(SpotConstant.PARAM_FIELD_END_IDX)) {
                    endIdx = ((Number) message.getParams().get(SpotConstant.PARAM_FIELD_END_IDX)).longValue();
                }
            }

            // 季线（3MONTH）和年线（1YEAR）不从币安获取，由月线聚合计算
            List<Map<String, Object>> dataResult;
            if ("3MONTH".equals(dbPeriod)) {
                dataResult = queryQuarterlyKlineAggregated(symbol, endIdx, pageSize);
            } else if ("1YEAR".equals(dbPeriod)) {
                dataResult = queryYearlyKlineAggregated(symbol, endIdx, pageSize);
            } else {
                dataResult = queryKlineFromDatabase(symbol, dbPeriod, endIdx, pageSize);
            }
            response.put(SpotConstant.RESPONSE_FIELD_DATA, dataResult);
        } catch (Exception e) {
            log.error("查询 K 线数据失败: channel={}, symbol={}", channel, symbol, e);
            response.put(SpotConstant.RESPONSE_FIELD_DATA, new ArrayList<>());
        }
        return response;
    }

    /**
     * 从数据库查询 K 线数据（使用 spot_kline 数据源）
     *
     * @param symbol   交易对（小写）
     * @param dbPeriod 数据库周期
     * @param endIdx   结束索引（0表示不限）
     * @param pageSize 每页条数
     * @return K 线数据列表
     */
    @DS("market")
    public List<Map<String, Object>> queryKlineFromDatabase(String symbol, String dbPeriod,
                                                             long endIdx, int pageSize) {
        List<Map<String, Object>> result = new ArrayList<>();
        try {
            String tableName = SpotConstant.TABLE_KLINE_PREFIX + symbol.toLowerCase();
            Long endTime = endIdx > 0 ? endIdx * SpotConstant.MILLISECONDS_TO_SECONDS : null;

            List<Kline> klines = klineMapper.selectByPeriodAndTimeRange(
                    tableName, dbPeriod, null, endTime, pageSize);

            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            for (int i = klines.size() - 1; i >= 0; i--) {
                Kline kline = klines.get(i);
                long id = kline.getBeginTime() / SpotConstant.MILLISECONDS_TO_SECONDS;
                Map<String, Object> klineData = new HashMap<>();
                klineData.put(SpotConstant.DATA_FIELD_ID,     id);
                klineData.put(SpotConstant.DATA_FIELD_OPEN,   kline.getOpen());
                klineData.put(SpotConstant.DATA_FIELD_CLOSE,  kline.getClose());
                klineData.put(SpotConstant.DATA_FIELD_HIGH,   kline.getHigh());
                klineData.put(SpotConstant.DATA_FIELD_LOW,    kline.getLow());
                klineData.put(SpotConstant.DATA_FIELD_VOL,    kline.getVolume());
                klineData.put(SpotConstant.DATA_FIELD_AMOUNT, kline.getTurnover());
                klineData.put(SpotConstant.DATA_FIELD_DS,     sdf.format(new Date(id * SpotConstant.MILLISECONDS_TO_SECONDS)));
                result.add(klineData);
            }
        } catch (Exception e) {
            log.error("从数据库查询 K 线数据失败: symbol={}, period={}", symbol, dbPeriod, e);
        }
        return result;
    }

    // ------------------------------------------------------------------ 季线聚合（从月线计算）

    /**
     * 查询季线数据：从月线（1MONTH）按自然季度聚合，按时间升序返回。
     * <p>
     * 季度定义：Q1=1-3月，Q2=4-6月，Q3=7-9月，Q4=10-12月。
     * open=首月开盘价，close=末月收盘价，high/low 取极值，volume/amount 累加。
     *
     * @param symbol   交易对（小写）
     * @param endIdx   结束索引（秒，0 表示不限）
     * @param pageSize 最大季度数
     * @return 季线数据列表（升序）
     */
    @DS("market")
    private List<Map<String, Object>> queryQuarterlyKlineAggregated(String symbol, long endIdx, int pageSize) {
        List<Map<String, Object>> result = new ArrayList<>();
        try {
            String tableName = SpotConstant.TABLE_KLINE_PREFIX + symbol.toLowerCase();
            int monthLimit = pageSize * 3;
            Long endTime = endIdx > 0 ? endIdx * SpotConstant.MILLISECONDS_TO_SECONDS : null;

            List<Kline> monthly = klineMapper.selectByPeriodAndTimeRange(
                    tableName, "1MONTH", null, endTime, monthLimit);

            if (monthly.isEmpty()) {
                return result;
            }

            // 按自然季度分组（UTC），倒序→升序
            Map<Integer, List<Kline>> quarterGroups = new LinkedHashMap<>();
            Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
            for (int i = monthly.size() - 1; i >= 0; i--) {
                Kline k = monthly.get(i);
                cal.setTimeInMillis(k.getBeginTime());
                int quarterKey = cal.get(Calendar.YEAR) * 4 + cal.get(Calendar.MONTH) / 3;
                quarterGroups.computeIfAbsent(quarterKey, q -> new ArrayList<>()).add(k);
            }

            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            sdf.setTimeZone(TimeZone.getTimeZone("UTC"));

            List<Integer> keys = new ArrayList<>(quarterGroups.keySet());
            int startIdx = Math.max(0, keys.size() - pageSize);
            for (int ki = startIdx; ki < keys.size(); ki++) {
                int quarterKey = keys.get(ki);
                List<Kline> months = quarterGroups.get(quarterKey);

                int year = quarterKey / 4;
                int startMonth = (quarterKey % 4) * 3;
                cal.set(year, startMonth, 1, 0, 0, 0);
                cal.set(Calendar.MILLISECOND, 0);
                long quarterBeginMs = cal.getTimeInMillis();
                long id = quarterBeginMs / SpotConstant.MILLISECONDS_TO_SECONDS;

                BigDecimal high = months.get(0).getHigh();
                BigDecimal low  = months.get(0).getLow();
                BigDecimal vol  = BigDecimal.ZERO;
                BigDecimal turn = BigDecimal.ZERO;
                for (Kline m : months) {
                    if (m.getHigh().compareTo(high) > 0) {
                        high = m.getHigh();
                    }
                    if (m.getLow().compareTo(low) < 0) {
                        low = m.getLow();
                    }
                    vol  = vol.add(m.getVolume());
                    turn = turn.add(m.getTurnover());
                }

                Map<String, Object> klineData = new HashMap<>();
                klineData.put(SpotConstant.DATA_FIELD_ID,     id);
                klineData.put(SpotConstant.DATA_FIELD_OPEN,   months.get(0).getOpen());
                klineData.put(SpotConstant.DATA_FIELD_CLOSE,  months.get(months.size() - 1).getClose());
                klineData.put(SpotConstant.DATA_FIELD_HIGH,   high);
                klineData.put(SpotConstant.DATA_FIELD_LOW,    low);
                klineData.put(SpotConstant.DATA_FIELD_VOL,    vol);
                klineData.put(SpotConstant.DATA_FIELD_AMOUNT, turn);
                klineData.put(SpotConstant.DATA_FIELD_DS,     sdf.format(new Date(quarterBeginMs)));
                result.add(klineData);
            }
        } catch (Exception e) {
            log.error("聚合季线数据失败: symbol={}", symbol, e);
        }
        return result;
    }

    // ------------------------------------------------------------------ 年线聚合（从月线计算）

    /**
     * 查询年线数据：从月线（1MONTH）按自然年聚合，按时间升序返回。
     * <p>
     * 币安不提供年线，year K-line 由月线聚合得到：
     * open=首月开盘价，close=末月收盘价，high=最高，low=最低，volume/amount 累加。
     *
     * @param symbol   交易对（小写）
     * @param endIdx   结束索引（秒，0 表示不限）
     * @param pageSize 最大年数
     * @return 年线数据列表（升序）
     */
    @DS("market")
    private List<Map<String, Object>> queryYearlyKlineAggregated(String symbol, long endIdx, int pageSize) {
        List<Map<String, Object>> result = new ArrayList<>();
        try {
            String tableName = SpotConstant.TABLE_KLINE_PREFIX + symbol.toLowerCase();
            // 年线最多 pageSize 年，月线最多需要 pageSize*12 条
            int monthLimit = pageSize * 12;
            Long endTime = endIdx > 0 ? endIdx * SpotConstant.MILLISECONDS_TO_SECONDS : null;

            List<Kline> monthly = klineMapper.selectByPeriodAndTimeRange(
                    tableName, "1MONTH", null, endTime, monthLimit);

            if (monthly.isEmpty()) {
                return result;
            }

            // 按自然年分组（UTC），倒序→升序：先按年倒序分组后，再逆序输出
            Map<Integer, List<Kline>> yearGroups = new LinkedHashMap<>();
            Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
            // monthly 查询结果是倒序（最新在前），需要逆序遍历使年份从早到晚
            for (int i = monthly.size() - 1; i >= 0; i--) {
                Kline k = monthly.get(i);
                cal.setTimeInMillis(k.getBeginTime());
                int year = cal.get(Calendar.YEAR);
                yearGroups.computeIfAbsent(year, y -> new ArrayList<>()).add(k);
            }

            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            sdf.setTimeZone(TimeZone.getTimeZone("UTC"));

            // 聚合并按升序添加，最多取 pageSize 条
            List<Integer> years = new ArrayList<>(yearGroups.keySet());
            int startIdx = Math.max(0, years.size() - pageSize);
            for (int yi = startIdx; yi < years.size(); yi++) {
                int year = years.get(yi);
                List<Kline> months = yearGroups.get(year);

                cal.set(year, Calendar.JANUARY, 1, 0, 0, 0);
                cal.set(Calendar.MILLISECOND, 0);
                long yearBeginMs = cal.getTimeInMillis();
                long id = yearBeginMs / SpotConstant.MILLISECONDS_TO_SECONDS;

                BigDecimal high = months.get(0).getHigh();
                BigDecimal low  = months.get(0).getLow();
                BigDecimal vol  = BigDecimal.ZERO;
                BigDecimal turn = BigDecimal.ZERO;
                for (Kline m : months) {
                    if (m.getHigh().compareTo(high) > 0) {
                        high = m.getHigh();
                    }
                    if (m.getLow().compareTo(low) < 0) {
                        low = m.getLow();
                    }
                    vol  = vol.add(m.getVolume());
                    turn = turn.add(m.getTurnover());
                }

                Map<String, Object> klineData = new HashMap<>();
                klineData.put(SpotConstant.DATA_FIELD_ID,     id);
                klineData.put(SpotConstant.DATA_FIELD_OPEN,   months.get(0).getOpen());
                klineData.put(SpotConstant.DATA_FIELD_CLOSE,  months.get(months.size() - 1).getClose());
                klineData.put(SpotConstant.DATA_FIELD_HIGH,   high);
                klineData.put(SpotConstant.DATA_FIELD_LOW,    low);
                klineData.put(SpotConstant.DATA_FIELD_VOL,    vol);
                klineData.put(SpotConstant.DATA_FIELD_AMOUNT, turn);
                klineData.put(SpotConstant.DATA_FIELD_DS,     sdf.format(new Date(yearBeginMs)));
                result.add(klineData);
            }
        } catch (Exception e) {
            log.error("聚合年线数据失败: symbol={}", symbol, e);
        }
        return result;
    }

    // ------------------------------------------------------------------ 深度数据

    private Map<String, Object> queryDepthData(String channel, Map<String, Object> response, String symbol) {
        try {
            String askKey = SpotConstant.REDIS_DEPTH_ASK_PREFIX + symbol;
            String bidKey = SpotConstant.REDIS_DEPTH_BID_PREFIX + symbol;

            String askData = redisTemplate.opsForValue().get(askKey);
            String bidData = redisTemplate.opsForValue().get(bidKey);

            List<List<String>> asks = parseDepthList(askData);
            List<List<String>> bids = parseDepthList(bidData);

            if (asks.size() > SpotConstant.DEFAULT_DEPTH_LIMIT) {
                asks = asks.subList(0, SpotConstant.DEFAULT_DEPTH_LIMIT);
            }
            if (bids.size() > SpotConstant.DEFAULT_DEPTH_LIMIT) {
                bids = bids.subList(0, SpotConstant.DEFAULT_DEPTH_LIMIT);
            }

            Map<String, Object> tick = new HashMap<>();
            tick.put(SpotConstant.DATA_FIELD_ASKS, asks);
            tick.put(SpotConstant.DATA_FIELD_BUYS, bids);
            response.put(SpotConstant.RESPONSE_FIELD_TICK, tick);
        } catch (Exception e) {
            log.error("查询深度数据失败: channel={}, symbol={}", channel, symbol, e);
            Map<String, Object> tick = new HashMap<>();
            tick.put(SpotConstant.DATA_FIELD_ASKS, new ArrayList<>());
            tick.put(SpotConstant.DATA_FIELD_BUYS, new ArrayList<>());
            response.put(SpotConstant.RESPONSE_FIELD_TICK, tick);
        }
        return response;
    }

    // ------------------------------------------------------------------ 成交记录

    private Map<String, Object> queryTradeData(String channel, SpotBaseMessage message,
                                               Map<String, Object> response, String symbol) {
        try {
            int pageSize = SpotConstant.DEFAULT_TRADE_PAGE_SIZE;
            if (message.getParams() != null && message.getParams().containsKey(SpotConstant.PARAM_FIELD_TOP)) {
                pageSize = Math.min(
                        ((Number) message.getParams().get(SpotConstant.PARAM_FIELD_TOP)).intValue(),
                        SpotConstant.MAX_TRADE_PAGE_SIZE);
            }

            String cacheKey = SpotRedisKey.trade(symbol);
            Set<String> tradeHistory = redisTemplate.opsForZSet().reverseRange(cacheKey, 0, -1);
            List<Map<String, Object>> dataResult = new ArrayList<>();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

            if (tradeHistory != null) {
                int count = 0;
                for (String trade : tradeHistory) {
                    if (count >= pageSize) {
                        break;
                    }
                    Map<String, Object> tempData = objectMapper.readValue(trade, new TypeReference<Map<String, Object>>() {});
                    long time = ((Number) tempData.get(SpotConstant.DATA_FIELD_TIME)).longValue();
                    long id   = time / SpotConstant.MILLISECONDS_TO_SECONDS;
                    tempData.put(SpotConstant.DATA_FIELD_TS, time);
                    tempData.put(SpotConstant.DATA_FIELD_DS, sdf.format(new Date(id * SpotConstant.MILLISECONDS_TO_SECONDS)));
                    dataResult.add(tempData);
                    count++;
                }
            }

            Map<String, Object> tick = new HashMap<>();
            tick.put(SpotConstant.RESPONSE_FIELD_DATA, dataResult);
            response.put(SpotConstant.RESPONSE_FIELD_TICK, tick);
        } catch (Exception e) {
            log.error("查询成交数据失败: channel={}, symbol={}", channel, symbol, e);
            Map<String, Object> tick = new HashMap<>();
            tick.put(SpotConstant.RESPONSE_FIELD_DATA, new ArrayList<>());
            response.put(SpotConstant.RESPONSE_FIELD_TICK, tick);
        }
        return response;
    }

    // ------------------------------------------------------------------ 多日行情

    private Map<String, Object> queryDetailTradeData(String channel, Map<String, Object> response, String symbol) {
        try {
            String cacheKey = SpotRedisKey.longTimeTicker(symbol);
            Map<Object, Object> longTimeTicker = redisTemplate.opsForHash().entries(cacheKey);
            Map<String, Map<String, String>> result = new HashMap<>();

            for (Map.Entry<Object, Object> entry : longTimeTicker.entrySet()) {
                String key   = entry.getKey().toString();
                String value = entry.getValue().toString();
                Map<String, String> tempData = objectMapper.readValue(value, new TypeReference<Map<String, String>>() {});

                tempData.put(SpotConstant.DATA_FIELD_SCALE,
                        tempData.get(SpotConstant.DATA_FIELD_ROSE) + SpotConstant.PERCENTAGE_SIGN);

                try {
                    BigDecimal rose = new BigDecimal(tempData.get(SpotConstant.DATA_FIELD_ROSE));
                    BigDecimal processed = rose.divide(
                            new BigDecimal(String.valueOf(SpotConstant.PERCENTAGE_MULTIPLIER)),
                            SpotConstant.DECIMAL_SCALE, RoundingMode.DOWN);
                    tempData.put(SpotConstant.DATA_FIELD_ROSE, processed.toPlainString());
                } catch (Exception e) {
                    log.warn("处理 rose 字段失败: {}", tempData.get(SpotConstant.DATA_FIELD_ROSE));
                }
                result.put(key, tempData);
            }
            response.put(SpotConstant.RESPONSE_FIELD_DATA, result);
        } catch (Exception e) {
            log.error("查询多日行情数据失败: channel={}, symbol={}", channel, symbol, e);
            response.put(SpotConstant.RESPONSE_FIELD_DATA, new HashMap<>());
        }
        return response;
    }

    // ------------------------------------------------------------------ helpers

    private List<List<String>> parseDepthList(String json) throws Exception {
        if (json == null || json.isEmpty()) {
            return new ArrayList<>();
        }
        return objectMapper.readValue(json, new TypeReference<List<List<String>>>() {});
    }
}
