package net.northking.cctp.debuger.service;


import net.northking.cctp.debuger.bean.DebugerConst;
import net.northking.cctp.debuger.bean.DebugerRequest;
import net.northking.cctp.debuger.bean.DebugerResponse;
import net.northking.cctp.debuger.constants.MobileScriptDebugerConstant;
import net.northking.cctp.debuger.util.JsonUtils;
import net.northking.cctp.debuger.websocket.MessageHandler;
import net.northking.cctp.debuger.websocket.WebSocketClientChannel;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.websocket.Session;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.*;


@Service("debugService")
public class MobileScriptDebugServiceImpl implements MobileScriptDebugService {
    private static final Logger logger = LoggerFactory.getLogger(MobileScriptDebugServiceImpl.class);

    @Autowired
    private DeviceInfoService deviceInfoService;



    private final static String TMPL_ENGINE_WS_URI = "ws://%s/debugger/mobile/%s/%s";


    private BlockingQueue<String> webMsgQueue = new LinkedBlockingQueue<>();

    private BlockingQueue<String> engineMsgQueue = new LinkedBlockingQueue<>();

    private ThreadFactory threadFactory = new DebugThreadFactory();
    /**
     * web端到本服务的会话集合
     */
    private ConcurrentMap<String,Session> sessionMap = new ConcurrentHashMap<>();

    /**
     * 设备退出标记,key->requestId
     */
    private ConcurrentMap<String,String> deviceExistMap = new ConcurrentHashMap<>();
    /**
     * 引擎端到本服务的会话集合
     */
    private ConcurrentMap<String, WebSocketClientChannel> engineSessionMap = new ConcurrentHashMap<>();

    /**
     *  缓存用户token的map
     */
    private static Map<String, String> userTokenMap = new ConcurrentHashMap<>();


    private Map<String,String> deviceTokens = new HashMap<>();
    private boolean running = true;
    /**
     * 引擎端消息接收器
     */
    private MessageHandler engineMessageHandler = new MessageHandler() {
        @Override
        public void handle(String msg) {
            engineMsgQueue.add(msg);
        }
    };

    @Override
    public void removeEngineSession(String sessionId){
        logger.info("remove engineSession by sessionId====>{}",sessionId);
        engineSessionMap.remove(sessionId);
    }

    @Override
    public void exitDeviceUsage(String sessionId) {
        String deviceToken = deviceTokens.remove(sessionId);
        deviceInfoService.exitDeviceUsage(deviceToken);
    }

    @Override
    public String removeUserToken(String sessionId) {
        return userTokenMap.remove(sessionId);
    }

    @Override
    public void addUserToken(String sessionId, String userToken) {
        userTokenMap.put(sessionId, userToken);
    }

    @Override
    public String getUserToken(String sessionId) {
        return userTokenMap.get(sessionId);
    }


    @PostConstruct
    public void init(){
        logger.info("Mobile script debug service started.");
        threadFactory.newThread(() -> {
            Thread.currentThread().setName(MobileScriptDebugerConstant.DEBUG_THREAD_WEB_MSG_PROCESSOR);
            while(running){
                try{
                    String msg = webMsgQueue.take();//此方法阻塞
                    logger.debug("Take a web msg:{}",msg);
                    handleWebMessage(msg);
                }catch (Throwable t){
                    logger.warn("Error when process web msg",t);
                    try{
                        TimeUnit.SECONDS.sleep(1l);//当发生异常时，停留1秒后再处理下一个消息，以防无限循环
                    }catch (InterruptedException e){
                        Thread.currentThread().interrupt();
                    }
                }

            }

        }).start();
        threadFactory.newThread(() -> {
            Thread.currentThread().setName(MobileScriptDebugerConstant.DEBUG_THREAD_ENGINE_MSG_PROCESSOR);
            while(running){
                try{
                    String msg = engineMsgQueue.take();//此方法阻塞
                    logger.debug("Take a engine msg:{}",msg);
                    handleEngineMessage(msg);
                }catch (Throwable t){
                    logger.warn("Error when processing engine msg",t);
                    try{
                        TimeUnit.SECONDS.sleep(1l);//当发生异常时，停留1秒后再处理下一个消息，以防无限循环
                    }catch (InterruptedException e){
                        Thread.currentThread().interrupt();
                    }
                }
            }
        }).start();

    }

    public  void destroy(){
        this.running = false;
    }

    @Override
    public void addDebugSession(String sessionId, Session session) {
        sessionMap.put(sessionId,session);
    }

    @Override
    public void removeDebugSession(String sessionId, Session session) {
        sessionMap.remove(sessionId,session);
        WebSocketClientChannel webSocketClientChannel = engineSessionMap.remove(sessionId);
        if (null != webSocketClientChannel) {
            logger.info("页面ws连接断开，断开引擎。。。。。。");
            webSocketClientChannel.close();
        }
    }


    /**
     * 用于接收web端消息
     * @param msg
     */
    @Override
    public void processWebMsg(String msg) {
        webMsgQueue.add(msg);
    }


    private DebugerResponse buildResponse(DebugerRequest request,boolean finish,boolean success,String msg){
        return DebugerResponse.newRespone()
                .sessionId(request.getSessionId())
                .requestId(request.getRequestId())
                .finish(finish)
                .success(success)
                .msg(msg);
    }
    /**
     * 处理web端消息
     * @param msg
     */
    private void handleWebMessage(String msg){
        DebugerRequest request = JsonUtils.fromJsonString(msg, DebugerRequest.class);
        String cmd = request.getCmd();
        if (DebugerConst.CommandConst.CMD_DEVICE_ENTER.equals(cmd)){
            String deviceToken = (String)request.getDataValue(DebugerConst.ReqConst.KEY_TOKEN);
            if(StringUtils.isBlank(deviceToken)){
                DebugerResponse response =  buildResponse(request,true,false,"设备token不能为空");
                sendMsg2Web(request.getSessionId(),JsonUtils.toJson(response));
                return;
            }
            try {
                deviceTokens.put(request.getSessionId(),deviceToken);
                connect2Engine(request.getSessionId(),deviceToken);
                sendMsg2Engine(request.getSessionId(),JsonUtils.toJson(request));
            }catch (Exception e){
                logger.warn("Error when connect to engine",e);
                DebugerResponse response =  buildResponse(request,true,false,"执行引擎连接失败，请稍后再试");
                sendMsg2Web(request.getSessionId(),JsonUtils.toJson(response));
            }
        }else if (DebugerConst.CommandConst.CMD_DEVICE_EXIT.equals(cmd)){
            //为了断开调试服务和引擎之间的ws
            deviceExistMap.put(request.getRequestId(), request.getSessionId());
            sendMsg2Engine(request.getSessionId(),JsonUtils.toJson(request));
        } else {
            sendMsg2Engine(request.getSessionId(),JsonUtils.toJson(request));
        }


    }
    /**
     * 处理引擎端消息
     * @param msg
     */
    private void handleEngineMessage(String msg){
        DebugerResponse response = JsonUtils.fromJsonString(msg, DebugerResponse.class);
        Map<String, Object> data = response.getData();
        String request = (String) data.get(DebugerConst.KEY_REQUESTID);
        try {
            if (null != request) {
                String remove = deviceExistMap.remove(request);
                if (null != remove) {  //设备退出
                    WebSocketClientChannel webSocketClientChannel = engineSessionMap.remove(response.getSessionId());
                    if (null != webSocketClientChannel) {
                        webSocketClientChannel.close();
                    }
                }
            }
        } catch (Exception e) {
            logger.warn("设备与引擎连接断开失败",e);
        }finally {
            sendMsg2Web(response.getSessionId(),msg);
        }
    }
    /**
     * 连接引擎端
     */
    private void connect2Engine(String sessionId,String deviceToken) throws Exception{
        String engineUri = getEngineWsURI(sessionId,deviceToken);
        logger.info("开始连接执行引擎:{}",engineUri);
        WebSocketClientChannel socketClientChannel = new WebSocketClientChannel(new URI(engineUri),engineMessageHandler,sessionId);
        socketClientChannel.connect();
        if (socketClientChannel.isConnected()){
            engineSessionMap.put(sessionId,socketClientChannel);
            logger.info("执行引擎连接成功。");
        }else {
            logger.info("执行引擎连接失败。");
            throw new Exception("连接引擎超时");
        }

    }

    /**
     * 从设备管理模块随机获取一台执行引擎的websocket连接地址
     * @return
     */
    private String getEngineWsURI(String sessionId, String deviceToken){
        String engineAddr = deviceInfoService.getEngineAddress(deviceToken);
        if (null == engineAddr) {
            throw new RuntimeException("未获取到有效的引擎地址");
        }
        return String.format(TMPL_ENGINE_WS_URI,engineAddr,deviceToken,sessionId);
    }


    private void sendMsg2Engine(String sessionId,String msg){
        WebSocketClientChannel webSocketClientChannel = engineSessionMap.get(sessionId);
        if (null == webSocketClientChannel){
            logger.warn("channel for sessionId:{} not found",sessionId);
            logger.info("Try to reconnect ...");
            DebugerResponse response = DebugerResponse.newRespone()
                    .cmd(DebugerConst.CommandConst.CMD_DEVICE_STATUS)
                    .sessionId(sessionId);
            try{
                response.withData(DebugerConst.ReplyConst.KEY_DEVICE_STATUS_TYPE,DebugerConst.ReplyConst.VAL_DEVICE_STATUS_CONNECTING);
                sendMsg2Web(sessionId,response);
                int times = 0 ;
                while (webSocketClientChannel==null && times++ <3 ){
                    String deviceToken = deviceTokens.get(sessionId);
                    connect2Engine(sessionId,deviceToken);
                    webSocketClientChannel = engineSessionMap.get(sessionId);
                }
                if (webSocketClientChannel!=null){
                    response.withData(DebugerConst.ReplyConst.KEY_DEVICE_STATUS_TYPE,DebugerConst.ReplyConst.VAL_DEVICE_STATUS_CONNECTED);
                }else {
                    response.withData(DebugerConst.ReplyConst.KEY_DEVICE_STATUS_TYPE,DebugerConst.ReplyConst.VAL_DEVICE_STATUS_DISCONNECTED);
                }
            }catch (Exception e){
                logger.error("连接引擎失败",e);
                response.withData(DebugerConst.ReplyConst.KEY_DEVICE_STATUS_TYPE,DebugerConst.ReplyConst.VAL_DEVICE_STATUS_DISCONNECTED);
            }finally {
                sendMsg2Web(sessionId,response);
            }
        }
        if (null != webSocketClientChannel){
            webSocketClientChannel.send(msg);
        }else{
            logger.warn("Failed to send msg to engine:[sessionId:{},msg:{}]",sessionId,msg);
        }
    }

    /**
     * 向WEB端响应消息
     * @param sessionId
     * @param response
     */
    private void sendMsg2Web(String sessionId,DebugerResponse response){
       sendMsg2Web(sessionId,JsonUtils.toJson(response));
    }
    /**
     * 向WEB端响应消息
     * @param sessionId
     * @param msg
     */
    private void sendMsg2Web(String sessionId,String msg){
       Session session = sessionMap.get(sessionId);
       if(null == session){
         logger.warn("Session[sessionId:{}] not found",sessionId);
         return;
       }
       try{
           session.getBasicRemote().sendText(msg);
       }catch (Exception e){
            logger.error(String.format("Error when responsing to sessionId:%s",sessionId),e);
            //增加重发机制
       }
    }


    private class DebugThreadFactory implements ThreadFactory{

        @Override
        public Thread newThread(Runnable r) {
            Thread thread = new Thread(r);
            return thread;
        }
    }


}
