package com.yutou.bilibili.BiliBili;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.yutou.bilibili.BiliBili.Datas.DanmuData;
import com.yutou.bilibili.BiliBili.Datas.GiftData;
import com.yutou.bilibili.BiliBili.Datas.LiveData;
import com.yutou.bilibili.BiliBili.Services.IBiliBiliLiveService;
import com.yutou.bilibili.BiliBili.Tools.SaveLive;
import com.yutou.bilibili.QQBot.QQBotManager;
import com.yutou.bilibili.Tools.AppTools;
import com.yutou.bilibili.Tools.Log;
import com.yutou.bilibili.Tools.RedisTools;
import com.yutou.bilibili.Tools.Tools;
import com.yutou.bilibili.interfaces.DownloadInterface;
import com.yutou.bilibili.mybatis.Bili.mybatis.model.BilibiliLiveData;
import com.yutou.bilibili.mybatis.Bili.mybatis.model.BilibiliLiveInfo;
import com.yutou.bilibili.mybatis.Bili.mybatis.model.BilibiliUpInfo;
import com.yutou.bilibili.sqlite.BiliBiliLiveDatabasesManager;
import lombok.Data;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import javax.annotation.PostConstruct;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.*;

public class Live implements ApplicationContextAware {
    IBiliBiliLiveService service;


    public static Map<Integer, Live> lives = new HashMap<>();


    private int roomId;
    private boolean isLogin;
    private boolean run;
    private int userId;
    private WebSocketClient client;
    private BilibiliLiveInfo info;
    private BilibiliUpInfo upData;
    private Timer heartBeattimer;
    public BiliBiliLiveDatabasesManager danmuManager = null;
    private LiveInfo liveInfo;

    public LiveInfo getLiveInfo() {
        return liveInfo;
    }

    public void setLiveInfo(LiveInfo liveInfo) {
        this.liveInfo = liveInfo;
    }

    @PostConstruct
    public void init() {

    }

    public Live() {
        if (applicationContext != null) {
            service = getBean(IBiliBiliLiveService.class);
        }
        info = new BilibiliLiveInfo();
        info.setGiftuser(0);
        info.setPopular(0);
        info.setUserindex(0);
        info.setVipuserindex(0);
    }

    /**
     * 构造方法
     *
     * @param roomId  直播间roomId，不是房间号，有可能是临时房间号，得通过 {@link com.yutou.bilibili.BiliBili.Tools.BiliTools#getBiliUpInfo(String)} 来获取roomId
     * @param isLogin 是否登录用户
     */
    public void add(int roomId, boolean isLogin) {
        if (Live.lives.containsKey(roomId)) {
            Log.i("已经在统计:" + roomId);
            return;
        }
        this.roomId = roomId;
        this.isLogin = isLogin;
        upData = new BilibiliUpInfo();
        upData.setRoomid(roomId);
        info.setRoomid(roomId);
        info.setGiftuser(0);
        info.setVipuserindex(0);
        info.setUserindex(0);
        Live.lives.put(roomId, this);
        updateUpInfo();
        com.yutou.bilibili.Tools.Log.i("roomId = " + roomId + ", isLogin = " + isLogin);
        try {
            start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void updateUpInfo() {
        try {
            upData = service.queryUp(upData);
            //upData.setLive(1);
            if (upData.getSavedanmu() == 1) {
                danmuManager = new BiliBiliLiveDatabasesManager();
                danmuManager.init("[" + AppTools.getToDayTime() + "]" + upData.getRoomid());
            } else {
                if (danmuManager != null) {
                    danmuManager.close();
                    danmuManager = null;
                }
            }
        } catch (Exception e) {
            Log.e(e);
        }
    }

    private Date startTime;

    /**
     * 开始监听
     *
     * @throws Exception 发生异常
     */
    private void start() throws Exception {
        run = true;
        if (LiveUtils.isLivePlayer(roomId)) {
            upData.setLive(-1);
        }
        String url = LiveUtils.getLiveUrl(roomId);
        if (url == null) {
            stop();
            return;
        }
        startTime = new Date();
        HashMap<String, String> header = new HashMap<>();
        header.put("Sec-WebSocket-Extensions", "permessage-deflate; client_max_window_bits");
        header.put("Sec-WebSocket-Key", "tORCZd8AI6xIyvqvgvI1Vw==");
        header.put("Sec-WebSocket-Version", "13");
        header.put("Cache-Control", "no-cache");
        header.put("Connection", "Upgrade");
        header.put("Host", "tx-gz-live-comet-02.chat.bilibili.com");
        header.put("Origin", "https://live.bilibili.com");
        header.put("Pragma", "no-cache");
        header.put("Upgrade", "websocket");
        header.put("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36");
        if (client != null && !client.isClosed()) {
            stop();
            return;
        }
        client = new WebSocketClient(new URI(url), header) {
            private long time = 0;
            private boolean init = true;

            @Override
            public void onOpen(ServerHandshake serverHandshake) {
                time = System.currentTimeMillis();
                com.yutou.bilibili.Tools.Log.i("ws: ok");
                try {
                    likeLive();
                } catch (Exception e) {
                    Log.e(e);
                }

            }

            @Override
            public void onMessage(String s) {

            }

            @Override
            public void onMessage(ByteBuffer bytes) {
                super.onMessage(bytes);
                if (init) {
                    heartBeattimer = new Timer();
                    heartBeattimer.schedule(new sendHeartbeat(), 2000, 20000);
                    init = false;
                }
                decompress(bytes.array());
            }

            @Override
            public void onClose(int i, String s, boolean b) {
                com.yutou.bilibili.Tools.Log.i("连接时间：" + (System.currentTimeMillis() - time));
                if (upData.getOfflinelistening() == 1) {
                    Log.i(roomId + " 断开连接，重连...");
                    try {
                        init = true;
                        heartBeattimer.cancel();
                        client.close();
                        start();
                    } catch (Exception e) {
                        Log.e(e);
                    }
                    return;
                }
                stop();
                client.close();
                run = false;
                throw new RuntimeException("连接关闭: code = " + i + " msg = " + s + " b = " + b + " roomId=" + roomId);
            }

            @Override
            public void onClosing(int code, String reason, boolean remote) {
                super.onClosing(code, reason, remote);
                com.yutou.bilibili.Tools.Log.i("code = " + code + ", reason = " + reason + ", remote = " + remote);
                run = false;
                client.close();
                stop();
            }

            @Override
            public void onError(Exception e) {
                Log.e(e);
                run = false;
                client.close();
                stop();
            }
        };
        client.connect();
    }

    public void stop() {
        run = false;
        client.close();
        if (danmuManager != null) {
            danmuManager.close();
        }
        if (SaveLive.getInstance().checkLive(roomId)) {
            SaveLive.getInstance().stop(roomId);
        }
        Live.lives.remove(roomId);
        com.yutou.bilibili.Tools.Log.i("退出" + roomId + "直播间");
    }

    /**
     * 连接房间
     *
     * @throws Exception e
     */
    private void likeLive() throws Exception {
        JSONObject tmp = LiveUtils.http_get("https://api.bilibili.com/x/web-interface/nav");
        if (tmp != null && tmp.getInteger("code") == -101) {
            if (isLogin) {
                new File("cookies.json").deleteOnExit();
                upData.setLive(0);
                service.updateUpInfo(upData);
                stop();
                return;
            } else {
                userId = 0;
            }
        } else if (tmp != null && tmp.getInteger("code") == 0) {
            userId = tmp.getJSONObject("data").getInteger("mid");
        }
        JSONObject http = LiveUtils.http_get("https://api.live.bilibili.com/xlive/web-room/v1/index/getDanmuInfo?id=" + roomId + "&type=0");
        if (http == null) {
            stop();
            return;
        }
        JSONObject json = new JSONObject();
        json.put("uid", userId);
        json.put("roomid", roomId);
        json.put("protover", 2);
        json.put("platform", "web");
        json.put("clientver", "2.6.36");
        json.put("type", 2);
        json.put("key", http.getJSONObject("data").getString("token"));
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        outputStream.write(LiveUtils.toLH(json.toJSONString().length() + 16));
        outputStream.write(new byte[]{0, 16, 0, 1, 0, 0, 0, 7, 0, 0, 0, 1});
        outputStream.write(json.toJSONString().getBytes(StandardCharsets.UTF_8));
        outputStream.flush();
        // LiveUtils.printHex(outputStream.toByteArray());
        client.send(outputStream.toByteArray());
        checkLive();
    }

    /**
     * 解压缩
     *
     * @param data 待压缩的数据
     */
    public void decompress(byte[] data) {
        try {
            byte[] bytes = new byte[data.length - 16];
            System.arraycopy(data, 16, bytes, 0, data.length - 16);


            if (data.length > 32) {
                List<String> list = LiveUtils.getMsgList(LiveUtils.dec(bytes), new ArrayList<>(), true);
                String tmp = "";
                for (int i=0;i<list.size();i++) {
                    processData(list.get(i), data);
                    tmp +="|||"+i+"[start] "+ list.get(i)+ "  [end]|||  ";
                }
           //     Log.i(AppTools.getToDayTimeToString(startTime), roomId, "list size = " + list.size() + " data = " + tmp);
            } else {
                try {
                    JSONObject json = JSONObject.parseObject(new String(bytes, StandardCharsets.UTF_8));
                    System.out.println("json = " + json);
                    com.yutou.bilibili.Tools.Log.i(json.toJSONString());
                } catch (Exception e) {
                    int popular = LiveUtils.bytesToInt2(bytes, 0);
                    info.setPopular(popular);
                }
                upData.setLive(LiveUtils.getLiveState(roomId)?1:0);
            }
            
        } catch (Exception e) {
            Log.e(e);
            com.yutou.bilibili.Tools.Log.i("----------ERROR----------");
            com.yutou.bilibili.Tools.Log.i(new String(data, StandardCharsets.UTF_8));
            LiveUtils.printHex(LiveUtils.dec(data));
            com.yutou.bilibili.Tools.Log.i(new SimpleDateFormat("HH:mm:ss.SSS").format(new Date()));
        }
    }

    public void checkLive() {
        boolean isLive = LiveUtils.isLivePlayer(roomId);
        if(SaveLive.getInstance().checkLive(roomId)){
            return;
        }
        if (isLive && upData.getSavelive() == 1) {
            SaveLive.getInstance().addLive(this);
        }
        if (!isLive || upData.getSavelive() == 0) {
            if (SaveLive.getInstance().checkLive(roomId)) {
                SaveLive.getInstance().stop(roomId);
            }
        }
    }


    /**
     * 处理数据
     *
     * @param msg   数据json
     * @param bytes 原始hex数据
     */
    public void processData(String msg, byte[] bytes) {
        if(msg.contains("[object Object]")){
            return;
        }
        try {
            JSONObject json = JSONObject.parseObject(msg);
            JSONObject data;
            BilibiliLiveData liveData = new BilibiliLiveData();
            String danmu;
            GiftData giftData;
            Log.i(AppTools.getToDayTime(),roomId,json.toJSONString());
            switch (json.getString("cmd")) {
                case "INTERACT_WORD"://普通用户进直播间
                    danmu = json.getJSONObject("data").getString("uname") + " 进入到直播间";
                    liveData.setUid(json.getJSONObject("data").getInteger("uid"));
                    liveData.setType(LiveData.INTERACT_WORD);
                    liveData.setMsg(danmu);
                    liveData.setRoomid(roomId);
                    liveData.setSubtime(new Date());
                    service.addLiveData(liveData);
                    break;
                case "DANMU_MSG"://普通弹幕
                    JSONArray infoData = json.getJSONArray("info");
                    danmu = json.getJSONArray("info").getString(1);
                    DanmuData danmuData = new DanmuData();
                    danmuData.setModel(infoData.getJSONArray(0).getInteger(1));
                    danmuData.setFontSize(infoData.getJSONArray(0).getInteger(2));
                    danmuData.setFontColor(infoData.getJSONArray(0).getInteger(3));
                    danmuData.setTime(infoData.getJSONArray(0).getLong(4));
                    danmuData.setUCode(infoData.getJSONArray(0).getString(7));
                    danmuData.setDanmu(danmu);
                    danmuData.setUid(infoData.getJSONArray(2).getInteger(0));
                    danmuData.setUname(infoData.getJSONArray(2).getString(1));

                    if (upData != null && upData.getSavedanmu() == 1) {
                        if (danmuManager != null) {
                            danmuManager.addDanmu(danmuData);
                        }
                    }
                    break;
                case "SEND_GIFT"://送礼
                    data = json.getJSONObject("data");
                    giftData = getGiftData(data.getInteger("giftId"));
                    if (giftData == null) {
                        giftData = new GiftData();
                        giftData.setName(data.getString("giftName"));
                        giftData.setId(data.getInteger("giftId"));
                        giftData.setPrice(data.getInteger("price"));
                        giftData.setIcon("");
                        giftData.setDesc("这是阿B没有收录的礼物，金额可能不准（无法判别为免费礼物）");
                        giftData.setRights("?");
                    }
                    danmu = data.getString("uname") + " " + data.getString("action") + " " + giftData.getName();
                    liveData.setType(LiveData.SEND_GIFT);
                    liveData.setUid(data.getInteger("uid"));
                    liveData.setGiftid(giftData.getId());
                    liveData.setGiftindex(data.getInteger("num"));
                    liveData.setGiftname(giftData.getName());
                    liveData.setMsg(danmu);
                    liveData.setPrice(giftData.getPrice());
                    liveData.setPriceofcommission(giftData.getPrice() == 0 ? 0 : giftData.getPrice() / 2);
                    liveData.setSubtime(new Date());
                    liveData.setRoomid(roomId);
                    info.setGiftuser(info.getGiftuser() + 1);
                    service.addLiveData(liveData);
                    break;
                case "COMBO_SEND"://礼物连击
                    data = json.getJSONObject("data");
                    String gift = data.getString("giftName");
                    if (gift == null || "null".equals(gift)) {
                        gift = data.getString("gift_name");
                    }
                    danmu = data.getString("uname") + " " + data.getString("action") + " " + gift + "x" + data.getInteger("batch_combo_num");
                    giftData = getGiftData(data.getInteger("gift_id"));
                    if (giftData == null) {
                        giftData = new GiftData();
                        giftData.setName(data.getString("giftName"));
                        giftData.setId(data.getInteger("giftId"));
                        giftData.setPrice(data.getInteger("price"));
                        giftData.setIcon("");
                        giftData.setDesc("这是阿B没有收录的礼物，金额可能不准（无法判别为免费礼物）");
                        giftData.setRights("?");
                    }
                    liveData.setType(LiveData.COMBO_SEND);
                    liveData.setUid(data.getInteger("uid"));
                    liveData.setGiftid(giftData.getId());
                    liveData.setGiftindex(data.getInteger("batch_combo_num"));
                    liveData.setGiftname(giftData.getName());
                    liveData.setMsg(danmu);
                    liveData.setPrice(giftData.getPrice() * liveData.getGiftindex());
                    liveData.setPriceofcommission(giftData.getPrice() == 0 ? 0 : giftData.getPrice() / 2);
                    liveData.setSubtime(new Date());
                    liveData.setRoomid(roomId);
                    info.setGiftuser(info.getGiftuser() + 1);
                    service.addLiveData(liveData);
                    break;
                case "ENTRY_EFFECT"://舰长进直播间
                    info.setVipuserindex(info.getVipuserindex() + 1);
                    danmu = "[舰长]" + json.getJSONObject("data").getString("uid") + " 进入到直播间";
                    liveData.setUid(json.getJSONObject("data").getInteger("uid"));
                    liveData.setType(LiveData.ENTRY_EFFECT);
                    liveData.setMsg(danmu);
                    liveData.setRoomid(roomId);
                    liveData.setSubtime(new Date());
                    service.addLiveData(liveData);
                    break;
                case "LIVE_INTERACTIVE_GAME"://彩色弹幕？通过游戏弹幕
                    break;
                case "SUPER_CHAT_MESSAGE"://SC
                    data = json.getJSONObject("data");
                    danmu = data.getJSONObject("user_info").getString("uname") + " "
                            + data.getJSONObject("gift").getString("gift_name") + " " + data.getInteger("price") + "元: " + data.getString("message");
                    liveData.setType(LiveData.SUPER_CHAT_MESSAGE);
                    liveData.setUid(data.getInteger("uid"));
                    liveData.setMsg(danmu);
                    liveData.setGiftid(0);
                    liveData.setRoomid(roomId);
                    liveData.setGiftname(data.getJSONObject("gift").getString("gift_name"));
                    liveData.setGiftindex(1);
                    liveData.setPrice(data.getInteger("price") * 1000);
                    liveData.setPriceofcommission(liveData.getPrice() == 0 ? 0 : liveData.getPrice() / 2);
                    liveData.setSubtime(new Date());
                    info.setGiftuser(info.getGiftuser() + 1);
                    service.addLiveData(liveData);
                    break;
                case "USER_TOAST_MSG":
                    break;
                case "GUARD_BUY"://开通/续费 牛逼的东西
                    data = json.getJSONObject("data");
                    liveData.setType(LiveData.GUARD_BUY);
                    liveData.setUid(data.getInteger("uid"));
                    liveData.setMsg(data.getString("gift_name"));
                    liveData.setGiftid(-data.getInteger("guard_level"));
                    liveData.setGiftindex(data.getInteger("num"));
                    liveData.setGiftname(data.getString("gift_name"));
                    liveData.setPrice(data.getInteger("price"));
                    liveData.setPriceofcommission(liveData.getPrice() == 0 ? 0 : liveData.getPrice() / 2);
                    liveData.setSubtime(new Date());
                    liveData.setRoomid(roomId);
                    info.setGiftuser(info.getGiftuser() + 1);
                    service.addLiveData(liveData);
                    break;
                case "LIVE"://开始直播,不过有在心跳包上做检测了，所以也无所谓？
                    if (upData.getLive() == 1) {
                        break;
                    }
                    upData.setLive(1);
                    JSONObject liveInfo = LiveUtils.getLiveInfo(roomId);
                    if (liveInfo == null) {
                        break;
                    }
                    LiveInfo info = new LiveInfo();
                    info.setTitle(liveInfo.getJSONObject("data").getJSONObject("room_info").getString("title"));
                    info.setKeyframe(liveInfo.getJSONObject("data").getJSONObject("room_info").getString("keyframe"));
                    info.setRoomId(liveInfo.getJSONObject("data").getJSONObject("room_info").getInteger("room_id"));
                    info.setShortId(liveInfo.getJSONObject("data").getJSONObject("room_info").getInteger("short_id"));
                    info.setInfo(liveInfo);
                    setLiveInfo(info);
                    StringBuilder builder = new StringBuilder();
                    builder.append(upData.getName()).append("开播了！").append("\n");
                    builder.append(info.getTitle());
                    Tools.download(info.getKeyframe(), new DownloadInterface() {
                        @Override
                        public void onDownload(File file) {
                            super.onDownload(file);
                            QQBotManager.getInstance().sendMessage(file, builder.toString());
                        }
                    });
                    break;
                case "PREPARING"://
                case "SUPER_CHAT_MESSAGE_JPN":
                case "NOTICE_MSG":
                case "HOT_RANK_CHANGED"://榜单更新等无用信息
                case "ONLINE_RANK_COUNT":
                case "ONLINE_RANK_V2":
                case "ONLINE_RANK_TOP3":
                case "ROOM_REAL_TIME_MESSAGE_UPDATE":
                case "WIDGET_BANNER"://鬼知道是啥
                case "HOT_RANK_SETTLEMENT":
                case "PK_BATTLE_SETTLE_V2":
                case "PK_BATTLE_END":
                case "PK_BATTLE_SETTLE":
                case "PK_BATTLE_PRE_NEW":
                case "PK_BATTLE_PRE":
                case "STOP_LIVE_ROOM_LIST"://不知道是啥，停止直播？


                    //com.yutou.bilibili.Tools.Log.i(msg);
                    break;
                default:
                    com.yutou.bilibili.Tools.Log.i("["+roomId+"]"+msg);
                    liveData = new BilibiliLiveData();
                    liveData.setType(LiveData.UNKNOWN_MESSAGE);
                    liveData.setUid(-1);
                    liveData.setMsg(msg);
                    liveData.setRoomid(roomId);
                    liveData.setSubtime(new Date());
                    service.addLiveData(liveData);
            }
            checkLive();
        } catch (Exception e) {
            Log.e(e);
            Log.i("msg = "+msg);
           // Log.i(AppTools.getToDayTimeToString(startTime), roomId, e.getLocalizedMessage());
            /*try {
                JSONObject.parseObject(new String(bytes, StandardCharsets.UTF_8));
                processData(new String(bytes, StandardCharsets.UTF_8), null);
            } catch (Exception e2) {
                try {
                    JSONObject.parseObject(msg);
                    processData(msg, bytes);
                } catch (Exception e3) {
                    Log.e(e2);
                    com.yutou.bilibili.Tools.Log.i("---------ERROR ！！ start-----");
                    com.yutou.bilibili.Tools.Log.i(msg);
                    com.yutou.bilibili.Tools.Log.i("---------ERROR ！！ end-----");
                    // LiveUtils.printHex(bytes);
                }
            }*/

        }

    }

    public void clearInfo() {
        info = new BilibiliLiveInfo();
    }

    public BilibiliLiveInfo getInfo() {
        return info;
    }

    public void setSaveDanmo(boolean saveDanmu) {
        upData.setSavedanmu(saveDanmu ? 1 : 0);
    }

    private static ApplicationContext applicationContext = null;

    @Override
    public void setApplicationContext(@NotNull ApplicationContext applicationContext) throws BeansException {
        if (Live.applicationContext == null) {
            Live.applicationContext = applicationContext;
        }
    }

    public static <T> T getBean(Class<T> clazz) {
        return applicationContext.getBean(clazz);
    }

    public BilibiliUpInfo geData() {
        return upData;
    }

    /**
     * 发送心跳包
     */
    public class sendHeartbeat extends TimerTask {

        public sendHeartbeat() {

        }

        @Override
        public void run() {
            if (!run) {
                cancel();
                return;
            }
            updateUpInfo();
            if (upData.getOfflinelistening() != 1) {
                if (!LiveUtils.isLivePlayer(upData.getRoomid())) {
                    stop();
                }
            }
            try {
                // com.yutou.bilibili.Tools.Log.i("-------发送心跳--------");
                ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
                outputStream.write(LiveUtils.toLH("[object Object]".length() + 16));
                outputStream.write(new byte[]{0, 16, 0, 1, 0, 0, 0, 2, 0, 0, 0, 1});
                outputStream.write("[object Object]".getBytes(StandardCharsets.UTF_8));
                outputStream.flush();
                client.send(outputStream.toByteArray());
            } catch (Exception e) {
                Log.e(e);
                com.yutou.bilibili.Tools.Log.i(client.isClosed());
                com.yutou.bilibili.Tools.Log.i(client.isOpen());

            }

        }
    }

    public GiftData getGiftData(int id) {
        JSONObject item = JSONObject.parseObject(RedisTools.get("bili_gift_" + id, 2));
        GiftData data = new GiftData();
        data.setPrice(item.getInteger("price"));
        data.setName(item.getString("name"));
        data.setId(item.getInteger("id"));
        data.setRights(item.getString("rights"));
        data.setIcon(item.getString("img_basic"));
        data.setDesc(item.getString("desc"));
        return data;
    }

    @Data
    public static class LiveInfo {
        private String title;
        private String keyframe;
        private int roomId;
        private int shortId;
        private JSONObject info;

    }
}
