package com.jxy.binance.task;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.jxy.binance.dto.BiAnWSDto;
import com.jxy.binance.service.impl.BiAnServiceImpl;
import com.jxy.common.core.utils.SpringUtils;
import com.jxy.common.core.utils.StringUtils;
import org.java_websocket.WebSocket;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.framing.Framedata;
import org.java_websocket.handshake.ServerHandshake;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.net.URI;
import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * @Description U本位WS
 * @Date 2023/1/31 17:23
 * @Author 杜懿
 */

/**
 * 连接方式：
 * Base Url：wss://fstream.binance.com
 * 订阅单一stream格式为 /ws/<streamName>
 * 组合streams的URL格式为 /stream?streams=<streamName1>/<streamName2>/<streamName3>
 * 连接样例：
 * wss://fstream.binance.com/ws/bnbusdt@aggTrade
 * wss://fstream.binance.com/stream?streams=bnbusdt@aggTrade/btcusdt@markPrice

 * 每个链接有效期不超过24小时，请妥善处理断线重连。
 *
 * 服务端每5分钟会发送ping帧，客户端应当在15分钟内回复pong帧，否则服务端会主动断开链接。允许客户端发送不成对的pong帧(即客户端可以以高于15分钟每次的频率发送pong帧保持链接)。
 *
 * Websocket服务器每秒最多接受10个订阅消息。
 *
 * 如果用户发送的消息超过限制，连接会被断开连接。反复被断开连接的IP有可能被服务器屏蔽。
 *
 * 单个连接最多可以订阅 200 个Streams。
 */
@Component
@Order(value = 2)
public class UstreamTask implements ApplicationRunner {

    private static BiAnServiceImpl biAnService;

    /** 入库数据 */
    private static Map<String,List<BiAnWSDto>> dbMap = new HashMap<>();

    /** 推送数据 */
    private static Map<String,BiAnWSDto> map = new HashMap<>();


    private static Map<String,String> tableNameMap = new HashMap<>();

    static {
        tableNameMap.put("\"btcusdt1m\"","BTCUSDT");
        tableNameMap.put("\"btcusdt3m\"","3mBTCUSDT");
        tableNameMap.put("\"btcusdt5m\"","5mBTCUSDT");
        tableNameMap.put("\"btcusdt15m\"","15mBTCUSDT");
        tableNameMap.put("\"btcusdt30m\"","30mBTCUSDT");
        tableNameMap.put("\"btcusdt1h\"","1hBTCUSDT");
        tableNameMap.put("\"btcusdt2h\"","2hBTCUSDT");
        tableNameMap.put("\"btcusdt4h\"","4hBTCUSDT");
        tableNameMap.put("\"btcusdt6h\"","6hBTCUSDT");
        tableNameMap.put("\"btcusdt8h\"","8hBTCUSDT");
        tableNameMap.put("\"btcusdt12h\"","12hBTCUSDT");
        tableNameMap.put("\"btcusdt1d\"","1dBTCUSDT");
        tableNameMap.put("\"btcusdt3d\"","3dBTCUSDT");
        tableNameMap.put("\"btcusdt1w\"","1wBTCUSDT");
        tableNameMap.put("\"btcusdt1M\"","1MBTCUSDT");

        tableNameMap.put("\"ethusdt1m\"","ETHUSDT");
        tableNameMap.put("\"ethusdt3m\"","3mETHUSDT");
        tableNameMap.put("\"ethusdt5m\"","5mETHUSDT");
        tableNameMap.put("\"ethusdt15m\"","15mETHUSDT");
        tableNameMap.put("\"ethusdt30m\"","30mETHUSDT");
        tableNameMap.put("\"ethusdt1h\"","1hETHUSDT");
        tableNameMap.put("\"ethusdt2h\"","2hETHUSDT");
        tableNameMap.put("\"ethusdt4h\"","4hETHUSDT");
        tableNameMap.put("\"ethusdt6h\"","6hETHUSDT");
        tableNameMap.put("\"ethusdt8h\"","8hETHUSDT");
        tableNameMap.put("\"ethusdt12h\"","12hETHUSDT");
        tableNameMap.put("\"ethusdt1d\"","1dETHUSDT");
        tableNameMap.put("\"ethusdt3d\"","3dETHUSDT");
        tableNameMap.put("\"ethusdt1w\"","1wETHUSDT");
        tableNameMap.put("\"ethusdt1M\"","1METHUSDT");

    }


    @Override
    public void run(ApplicationArguments args) throws Exception {

        String url = "wss://fstream.binance.com/stream?streams=btcusdt@kline_1m/btcusdt@kline_3m/btcusdt@kline_5m/btcusdt@kline_15m/btcusdt@kline_30m/" +
                "btcusdt@kline_1h/btcusdt@kline_2h/btcusdt@kline_4h/btcusdt@kline_6h/btcusdt@kline_8h/btcusdt@kline_12h/" +
                "btcusdt@kline_1d/btcusdt@kline_3d/btcusdt@kline_1w/btcusdt@kline_1M/" +
                "ethusdt@kline_1m/ethusdt@kline_3m/ethusdt@kline_5m/ethusdt@kline_15m/ethusdt@kline_30m/" +
                "ethusdt@kline_1h/ethusdt@kline_2h/ethusdt@kline_4h/ethusdt@kline_6h/ethusdt@kline_8h/ethusdt@kline_12h/" +
                "ethusdt@kline_1d/ethusdt@kline_3d/ethusdt@kline_1w/ethusdt@kline_1M";

        ScheduledExecutorService ses1 = Executors.newSingleThreadScheduledExecutor();
        ses1.scheduleAtFixedRate(()->{
            WebSocketClient ws = getWS(url);

            ScheduledExecutorService ses2 = Executors.newSingleThreadScheduledExecutor();
            ses2.scheduleAtFixedRate(()->{
                ws.sendPing();
                if(!ws.isConnecting()){
                 //如果连接断开重连
                    ws.connect();
                }},0,3, TimeUnit.MINUTES);

            ScheduledExecutorService ses3 = Executors.newSingleThreadScheduledExecutor();
            ses3.schedule(()->{ws.close();},18, TimeUnit.HOURS);

            },0,18,TimeUnit.HOURS);
        //WebSocketClient ws = getWS(url);
        //String url = "wss://dstream.binance.com/ws/btcusd_perp@kline_1h";



    }

    public static WebSocketClient getWS(String url){

        try{

            WebSocketClient wbc = new WebSocketClient(new URI(url)) {

                @Override
                public void onOpen(ServerHandshake handshakedata) {
                }

                @Override
                public void onWebsocketPing(WebSocket conn, Framedata f) {
                    conn.sendFrame(f);
                }

                @Override
                public void onMessage(String message) {

                    JSONObject jsonObject = JSON.parseObject(message);

                    String stream = JSONObject.toJSONString(jsonObject.get("stream"));
                    //stream:"ethusdt@kline_1m"
                    String key = stream.replace("_", "").replace("@kline", "");

                    String dataStr = JSONObject.toJSONString(jsonObject.get("data"));

                    String dateStr = JSONObject.toJSONString(JSON.parseObject(dataStr).get("E"));
                    Date date = JSON.parseObject(dateStr, Date.class);

                    String kStr = JSONObject.toJSONString(JSON.parseObject(dataStr).get("k"));

                    String openTimeStr = JSONObject.toJSONString(JSON.parseObject(kStr).get("t"));
                    Date openTime = JSON.parseObject(openTimeStr, Date.class);

                    Calendar openCa = Calendar.getInstance();
                    openCa.setTime(openTime);
                    openCa.add(Calendar.HOUR, +8);
                    openTime = openCa.getTime();

                    String closeTimeStr = JSONObject.toJSONString(JSON.parseObject(kStr).get("T"));
                    Date closeTime = JSON.parseObject(closeTimeStr, Date.class);

                    Calendar closeCa = Calendar.getInstance();
                    closeCa.setTime(closeTime);
                    closeCa.add(Calendar.HOUR, +8);
                    closeTime = closeCa.getTime();


                    String openPriceStr = JSONObject.toJSONString(JSON.parseObject(kStr).get("o"));
                    BigDecimal openPrice = JSON.parseObject(openPriceStr, BigDecimal.class);

                    String highPriceStr = JSONObject.toJSONString(JSON.parseObject(kStr).get("h"));
                    BigDecimal highPrice = JSON.parseObject(highPriceStr, BigDecimal.class);

                    String lowPriceStr = JSONObject.toJSONString(JSON.parseObject(kStr).get("l"));
                    BigDecimal lowPrice = JSON.parseObject(lowPriceStr, BigDecimal.class);

                    String closePriceStr = JSONObject.toJSONString(JSON.parseObject(kStr).get("c"));
                    BigDecimal closePrice = JSON.parseObject(closePriceStr, BigDecimal.class);

                    String volumeStr = JSONObject.toJSONString(JSON.parseObject(kStr).get("v"));
                    BigDecimal volume = JSON.parseObject(volumeStr, BigDecimal.class);

                    String qualityStr = JSONObject.toJSONString(JSON.parseObject(kStr).get("q"));
                    BigDecimal quality = JSON.parseObject(qualityStr, BigDecimal.class);

                    String buyVolumeStr = JSONObject.toJSONString(JSON.parseObject(kStr).get("V"));
                    BigDecimal buyVolume = JSON.parseObject(buyVolumeStr, BigDecimal.class);

                    String buyQualityStr = JSONObject.toJSONString(JSON.parseObject(kStr).get("Q"));
                    BigDecimal buyQuality = JSON.parseObject(buyQualityStr, BigDecimal.class);

                    String numStr = JSONObject.toJSONString(JSON.parseObject(kStr).get("n"));
                    BigDecimal num = JSON.parseObject(numStr, BigDecimal.class);

                    String symbolStr = JSONObject.toJSONString(JSON.parseObject(kStr).get("s"));
                    String symbol = JSON.parseObject(symbolStr, String.class);

                    String flagStr = JSONObject.toJSONString(JSON.parseObject(kStr).get("x"));
                    boolean flag = JSON.parseObject(flagStr, Boolean.class);

                    //赋值
                    BiAnWSDto dto = new BiAnWSDto();
                    dto.setDate(date);
                    dto.setOpenTime(openTime);
                    dto.setCloseTime(closeTime);
                    dto.setOpenPrice(openPrice);
                    dto.setHighPrice(highPrice);
                    dto.setLowPrice(lowPrice);
                    dto.setClosePrice(closePrice);
                    dto.setVolume(volume);
                    dto.setBuyVolume(buyVolume);
                    dto.setQuality(quality);
                    dto.setBuyQuality(buyQuality);
                    dto.setNum(num);
                    dto.setSymbol(symbol);

                    //取值对比
                    if(flag){
                        //是K线最后一条数据 即不再变化的数据
                        //存入数据库
                        insertDataToDb(dto,key);
                    }
                    if(StringUtils.isNotNull(map.get(key))){
                        //stream不为空 说明map中已有key对应的数据
                        BiAnWSDto oldData = map.get(key);
                        if((oldData.getOpenTime().compareTo(dto.getOpenTime())) != 0){
                            //map中的数据和新传过来的数据开盘时间不一致 则把新数据放入dbMap中
                            if(StringUtils.isNull(dbMap.get(key))){
                                //dbMap中没数据
                                List<BiAnWSDto> list = new ArrayList<>();
                                list.add(oldData);
                                dbMap.put(key,list);
                            }else{
                                List<BiAnWSDto> list = dbMap.get(key);
                                list.add(oldData);
                                dbMap.put(key,list);
                            }
                            //存入数据库
                            insertDataToDb(oldData,key);
                        }
                    }
                    map.put(key,dto);

                }

                @Override
                public void onClose(int code, String reason, boolean remote) {
                    System.out.println("连接已断开");
                }

                @Override
                public void onError(Exception ex) {
                    System.out.println("连接因为异常断开：");
                    ex.printStackTrace();
                }

            };
            wbc.connect();

            return  wbc;
        }
        catch (Exception e){

            e.printStackTrace();
        }
        return null;
    }

    public static boolean insertDataToDb(BiAnWSDto dto,String key){

        String tableName = tableNameMap.get(key);

        if(StringUtils.isNotNull(tableName)){

            biAnService = SpringUtils.getBean("biAnService");
            biAnService.insertUSDT(dto,tableName);
        }

        return true;
    }


    public static BiAnWSDto getRealDataBykey(String key){

        return map.get(key);
    }

    public static List<BiAnWSDto> getDbDataByKey(String key){

        return dbMap.get(key);
    }

    public static Map<String,BiAnWSDto> getAllRealData(){

        return map;
    }

    public static Map<String, List<BiAnWSDto>> getAllDbData(){

        return dbMap;
    }
}
