package com.bizzan.bitrade.socket.ws;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.bizzan.bitrade.engine.ContractCoinMatchFactory;
import com.bizzan.bitrade.entity.*;
import com.bizzan.bitrade.job.ExchangePushJob;
import com.bizzan.bitrade.service.ContractMarketService;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.enums.ReadyState;
import org.java_websocket.handshake.ServerHandshake;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 每个币对独立一个 WebSocket 连接，独立跟踪连接状态
 */
public class WebSocketBinance {

    private static final Logger logger = LoggerFactory.getLogger(WebSocketBinance.class);

    private static final String BINANCE_WS_BASE = "wss://fstream.binance.com/market/stream?streams=";
    private static final String BINANCE_REST_KLINE = "https://fapi.binance.com/fapi/v1/klines";

    private final ContractCoinMatchFactory matchFactory;
    private final ContractMarketService marketService;
    private final ExchangePushJob exchangePushJob;

    private double VOLUME_PERCENT = 0.13;

    public static String PERIOD[] = {"1min", "5min", "15min", "30min", "60min", "4hour", "1day", "1mon", "1week"};

    // 每个 symbol 独立的连接和状态
    private final Map<String, WebSocketClient> clientMap = new ConcurrentHashMap<>();
    private final Map<String, Boolean> connectedMap = new ConcurrentHashMap<>();
    private final Map<String, AtomicLong> delayMap = new ConcurrentHashMap<>();

    /** 合约最新价格缓存：key=symbol，value=最新成交价 */
    private final Map<String, BigDecimal> priceCache = new ConcurrentHashMap<>();

    private final ScheduledExecutorService watchdog = Executors.newSingleThreadScheduledExecutor();

    public WebSocketBinance(URI uri, ContractCoinMatchFactory matchFactory, ContractMarketService service, ExchangePushJob pushJob) {
        this.matchFactory = matchFactory;
        this.marketService = service;
        this.exchangePushJob = pushJob;
        // 启动 watchdog，每 15 秒检查断线的连接
        watchdog.scheduleAtFixedRate(this::checkConnections, 15, 15, TimeUnit.SECONDS);
    }

    /**
     * 初始订阅所有币对
     */
    public void init() {
        if (matchFactory.getMatchMap() == null) return;
        for (String symbol : matchFactory.getMatchMap().keySet()) {
            connectSymbol(symbol);
        }
    }

    /**
     * 订阅新币种
     */
    public void subNewCoin(String symbol) {
        if (!clientMap.containsKey(symbol)) {
            connectSymbol(symbol);
        }
    }

    private void connectSymbol(String symbol) {
        String s = symbol.replace("/", "").toLowerCase();
        // ticker：合约行情；aggTrade：合约聚合逐笔成交
        String streamsParam = s + "@ticker/" + s + "@aggTrade";
        try {
            URI uri = new URI(BINANCE_WS_BASE + streamsParam);
            delayMap.put(symbol, new AtomicLong(5_000L));

            WebSocketClient client = new WebSocketClient(uri) {
                @Override
                public void onOpen(ServerHandshake shake) {
                    connectedMap.put(symbol, true);
                    delayMap.get(symbol).set(5_000L); // 重置退避
                    logger.info("[WebSocketBinance][{}] 连接成功", symbol);
                }

                @Override
                public void onMessage(String message) {
                    handleMessage(symbol, message);
                }

                @Override
                public void onClose(int code, String reason, boolean remote) {
                    connectedMap.put(symbol, false);
                    logger.warn("[WebSocketBinance][{}] 连接关闭: {} - {}", symbol, code, reason);
                    scheduleReconnect(symbol, this);
                }

                @Override
                public void onError(Exception e) {
                    connectedMap.put(symbol, false);
                    logger.error("[WebSocketBinance][{}] 连接异常: {}", symbol, e.getMessage());
                }
            };

            clientMap.put(symbol, client);
            connectedMap.put(symbol, false);
            client.connect();
            logger.info("[WebSocketBinance][{}] 正在连接: {}", symbol, BINANCE_WS_BASE + streamsParam);

        } catch (Exception e) {
            logger.error("[WebSocketBinance][{}] 连接初始化失败: {}", symbol, e.getMessage());
        }
    }

    private void scheduleReconnect(String symbol, WebSocketClient client) {
        AtomicLong delayRef = delayMap.get(symbol);
        long delay = delayRef.get();
        delayRef.set(Math.min(delay * 2, 60_000L));
        logger.info("[WebSocketBinance][{}] {}ms 后重连...", symbol, delay);
        new Thread(() -> {
            try { Thread.sleep(delay); } catch (InterruptedException ignored) {}
            if (Boolean.FALSE.equals(connectedMap.get(symbol))) {
                logger.info("[WebSocketBinance][{}] 重连中...", symbol);
                client.reconnect();
            }
        }).start();
    }

    /**
     * Watchdog: 定期检查各币对连接状态，打印汇总并补充重连
     */
    public void checkConnections() {
        List<String> connected = new ArrayList<>();
        List<String> disconnected = new ArrayList<>();
        for (Map.Entry<String, Boolean> entry : connectedMap.entrySet()) {
            if (Boolean.TRUE.equals(entry.getValue())) {
                connected.add(entry.getKey());
            } else {
                disconnected.add(entry.getKey());
            }
        }
        logger.info("[WebSocketBinance] 连接状态 - 已连接({}): {} | 断开({}): {}",
                connected.size(), connected, disconnected.size(), disconnected);

        // 对断开的补充触发重连（如果 reconnect 没有在进行中）
        for (String symbol : disconnected) {
            WebSocketClient client = clientMap.get(symbol);
            if (client != null) {
                ReadyState state = client.getReadyState();
                if (state == ReadyState.CLOSED || state == ReadyState.NOT_YET_CONNECTED) {
                    scheduleReconnect(symbol, client);
                }
            }
        }
    }

    private void handleMessage(String symbol, String message) {
        if (message == null || message.isEmpty()) return;
        try {
            JSONObject json = JSONObject.parseObject(message);

            // 心跳
            if (json.containsKey("ping")) {
                WebSocketClient client = clientMap.get(symbol);
                if (client != null) {
                    JSONObject pong = new JSONObject();
                    pong.put("pong", json.getLong("ping"));
                    client.send(pong.toString());
                }
                return;
            }

            if (!json.containsKey("stream")) return;
            String stream = json.getString("stream");
            JSONObject data = json.getJSONObject("data");
            if (data == null) return;

            String[] parts = stream.split("@");
            String type = parts.length > 1 ? parts[1] : "";

            if (type.equals("ticker")) {
                handleDetail(symbol, data);
            } else if (type.equals("aggTrade")) {
                handleTrade(symbol, data);
            }

        } catch (Exception e) {
            logger.error("[WebSocketBinance][{}] 消息处理异常: {}", symbol, e.getMessage());
        }
    }

    private void handleDetail(String symbol, JSONObject data) {
        BigDecimal open  = data.getBigDecimal("o");
        BigDecimal close = data.getBigDecimal("c");
        BigDecimal high  = data.getBigDecimal("h");
        BigDecimal low   = data.getBigDecimal("l");
        BigDecimal vol   = data.getBigDecimal("v");
        BigDecimal quote = data.getBigDecimal("q");

        // 缓存最新合约价格
        if (close != null && close.compareTo(BigDecimal.ZERO) > 0) {
            priceCache.put(symbol, close);
        }

        CoinThumb thumb = new CoinThumb();
        thumb.setOpen(open);
        thumb.setClose(close);
        thumb.setHigh(high);
        thumb.setLow(low);
        thumb.setVolume(vol.multiply(BigDecimal.valueOf(VOLUME_PERCENT)));
        thumb.setTurnover(quote.multiply(BigDecimal.valueOf(VOLUME_PERCENT)));
        matchFactory.getContractCoinMatch(symbol).refreshThumb(thumb);
        matchFactory.getContractCoinMatch(symbol).refreshPrice(close);
    }

    private void handleTrade(String symbol, JSONObject data) {
        BigDecimal price  = data.getBigDecimal("p");
        BigDecimal amount = data.getBigDecimal("q");
        boolean isBuyerMaker = data.getBooleanValue("m");
        long time = data.getLongValue("T");
        String tradeId = data.getString("t");

        ContractTrade trade = new ContractTrade();
        trade.setAmount(amount);
        trade.setPrice(price);
        trade.setSymbol(symbol);
        trade.setTime(time);

        if (!isBuyerMaker) {
            trade.setDirection(ContractOrderDirection.BUY);
            trade.setBuyOrderId(tradeId);
            trade.setBuyTurnover(amount.multiply(price));
        } else {
            trade.setDirection(ContractOrderDirection.SELL);
            trade.setSellOrderId(tradeId);
            trade.setSellTurnover(amount.multiply(price));
        }

        List<ContractTrade> tradeList = new ArrayList<>();
        tradeList.add(trade);
        matchFactory.getContractCoinMatch(symbol).refreshLastedTrade(tradeList);
    }

    /**
     * 获取指定交易对的最新缓存价格
     *
     * @param symbol 交易对，如 BTC/USDT
     * @return 最新价格，未收到行情时返回 null
     */
    public BigDecimal getCachedPrice(String symbol) {
        return priceCache.get(symbol);
    }

    /**
     * 获取所有交易对的最新缓存价格快照
     *
     * @return 不可修改的价格 Map
     */
    public Map<String, BigDecimal> getPriceCache() {
        return Collections.unmodifiableMap(priceCache);
    }
}
