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 B本位WS
 * @Date 2023/1/31 17:23
 * @Author 杜懿
 */

/**
 * baseurl为: wss://dstream.binance.com
 * 订阅单一stream格式为 /ws/<streamName>
 * 组合streams的URL格式为 /stream?streams=<streamName1>/<streamName2>/<streamName3>
 * 订阅组合streams时,事件payload会以这样的格式封装 {"stream":"<streamName>","data":<rawPayload>}
 * stream名称中所有交易对,标的交易对,合约类型均为小写
 * 每个链接有效期不超过24小时，请妥善处理断线重连。
 * 服务端每5分钟会发送ping帧，客户端应当在15分钟内回复pong帧，否则服务端会主动断开链接。允许客户端发送不成对的pong帧(即客户端可以以高于15分钟每次的频率发送pong帧保持链接)。
 * Websocket服务器每秒最多接受10个订阅消息。
 * 如果用户发送的消息超过限制，连接会被断开连接。反复被断开连接的IP有可能被服务器屏蔽。
 * 单个连接最多可以订阅 200 个Streams。
 */

/** 1m 3m 5m 15m 30m 1h 2h 4h 6h 8h 12h 1d 3d 1w 1M */

/**
 * Stream Name:
 * <symbol>@kline_<interval>
 */
@Component
@Order(value = 1)
public class BstreamTask 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("\"btcusdperp1m\"","BTCUSDPERP");
        tableNameMap.put("\"btcusdperp3m\"","3mBTCUSDPERP");
        tableNameMap.put("\"btcusdperp5m\"","5mBTCUSDPERP");
        tableNameMap.put("\"btcusdperp15m\"","15mBTCUSDPERP");
        tableNameMap.put("\"btcusdperp30m\"","30mBTCUSDPERP");
        tableNameMap.put("\"btcusdperp1h\"","1hBTCUSDPERP");
        tableNameMap.put("\"btcusdperp2h\"","2hBTCUSDPERP");
        tableNameMap.put("\"btcusdperp4h\"","4hBTCUSDPERP");
        tableNameMap.put("\"btcusdperp6h\"","6hBTCUSDPERP");
        tableNameMap.put("\"btcusdperp8h\"","8hBTCUSDPERP");
        tableNameMap.put("\"btcusdperp12h\"","12hBTCUSDPERP");
        tableNameMap.put("\"btcusdperp1d\"","1dBTCUSDPERP");
        tableNameMap.put("\"btcusdperp3d\"","3dBTCUSDPERP");
        tableNameMap.put("\"btcusdperp1w\"","1wBTCUSDPERP");
        tableNameMap.put("\"btcusdperp1M\"","1MBTCUSDPERP");

        tableNameMap.put("\"ethusdperp1m\"","ETHUSDPERP");
        tableNameMap.put("\"ethusdperp3m\"","3mETHUSDPERP");
        tableNameMap.put("\"ethusdperp5m\"","5mETHUSDPERP");
        tableNameMap.put("\"ethusdperp15m\"","15mETHUSDPERP");
        tableNameMap.put("\"ethusdperp30m\"","30mETHUSDPERP");
        tableNameMap.put("\"ethusdperp1h\"","1hETHUSDPERP");
        tableNameMap.put("\"ethusdperp2h\"","2hETHUSDPERP");
        tableNameMap.put("\"ethusdperp4h\"","4hETHUSDPERP");
        tableNameMap.put("\"ethusdperp6h\"","6hETHUSDPERP");
        tableNameMap.put("\"ethusdperp8h\"","8hETHUSDPERP");
        tableNameMap.put("\"ethusdperp12h\"","12hETHUSDPERP");
        tableNameMap.put("\"ethusdperp1d\"","1dETHUSDPERP");
        tableNameMap.put("\"ethusdperp3d\"","3dETHUSDPERP");
        tableNameMap.put("\"ethusdperp1w\"","1wETHUSDPERP");
        tableNameMap.put("\"ethusdperp1M\"","1METHUSDPERP");

    }


    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("币本位连接");

        String url = "wss://dstream.binance.com/stream?streams=btcusd_perp@kline_1m/btcusd_perp@kline_3m/btcusd_perp@kline_5m/btcusd_perp@kline_15m/btcusd_perp@kline_30m/" +
                "btcusd_perp@kline_1h/btcusd_perp@kline_2h/btcusd_perp@kline_4h/btcusd_perp@kline_6h/btcusd_perp@kline_8h/btcusd_perp@kline_12h/" +
                "btcusd_perp@kline_1d/btcusd_perp@kline_3d/btcusd_perp@kline_1w/btcusd_perp@kline_1M/" +
                "ethusd_perp@kline_1m/ethusd_perp@kline_3m/ethusd_perp@kline_5m/ethusd_perp@kline_15m/ethusd_perp@kline_30m/" +
                "ethusd_perp@kline_1h/ethusd_perp@kline_2h/ethusd_perp@kline_4h/ethusd_perp@kline_6h/ethusd_perp@kline_8h/ethusd_perp@kline_12h/" +
                "ethusd_perp@kline_1d/ethusd_perp@kline_3d/ethusd_perp@kline_1w/ethusd_perp@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"));
                    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);

                    //赋值
                    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(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.insertUSDPerp(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;
    }
}
