package com.yingpai.stock.service.impl;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yingpai.stock.domain.StockQuoteData;
import com.yingpai.stock.service.IStockQuoteService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * 股票行情服务实现类（多线程优化）
 */
@Slf4j
@Service
public class StockQuoteServiceImpl implements IStockQuoteService {

    private final ThreadPoolTaskExecutor executor;
    private final ObjectMapper objectMapper = new ObjectMapper();

    private final HttpClient httpClient = HttpClient.newBuilder()
        .connectTimeout(Duration.ofSeconds(10))
        .build();

    private static final int BATCH_SIZE = 50;
    private static final String BATCH_QUOTE_URL = "http://push2.eastmoney.com/api/qt/ulist.np/get?fltt=2&fields=f12,f14,f2,f3,f4,f5,f6,f8,f18&secids=%s";

    public StockQuoteServiceImpl(@Qualifier("stockQuoteExecutor") ThreadPoolTaskExecutor executor) {
        this.executor = executor;
    }

    @Override
    public Map<String, StockQuoteData> batchFetchQuotesParallel(List<String> codes) {
        if (codes == null || codes.isEmpty()) {
            return Collections.emptyMap();
        }

        long startTime = System.currentTimeMillis();
        log.info("[行情获取] 开始多线程获取，股票数量: {}", codes.size());

        // 1. 数据分片
        List<List<String>> batches = partition(codes, BATCH_SIZE);
        log.info("[行情获取] 分为 {} 个批次", batches.size());

        // 2. 并行请求
        List<CompletableFuture<Map<String, StockQuoteData>>> futures = new ArrayList<>();
        for (List<String> batch : batches) {
            CompletableFuture<Map<String, StockQuoteData>> future = CompletableFuture.supplyAsync(
                () -> fetchBatchQuotes(batch),
                executor
            );
            futures.add(future);
        }

        // 3. 等待所有任务完成并合并结果
        Map<String, StockQuoteData> result = new ConcurrentHashMap<>();

        try {
            CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();

            for (CompletableFuture<Map<String, StockQuoteData>> future : futures) {
                try {
                    result.putAll(future.get());
                } catch (Exception e) {
                    log.warn("[行情获取] 部分批次失败: {}", e.getMessage());
                }
            }
        } catch (Exception e) {
            log.error("[行情获取] 多线程执行异常: {}", e.getMessage());
        }

        long costTime = System.currentTimeMillis() - startTime;
        log.info("[行情获取] 完成，获取到 {} 条数据，耗时 {}ms", result.size(), costTime);

        return result;
    }

    /**
     * 获取单批次行情（最多50只股票）
     */
    private Map<String, StockQuoteData> fetchBatchQuotes(List<String> batch) {
        Map<String, StockQuoteData> result = new HashMap<>();
        try {
            String secids = batch.stream()
                .map(this::getSecId)
                .filter(Objects::nonNull)
                .collect(Collectors.joining(","));

            if (secids.isEmpty()) return result;

            HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(String.format(BATCH_QUOTE_URL, secids)))
                .header("User-Agent", "Mozilla/5.0")
                .GET()
                .build();

            HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());

            if (response.statusCode() == 200) {
                JsonNode root = objectMapper.readTree(response.body());
                JsonNode data = root.path("data").path("diff");

                if (data != null && data.isArray()) {
                    for (JsonNode item : data) {
                        String code = item.path("f12").asText();
                        if (code == null || code.isEmpty()) continue;

                        StockQuoteData quote = StockQuoteData.builder()
                            .stockCode(code)
                            .currentPrice(parseBigDecimal(item, "f2"))
                            .yesterdayClose(parseBigDecimal(item, "f18"))
                            .changeAmount(parseBigDecimal(item, "f4"))
                            .changePercent(parseBigDecimal(item, "f3"))
                            .turnoverRate(parseBigDecimal(item, "f8"))
                            .build();

                        BigDecimal tradeAmount = parseBigDecimal(item, "f6");
                        if (tradeAmount != null) {
                            quote.setTradeAmount(tradeAmount.divide(new BigDecimal("100000000"), 2, RoundingMode.HALF_UP));
                        }

                        result.put(code, quote);
                    }
                }
            }
        } catch (Exception e) {
            log.warn("[批量行情] 获取失败: {}", e.getMessage());
        }
        return result;
    }

    /**
     * 数据分片
     */
    private <T> List<List<T>> partition(List<T> list, int size) {
        List<List<T>> partitions = new ArrayList<>();
        for (int i = 0; i < list.size(); i += size) {
            partitions.add(new ArrayList<>(list.subList(i, Math.min(i + size, list.size()))));
        }
        return partitions;
    }

    /**
     * 获取secId（东方财富格式）
     */
    private String getSecId(String code) {
        if (code == null || code.length() != 6) return null;
        if (code.startsWith("6")) return "1." + code;
        if (code.startsWith("0") || code.startsWith("3")) return "0." + code;
        return null;
    }

    /**
     * 解析BigDecimal
     */
    private BigDecimal parseBigDecimal(JsonNode node, String field) {
        if (!node.has(field)) return null;
        JsonNode value = node.get(field);
        if (value == null || value.isNull() || "-".equals(value.asText())) return null;
        try {
            return new BigDecimal(value.asText());
        } catch (Exception e) {
            return null;
        }
    }
}
