package me.zhengjie.modules.system.service.impl;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import me.zhengjie.modules.security.config.WebSocketConfig;
import me.zhengjie.modules.security.config.enums.*;
import me.zhengjie.modules.system.domain.BusDevice;
import me.zhengjie.modules.system.domain.dto.ImageDataVo;
import me.zhengjie.modules.system.service.WebSocketService;
import me.zhengjie.modules.system.service.webstocket.SocketMsg;
import me.zhengjie.modules.system.service.webstocket.req.WebSocketReqDTO;
import me.zhengjie.modules.system.service.webstocket.req.WebSocketReqData;
import me.zhengjie.modules.system.service.webstocket.res.*;
import me.zhengjie.utils.StringUtils;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Arrays;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

@ServerEndpoint("/webSocket/sdcp")
@Slf4j
@Component
public class WebSocketSdcpServiceImpl extends WebSocketService {

    /**
     * 连接调用方法
     * @param session
     */
    @OnOpen
    public void onOpen(Session session) {
        this.session = session;
        this.channel = WebSocketConfig.TOPIC_PREFIX;
        log.info("SDCP服务端 连接成功：channel={}", channel);
        clients.put(channel, this);
        openDeviceStatus(null);
    }
    /**
     * 连接关闭调用的方法
     * @param session
     */
    @OnClose
    public void onClose(Session session) {
        WebSocketService collect = getSocket(WebSocketConfig.TOPIC_PREFIX);
        if (null == collect) { return; }
        log.info("SDCP服务端 连接断开：channel={}", collect.channel);
        closeDeviceStatus();
    }

    /**
     * 连接异常时
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        WebSocketService collect = getSocket(WebSocketConfig.TOPIC_PREFIX);
        if (null == collect) { return; }
        long time = System.currentTimeMillis();
        log.info("【+++++++++++++++++++++++++++++ ↓ " + time + " ↓ +++++++++++++++++++++++++++++】");
        log.info("SDCP服务端 发现异常：channel={}，errorMsg={}", collect.channel, error.getMessage());
        error.printStackTrace();
        log.error("SDCP服务端 发现异常", error);
        log.info("【+++++++++++++++++++++++++++++ ↑ " + time + " ↑ +++++++++++++++++++++++++++++】");
    }

    /**
     * 收到客户端消息后调用的方法
     * @param message 客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(Session session, String message) {
        WebSocketService collect = getSocket(WebSocketConfig.TOPIC_PREFIX);
        if (null == collect) { return; }
        long time = System.currentTimeMillis();
        log.info("【+++++++++++++++++++++++++++++ ↓ " + time + " ↓ +++++++++++++++++++++++++++++】");
        log.info("SDCP服务端 收到消息：channel={}，message={}", collect.channel, message);
        serviceMessageHandle(session, message);
        log.info("【+++++++++++++++++++++++++++++ ↑ " + time + " ↑ +++++++++++++++++++++++++++++】");
    }

    // 连接关闭状态刷新
    private void closeDeviceStatus() {
        CurrentStatusEnum status = CurrentStatusEnum.SDCP_MACHINE_STATUS_DEFAULT;
        PrintInfoStatusEnum print = PrintInfoStatusEnum.SDCP_PRINT_STATUS_DEFAULT;
        ErrorStatusEnum error = ErrorStatusEnum.SDCP_PRINT_ERROR_DEFAULT;
        String remark = "已关闭连接";
        myDeviceService.updateDeviceStatus(null, status, print, error, remark, null);
        sendVueMessage(JSON.toJSONString(new SocketMsg(new HashMap<>() , remark, MsgEnum.CLOSE)), null, null);
    }

    public void openDeviceStatus(String thisDeviceSn) {
        log.info(">>> 初始化设备状态");
        if (StringUtils.isEmpty(thisDeviceSn)) {
            for (String deviceSn : deviceList) {
                pushDeviceStatus(deviceSn);
            }
        } else {
            pushDeviceStatus(thisDeviceSn);
        }
        log.info("<<< 初始化设备");
    }

    // 初始状态刷新消息(Cmd:0) 指令
    private void pushDeviceStatus(String deviceSn) {
        WebSocketService socket = getSocket(WebSocketConfig.TOPIC_PREFIX);
        if (null == socket || null == socket.session || !socket.session.isOpen()) {
            log.info("<<< 第三方未连接，无法初始化，略过");
            return;
        }
        long currentTimeLong = System.currentTimeMillis();
        Map<String, Object> dataParam = new HashMap<>();
        dataParam.put("UserId", socket.session.getId());
        WebSocketReqData data = new WebSocketReqData();
        data.setCmd(0);
        data.setData(dataParam);
        data.setRequestID(currentTimeLong + "");
        data.setMainboardID(deviceSn);
        data.setTimeStamp(currentTimeLong);
        data.setFrom(0);
        WebSocketReqDTO reqDTO = new WebSocketReqDTO();
        reqDTO.setTopic(WebSocketConfig.TOPIC_PREFIX + "/" + MethodEnum.request.name() + "/" + deviceSn);
        reqDTO.setData(data);
        String message = JSON.toJSONString(reqDTO);
        try {
            log.info("发送【初始化】指令：channel={}，message={}", channel, message);
            socket.session.getBasicRemote().sendText(message);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    // 处理二进制图片
    private void byteImgDataHandle(String message) {
        Map<String, String> dataMap = Arrays.stream(message.split("&"))
                                            .map(s-> s.split("="))
                                            .collect(Collectors.toMap(data-> data[0], data -> data[1]));
        String deviceSn = dataMap.get("MainboardID").toString();
        String userId = dataMap.get("UserId").toString();
        String img64 = dataMap.get("Data").toString();
        WebSocketService uSocket = clients.get(userId);
        if (null == uSocket || null == uSocket.session || !uSocket.session.isOpen()) {
            log.info("<<< 用户【{}】已退出连接，忽略", userId);
            return;
        }
        ImageDataVo imageDataVo = imageUser.get(uSocket.session.getId());
        imageDataVo.imgWrite(Base64.getDecoder().decode(img64));
        imageUser.put(uSocket.session.getId(), imageDataVo);
        Map<String, Object> commandResult = new HashMap<>();
        commandResult.put("deviceSn", deviceSn);
        commandResult.put("chunkIndex", (Integer.parseInt(dataMap.get("ChunkIndex")) + 1) + "");   // 图片当前分片数
        commandResult.put("totalChunks", dataMap.get("TotalChunks")); // 图片总的分片数
        sendVueMessage(JSON.toJSONString(new SocketMsg(commandResult, "图片传输中", MsgEnum.WRITEIMG)), uSocket.channel, uSocket.session);
    }

    // 处理服务端接收到的消息
    private void serviceMessageHandle(Session session, String message) {
        if (StringUtils.isEmpty(message)) { return; }
        // 如果是第三方发来的[心跳]检测，返回 pong
        if (WebSocketConfig.PING.equals(message)) {
            log.info("<<< 心跳反馈");
            try { session.getBasicRemote().sendText(WebSocketConfig.PONG);
            } catch (IOException e) { throw new RuntimeException(e); }
            return;
        }
        message = message.replace("\\", "");
        WebSocketResDTO param = null;
        MethodEnum method = null;
        try {
            param = JSON.parseObject(message, WebSocketResDTO.class);
            method = param.getMethod();
        } catch (Exception e) {
            // json转换失败，说明是传输图片
            byteImgDataHandle(message);
            return;
        }
        if (null == method) {
            log.info("<<< 请求类型有误，忽略");
            return;
        }
        String deviceSn = param.getMainboardID();
        // 只关心已有的设备
        if (!deviceList.contains(deviceSn)) {
            log.info("<<< 设备 {} 在系统未找到，忽略", deviceSn);
            return;
        }
        if (method == MethodEnum.request) {
            return;
        }
        // 状态与消息前置参数定义
        String uChannel = null;
        Session uSession = null;
        String remark = "";
        MsgEnum msgEnum = null;
        WebSocketResDTO resDTO = JSON.parseObject(message, WebSocketResDTO.class);
        Map<String, Object> commandResult = new HashMap<>();
        commandResult.put("deviceSn", deviceSn);
        if (method == MethodEnum.response) {
            // 指令响应消息
            WebSocketResData data = param.getData();
            if (null == data.getData() || "".equals(data.getData().toString())) {
                log.info("<<< 该消息不是用户指令反馈，忽略");
                return;
            }
            JSONObject cData = JSON.parseObject(data.getData().toString());
            if (!cData.containsKey("UserId")) {
                log.info("<<< 该消息不是用户指令反馈，忽略");
                return;
            }
            WebSocketService uSocket = clients.get(cData.get("UserId").toString());
            if (!(null == uSocket || null == uSocket.session || !uSocket.session.isOpen())) {
                uChannel = uSocket.channel;
                uSession = uSocket.session;
            }
            Integer cmd = data.getCmd();
            // 开始打印
            if (cmd.equals(128)) {
                int Ack = Integer.parseInt(cData.get("Ack").toString());
                if (Ack != 0) {
                    if (Ack == 1) {
                        remark = "设备忙";
                    } else if (Ack == 2) {
                        remark  = "未找到目标文件";
                    } else if (Ack == 3) {
                        remark = "文件读取失败";
                    } else if (Ack == 4) {
                        remark = "文件分辨率不匹配";
                    } else if (Ack == 5) {
                        remark = "无法识别的文件格式";
                    }
                    msgEnum = MsgEnum.ERROR;
                }
            }
            // 暂停打印
            else if (cmd.equals(129)) {
                int Ack = Integer.parseInt(cData.get("Ack").toString());
                if (Ack != 0) {
                    remark = "暂停打印失败";
                    msgEnum = MsgEnum.ERROR;
                }
            }
            // 退出打印
            else if (cmd.equals(130)) {
                int Ack = Integer.parseInt(cData.get("Ack").toString());
                if (Ack != 0) {
                    remark = "退出打印失败";
                    msgEnum = MsgEnum.ERROR;
                }
            }
            // 继续
            else if (cmd.equals(131)) {
                int Ack = Integer.parseInt(cData.get("Ack").toString());
                if (Ack != 0) {
                    remark = "继续打印失败";
                    msgEnum = MsgEnum.ERROR;
                }
            }
            // 图片异物报警继续
            else if (cmd.equals(132)) {
                int Ack = Integer.parseInt(cData.get("Ack").toString());
                if (Ack != 0) {
                    remark = "图片异物报警继续失败";
                    msgEnum = MsgEnum.ERROR;
                }
            }
            // 图片异物报警退出
            else if (cmd.equals(133)) {
                int Ack = Integer.parseInt(cData.get("Ack").toString());
                if (Ack != 0) {
                    remark = "图片异物报警退出失败";
                    msgEnum = MsgEnum.ERROR;
                }
            }
            // 获取图片
            else if (cmd.equals(385)) {
                // 处理图片异常/图片接收时
                if (cData.containsKey("Ack")) {
                    int Ack = Integer.parseInt(cData.get("Ack").toString());
                    // 准备接收图片前期信息
                    if (Ack == 0 && cData.containsKey("Type") && cData.get("Type").toString().equals("start")) {
                        remark = "图片准备传输";
                        msgEnum = MsgEnum.WRITEIMG;
                    }
                    // 图片获取失败
                    else if (Ack == 1) {
                        remark = "图片获取失败";
                        msgEnum = MsgEnum.ERROR;
                    }
                    // 不支持该类型图片
                    else if (Ack == 2) {
                        remark = "不支持该类型图片";
                        msgEnum = MsgEnum.ERROR;
                    }
                    // 未知错误
                    else if (Ack == 3) {
                        remark = "未知错误";
                        msgEnum = MsgEnum.ERROR;
                    }
                } else if (cData.containsKey("Type") && cData.get("Type").toString().equals("end")) {
                    if (null == uSession) {
                        log.info("<<< 用户【{}】已退出连接无需发送图片，忽略", cData.get("UserId"));
                        return;
                    }
                    // 图片接收结束，生成图片，给前端推送图片链接
                    ImageDataVo imageDataVo = imageUser.get(uSession.getId());
                    byte[] imageAllData = imageDataVo.getFullImageData();
                    String folder = "image/";
                    String imgDir = myFileProperties.getPath().getPath() + folder;
                    String imgUrl = imageDataVo.saveImage(imageAllData, imgDir);
                    commandResult.put("imageUrl", "/file/" + folder + imgUrl);
                    remark = "图片完成传输";
                    msgEnum = MsgEnum.INFO;
                    imageUser.remove(uSession.getId());
                }
            }
            // 打开/关闭视频
            else if (cmd.equals(386)) {
                int Ack = Integer.parseInt(cData.get("Ack").toString());
                if (Ack == 0) {
                    commandResult.put("videoUrl", cData.get("VideoUrl"));
                    remark = "指令响应成功";
                    msgEnum = MsgEnum.INFO;
                } else {
                    if (Ack == 1) {
                        remark = "超过最大同时拉流限制";
                    } else if (Ack == 2) {
                        remark = "摄像头不存在";
                    } else if (Ack == 3) {
                        remark = "未知错误";
                    }
                    msgEnum = MsgEnum.ERROR;
                }
            }
        } else if (method == MethodEnum.status) {
            WebSocketResStatus resStatus = resDTO.getStatus();
            CurrentStatusEnum status = CurrentStatusEnum.getEnumByNum(resStatus.getCurrentStatus());;
            PrintInfoStatusEnum print = PrintInfoStatusEnum.getEnumByNum(resStatus.getPrintInfo().getStatus());;
            ErrorStatusEnum error = ErrorStatusEnum.getEnumByNum(resStatus.getPrintInfo().getErrorNumber());
            remark = "状态已更新";
            myDeviceService.updateDeviceStatus(deviceSn, status, print, error, remark, JSON.toJSONString(resDTO.getStatus().getPrintInfo()));
            sendVueMessage(JSON.toJSONString(new SocketMsg(commandResult, remark, MsgEnum.STATUS)), null, null);
            return;
        } else if (method == MethodEnum.attributes) {
            WebSocketResAttributes attributes = resDTO.getAttributes();
            BusDevice entity = new BusDevice();
            entity.setDeviceSn(deviceSn);
            entity.setName(attributes.getName());
            entity.setModel(attributes.getMachineName());
            entity.setFirmwareVersion(attributes.getFirmwareVersion());
            entity.setProtocolVersion(attributes.getProtocolVersion());
            myDeviceService.updateOrAdd(entity);
            return;
        } else if (method == MethodEnum.error) {
            WebSocketResStatus resStatus = resDTO.getStatus();
            CurrentStatusEnum status = CurrentStatusEnum.getEnumByNum(resStatus.getCurrentStatus());;
            PrintInfoStatusEnum print = PrintInfoStatusEnum.getEnumByNum(resStatus.getPrintInfo().getStatus());;
            ErrorStatusEnum error = ErrorStatusEnum.getEnumByNum(resStatus.getPrintInfo().getErrorNumber());
            remark = "设备发生异常";
            myDeviceService.updateDeviceStatus(deviceSn, status, print, error, remark, null);
            sendVueMessage(JSON.toJSONString(new SocketMsg(commandResult, remark, MsgEnum.STATUS)), null, null);
            return;
        } else if (method == MethodEnum.notice) {

        }
        // 给指定连接发送消息
        if (null != msgEnum) {
            sendVueMessage(JSON.toJSONString(new SocketMsg(commandResult, remark, msgEnum)), uChannel, uSession);
        }
    }

    // 发送消息给指定前端
    private void sendVueMessage(String message, String uChannel, Session uSession) {
        if (null != uSession) {
            sendMessage(message, uChannel, uSession);
            return;
        }
        for (Map.Entry<String, WebSocketService> entry : clients.entrySet()) {
            WebSocketService server = entry.getValue();
            String channel = server.channel;
            Session session = server.session;
            if (channel.equals(WebSocketConfig.TOPIC_PREFIX) || null == session || !session.isOpen()) { continue; }
            sendMessage(message, channel, session);
        }
    }

    // 发送消息给前端
    private void sendMessage(String message, String channel, Session session) {
        if (null != session && !session.isOpen()) { return; }
        try {
            log.info(">>> 推送消息给VUE：channel={}，message={}", (StringUtils.isEmpty(channel) ? session.getId() : channel), message);
            session.getBasicRemote().sendText(message);
        } catch (IOException e) {
            e.printStackTrace();
            log.info("<<< 推送消息给VUE-异常 原因：", e);
        }
    }
}