package net.northking.cctp.se.debug.websocket;

import net.northking.cctp.element.core.exception.ExecuteException;
import net.northking.cctp.se.debug.bean.DebugerConst;
import net.northking.cctp.se.debug.bean.DebuggerResponse;
import net.northking.cctp.se.debug.service.DebugService;
import net.northking.cctp.se.debug.service.DebugSession;
import net.northking.cctp.se.lifecycle.EngineRuntime;
import net.northking.cctp.se.util.JsonUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.boot.logging.LogLevel;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.Map;
import java.util.concurrent.*;

@Component
@ServerEndpoint("/debugger/{type}/{token}/{sessionId}")
public class DebugWebSocketManager implements ApplicationContextAware {
    private static final Logger logger = LoggerFactory.getLogger(DebugWebSocketManager.class);
    private static ApplicationContext context;

    private static Map<String, ScheduledFuture<?>> scheduleMap = new ConcurrentHashMap<>();

    private String sessionId;

    private final static long DELAY = 10L;

    /**
     * 服务端与引擎建立Websocket会话
     *
     * @param session 会话对象
     * @param token   录制调试逻辑会话ID，由界面端生成传入
     */
    @OnOpen
    public void onOpen(Session session,
                       @PathParam("type") String type,
                       @PathParam("token") String token, @PathParam("sessionId") String sessionId) throws InterruptedException {
        this.sessionId = sessionId;
        session.setMaxTextMessageBufferSize(10 * 1024 * 1024);
        session.setMaxBinaryMessageBufferSize(10 * 1024 * 1024);
        // 判断同一个sessionId是否重连
        DebugService debugService = context.getBean(DebugService.class);
        String intern = this.sessionId.intern();

        DebuggerResponse.newResponse()
                .sessionId(sessionId)
                .finish(true)
                .cmd(DebugerConst.ReplyConst.KEY_CMD_LOG)
                .success(true)
                .log(String.format("正在连接执行机：%s", EngineRuntime.getRuntime().getEngineInfo().getIpAddress()), LogLevel.INFO.name(), "正在连接")
                .send(session);
        logger.info("收到脚本设计服务建立连接请求:[type:{},token:{},request_session_id:{}]", type, token, sessionId);
        debugService.checkAndClear(type);
        DebugSession debugSession = null;
        if (scheduleMap.containsKey(sessionId)) {
            scheduleMap.get(sessionId).cancel(false); // 取消定时任务
            scheduleMap.remove(sessionId);
            debugSession = debugService.getDebugSession(this.sessionId);
            // 判断是否同一个token
            if (debugSession != null && debugSession.getDeviceToken().equalsIgnoreCase(token)) {
                logger.info("发现通道存在，正在重连...");
                // 尝试重新连接
                tryReconnect(session, token, sessionId, debugSession);
            }
            if (debugSession == null) {
                debugService.destroyDebugSession(this.sessionId);
            }
        }
        if (null == debugSession) {
            debugSession = debugService.createDebugSession(token, type, session, sessionId);
        }
        if (null == debugSession) {
            try {
                // 输出日志到前台
                DebuggerResponse.newResponse()
                        .sessionId(sessionId)
                        .finish(true)
                        .cmd(DebugerConst.ReplyConst.KEY_CMD_LOG)
                        .success(false)
                        .log("创建调试会话失败，请稍后重新连接", LogLevel.ERROR.name(), "创建连接会话")
                        .send(session);
                // 返回失败信息
                DebuggerResponse.newResponse()
                        .sessionId(sessionId)
                        .finish(true)
                        .cmd(DebugerConst.ReplyConst.KEY_CMD_REPLY)
                        .msg("创建调试器与引擎连接失败")
                        .success(false).send(session);
                session.close(new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE, "通道创建失败，手动关闭通道:sit"));
            } catch (Exception e) {
                logger.error("创建连接失败", e);
                throw new ExecuteException("创建连接失败", e);
            }
        } else {
            DebuggerResponse.newResponse()
                    .sessionId(sessionId)
                    .finish(true)
                    .cmd(DebugerConst.ReplyConst.KEY_CMD_LOG)
                    .success(true)
                    .log("创建连接会话成功", LogLevel.INFO.name(), "创建连接会话")
                    .send(session);
            logger.info("成功建立会话[token:{}, sessionId:{}]", token, sessionId);
        }

    }

    /**
     * 尝试获取锁重连
     *
     * @param session
     * @param token
     * @param sessionId
     * @param debugSession
     */
    private void tryReconnect(Session session, @PathParam("token") String token, @PathParam("sessionId") String sessionId, DebugSession debugSession) {
        if(session!=null){
            debugSession.refresh(token, session);
            logger.info("重新建立会话[token:{}, sessionId:{}]", token, sessionId);
        }
    }

    /**
     * 关闭调试会话
     *
     * @param session
     */
    @OnClose
    public void onClose(Session session, CloseReason reason) throws InterruptedException {
        logger.info("收到关闭WS连接请求");
        logger.debug("close reason -> {}", JsonUtils.toJson(reason));
        if(null != session) {
            if (reason != null && CloseReason.CloseCodes.NORMAL_CLOSURE.getCode() == reason.getCloseCode().getCode()) { // 正常退出
                context.getBean(DebugService.class).destroyDebugSession(this.sessionId);
                logger.info("成功关闭调试会话");
            } else {    // 不正常退出，保留10s信息，过期则摧毁
                ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
                DebugSession debugSession = context.getBean(DebugService.class).getDebugSession(this.sessionId);
                logger.info("连接异常断开，{}秒后释放资源：{}", DELAY, JsonUtils.toJson(debugSession));
                ScheduledFuture<?> schedule = scheduledExecutorService.schedule(() -> {
                    scheduleMap.remove(this.sessionId);
                    context.getBean(DebugService.class).destroyDebugSession(this.sessionId);
                }, DELAY, TimeUnit.SECONDS);
                if (scheduleMap.containsKey(this.sessionId)) {  // 如果已经存在，那么取消原来的定时任务，重新
                    scheduleMap.get(this.sessionId).cancel(false);
                }
                scheduleMap.put(this.sessionId, schedule);
            }
        }
    }

    /**
     * 接收页面消息
     *
     * @param msg
     * @param session
     */
    @OnMessage
    public void onMessage(String msg, Session session) {
        logger.debug("收到服务端消息请求:[msg:{}]", msg);
        logger.debug("消息已加入处理队列");
        context.getBean(DebugService.class).receiveRequestMsg(session, msg);
    }

    @OnError
    public void onError(Session session, Throwable t) {
        String msg = String.format("Session Error->sessionId:%s ", session.getId());
        if (t != null) {
            msg = String.format(msg + " error message: %s", t.getMessage());
        }
        logger.error(msg, t);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        context = applicationContext;
    }
}
