package com.market.service;

import com.baomidou.dynamic.datasource.annotation.DS;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.market.entity.Kline;
import com.market.mapper.SpotKlineMapper;
import com.market.utils.SpotConstant;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * 现货历史 K 线批量同步服务
 * <p>
 * 调用 Binance REST API 获取历史 K 线数据，upsert 到 spot 数据库。
 * 支持按交易对、周期、起止时间同步；symbol/interval 不传则全量同步。
 *
 * @author system
 * @date 2026-05-07
 */
@Service
public class SpotKlineSyncService {

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

    /** Binance REST API 单次最大返回条数 */
    private static final int BINANCE_MAX_LIMIT = 1000;

    /** 每次请求后休眠（毫秒），避免触发币安限流 */
    private static final long REQUEST_DELAY_MS = 300;

    /** HTTP 超时（秒） */
    private static final int HTTP_TIMEOUT_SECONDS = 30;

    @Value("${spot.binance.rest-url:https://api.binance.com}")
    private String binanceRestUrl;

    private final SpotKlineMapper          klineMapper;
    private final SpotKlineRecordService   klineRecordService;
    private final OkHttpClient             httpClient;
    private final ObjectMapper             objectMapper;

    public SpotKlineSyncService(SpotKlineMapper klineMapper,
                                SpotKlineRecordService klineRecordService) {
        this.klineMapper       = klineMapper;
        this.klineRecordService = klineRecordService;
        this.objectMapper      = new ObjectMapper();
        this.httpClient        = new OkHttpClient.Builder()
                .connectTimeout(HTTP_TIMEOUT_SECONDS, TimeUnit.SECONDS)
                .readTimeout(HTTP_TIMEOUT_SECONDS, TimeUnit.SECONDS)
                .build();
    }

    // ------------------------------------------------------------------ public

    /**
     * 批量同步现货历史 K 线
     *
     * @param fromMs    起始时间（毫秒）
     * @param toMs      结束时间（毫秒）
     * @param interval  K 线周期，为空则同步所有支持的周期
     * @param symbols   交易对列表（小写）
     * @return 同步结果统计
     */
    @DS("market")
    public Map<String, Object> syncKline(long fromMs, long toMs, String interval, List<String> symbols) {
        AtomicInteger total  = new AtomicInteger();
        List<String>  errors = new ArrayList<>();
        List<String>  intervals = resolveIntervals(interval);

        for (String symbol : symbols) {
            // 确保对应 K 线表已存在
            klineRecordService.ensureKlineTable(symbol);
            for (String itv : intervals) {
                try {
                    int count = syncOne(symbol, itv, fromMs, toMs);
                    total.addAndGet(count);
                    log.info("现货 K 线同步完成: symbol={}, interval={}, count={}", symbol, itv, count);
                } catch (Exception e) {
                    String errMsg = symbol + "/" + itv + ": " + e.getMessage();
                    errors.add(errMsg);
                    log.error("现货 K 线同步失败: {}", errMsg, e);
                }
            }
        }

        // 汇总结果
        Map<String, Object> result = new LinkedHashMap<>();
        result.put("total",     total.get());
        result.put("symbols",   symbols);
        result.put("intervals", intervals);
        if (!errors.isEmpty()) {
            result.put("errors", errors);
        }
        return result;
    }

    // ------------------------------------------------------------------ 单对单周期同步

    /**
     * 分页拉取单个交易对单个周期的 K 线并 upsert，返回本次写库总条数
     *
     * @param symbol   交易对（小写）
     * @param interval 币安周期
     * @param fromMs   起始时间（毫秒）
     * @param toMs     结束时间（毫秒）
     * @return 写库条数
     */
    private int syncOne(String symbol, String interval, long fromMs, long toMs) throws Exception {
        String tableName = SpotConstant.TABLE_KLINE_PREFIX + symbol.toLowerCase();
        String period    = SpotConstant.KLINE_PERIOD_MAP.get(interval);
        if (period == null) {
            log.warn("不支持的 K 线周期，跳过: {}", interval);
            return 0;
        }

        int  count     = 0;
        long startTime = fromMs;

        while (true) {
            // 1. 拉取币安数据
            String    url  = buildKlineUrl(symbol.toUpperCase(), interval, startTime, toMs);
            JsonNode  rows = fetchKlines(url);
            if (rows == null || !rows.isArray() || rows.size() == 0) {
                break;
            }

            // 2. 解析为实体列表
            List<Kline> batch = new ArrayList<>(rows.size());
            for (JsonNode row : rows) {
                batch.add(parseKline(row, period));
            }

            // 3. 查询已存在的 beginTime 集合，分流 insert / update
            List<Long> beginTimes = batch.stream().map(Kline::getBeginTime).collect(Collectors.toList());
            Set<Long>  existing   = queryExistingBeginTimes(tableName, period, beginTimes);

            List<Kline> toInsert = batch.stream()
                    .filter(k -> !existing.contains(k.getBeginTime()))
                    .collect(Collectors.toList());
            List<Kline> toUpdate = batch.stream()
                    .filter(k -> existing.contains(k.getBeginTime()))
                    .collect(Collectors.toList());

            if (!toInsert.isEmpty()) {
                klineMapper.batchInsertKline(tableName, toInsert);
            }
            if (!toUpdate.isEmpty()) {
                klineMapper.batchUpdateKline(tableName, toUpdate);
            }
            count += batch.size();

            // 4. 数据不足一页说明已到末尾
            if (rows.size() < BINANCE_MAX_LIMIT) {
                break;
            }

            // 5. 下一批从最后一条 beginTime + 1ms 开始
            startTime = batch.get(batch.size() - 1).getBeginTime() + 1;
            if (startTime > toMs) {
                break;
            }
        }
        return count;
    }

    // ------------------------------------------------------------------ 工具方法

    /**
     * 查询已存在的 beginTime 集合
     *
     * @param tableName  表名
     * @param period     数据库周期
     * @param beginTimes 候选时间列表
     * @return 已存在的 beginTime 集合
     */
    private Set<Long> queryExistingBeginTimes(String tableName, String period, List<Long> beginTimes) {
        List<Kline> existing = klineMapper.selectByBeginTimes(tableName, period, beginTimes);
        return existing.stream().map(Kline::getBeginTime).collect(Collectors.toSet());
    }

    /**
     * 构建 Binance K 线 REST API 请求 URL
     *
     * @param symbol    交易对（大写）
     * @param interval  周期
     * @param startTime 起始时间（毫秒）
     * @param endTime   结束时间（毫秒）
     * @return 完整 URL
     */
    private String buildKlineUrl(String symbol, String interval, long startTime, long endTime) {
        StringBuilder url = new StringBuilder(binanceRestUrl)
                .append("/api/v3/klines")
                .append("?symbol=").append(symbol)
                .append("&interval=").append(interval)
                .append("&limit=").append(BINANCE_MAX_LIMIT);
        if (startTime > 0) {
            url.append("&startTime=").append(startTime);
        }
        if (endTime > 0) {
            url.append("&endTime=").append(endTime);
        }
        return url.toString();
    }

    /**
     * 发送 HTTP GET 请求并解析为 JsonNode
     *
     * @param url 请求地址
     * @return JsonNode，请求失败返回 null
     */
    private JsonNode fetchKlines(String url) throws Exception {
        Request request = new Request.Builder().url(url).get().build();
        try (Response response = httpClient.newCall(request).execute()) {
            Thread.sleep(REQUEST_DELAY_MS);
            if (response.code() == 429 || response.code() == 418) {
                throw new RuntimeException("Binance 限流: HTTP " + response.code());
            }
            if (!response.isSuccessful() || response.body() == null) {
                log.warn("Binance K 线接口返回异常: url={}, code={}", url, response.code());
                return null;
            }
            return objectMapper.readTree(response.body().string());
        }
    }

    /**
     * 将币安 K 线数组行解析为 Kline 实体
     * <p>
     * 数组格式：[openTime, open, high, low, close, volume, closeTime, quoteVolume, ...]
     *
     * @param row    K 线数组节点
     * @param period 数据库周期
     * @return Kline 实体
     */
    private Kline parseKline(JsonNode row, String period) {
        Kline kline = new Kline();
        kline.setPeriod(period);
        kline.setBeginTime(row.get(0).asLong());
        kline.setOpen(new BigDecimal(row.get(1).asText()));
        kline.setHigh(new BigDecimal(row.get(2).asText()));
        kline.setLow(new BigDecimal(row.get(3).asText()));
        kline.setClose(new BigDecimal(row.get(4).asText()));
        kline.setVolume(new BigDecimal(row.get(5).asText()));
        kline.setEndTime(row.get(6).asLong());
        kline.setTurnover(new BigDecimal(row.get(7).asText()));
        return kline;
    }

    /**
     * 解析周期列表：有值取单个，为空则返回所有支持的周期
     *
     * @param interval 请求中的周期（可为空）
     * @return 周期列表
     */
    private List<String> resolveIntervals(String interval) {
        if (StringUtils.isNotBlank(interval)) {
            return Collections.singletonList(interval);
        }
        return Arrays.asList(SpotConstant.KLINE_PERIODS);
    }
}
