package net.northking.cctp.debuger.service;


import com.alibaba.fastjson.JSON;
import net.northking.cctp.common.s3.SimpleStorageService;
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 org.springframework.util.CollectionUtils;

import javax.annotation.PostConstruct;
import javax.websocket.Session;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.*;


@Service("apiDebugService")
public class InterfaceScriptDebugServiceImpl implements InterfaceScriptDebugService {
    private static final Logger logger = LoggerFactory.getLogger(InterfaceScriptDebugServiceImpl.class);

    @Autowired
    private DeviceInfoService deviceInfoService;


    private final static String TMPL_ENGINE_WS_URI = "ws://%s/debugger/interface/%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<>();
    /**
     * 引擎端到本服务的会话集合
     */
    private ConcurrentMap<String, WebSocketClientChannel> engineSessionMap = new ConcurrentHashMap<>();

    private Map<String,String> deviceTokens = new HashMap<>();
    private boolean running = true;

    @Autowired
    private SimpleStorageService storageService;
    /**
     * 引擎端消息接收器
     */
    private MessageHandler engineMessageHandler = new MessageHandler() {
        @Override
        public void handle(String msg) {
            engineMsgQueue.add(msg);
        }
    };




    @PostConstruct
    public void init(){
        logger.debug("Interface script debug service started.");
        threadFactory.newThread(() -> {
            Thread.currentThread().setName(MobileScriptDebugerConstant.DEBUG_THREAD_API_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){
                        logger.error("休眠线程断开异常：",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){
                        logger.error("休眠线程断开异常：",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) {
            webSocketClientChannel.close();
        }
    }


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

    @Override
    public void removeEngineSession(String sessionId,String requestId) {
        logger.debug("remove apiengineSession by sessionId====>{},requestId====>{}",sessionId,requestId);
        WebSocketClientChannel remove = engineSessionMap.remove(sessionId);
        try {
            Session session = sessionMap.get(sessionId);
            if(session!=null){
                DebugerResponse response = DebugerResponse.newRespone()
                        .sessionId(sessionId).requestId(requestId);
                //发日志及设置结果
                sendResultLog2Web(MobileScriptDebugerConstant.ENGINE_LOG_UNEXPECTED_DISCONNECTION,response);
                sendFailResult2Web(MobileScriptDebugerConstant.ENGINE_LOG_UNEXPECTED_DISCONNECTION,response);
            }
            sessionMap.remove(sessionId,session);
        } catch (Exception e) {
            logger.error("断开ws异常",e);
        }
        try {
            remove.close();
        } catch (Exception e) {
            logger.error("与引擎断开连接失败：",e);
        }
    }


    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();
        String deviceToken = "";
        if (DebugerConst.CommandConst.CMD_DEBUG_API.equals(cmd)){
            logger.debug("智能接口调试。。。。");
            deviceToken = (String)request.getDataValue(DebugerConst.ReqConst.KEY_DEVICE);
            if(StringUtils.isBlank(deviceToken)){
                DebugerResponse response =  buildResponse(request,true,false,"设备地址不能为空");
                sendMsg2Web(request.getSessionId(),JsonUtils.toJson(response));
                return;
            }
            //获取脚本信息
            handleInterfaceDebug(request);
        }else{
            logger.debug("录制接口调试。。。。");
            if(DebugerConst.CommandConst.CMD_REPLAY_START.equals(cmd)){
                deviceToken = deviceTokens.get(request.getSessionId());
                if(StringUtils.isBlank(deviceToken)){
                    //随机获取一台引擎
                    String projectId = (String)request.getDataValue(DebugerConst.ReqConst.KEY_PROJECT_ID);
                    Map<String, Object> engineInfo = deviceInfoService.getEngineInfo(projectId);

                    if(CollectionUtils.isEmpty(engineInfo)){
                        DebugerResponse response =  buildResponse(request,true,false,MobileScriptDebugerConstant.ENGINE_LOG_INFORMATION_RETRIEVE_FAILED);
                        sendMsg2Web(request.getSessionId(),JsonUtils.toJson(response));
                        return;
                    }
                    String ipAddr = (String)engineInfo.get(MobileScriptDebugerConstant.IP_ADDR);
                    String port = (String)engineInfo.get(MobileScriptDebugerConstant.IP_PORT);
                    deviceToken = ipAddr+":"+port;
                }
            }
        }

        logger.debug("回放指令："+ JSON.toJSONString(request));
        try {
            WebSocketClientChannel socketClientChannel = engineSessionMap.get(request.getSessionId());
            if(socketClientChannel == null){
                connect2Engine(request.getSessionId(),deviceToken,request.getRequestId());
                deviceTokens.put(request.getSessionId(),deviceToken);
            }
            sendMsg2Engine(request.getSessionId(),JsonUtils.toJson(request));
        }catch (Exception e){
            logger.warn("Error when creating debug channel",e);
            DebugerResponse response = DebugerResponse.newRespone()
                    .sessionId(request.getSessionId())
                    .requestId(request.getRequestId());
            //发日志及设置结果
            sendResultLog2Web(MobileScriptDebugerConstant.ENGINE_LOG_CONNECTION_EXECUTION_FAILED,response);
            //发送终止标识
            sendFailResult2Web(MobileScriptDebugerConstant.ENGINE_LOG_CONNECTION_EXECUTION_FAILED,response);
        }



    }
    /**
     * 处理引擎端消息
     * @param msg
     */
    private void handleEngineMessage(String msg){
        DebugerResponse response = JsonUtils.fromJsonString(msg, DebugerResponse.class);
        sendMsg2Web(response.getSessionId(),msg);
    }
    /**
     * 连接引擎端
     */
    private void connect2Engine(String sessionId,String deviceToken,String requestId) throws Exception{
        //获取设备websoket地址
        String engineUri = String.format(TMPL_ENGINE_WS_URI,deviceToken,sessionId,sessionId);
        logger.debug("开始连接执行引擎:{}",engineUri);
        WebSocketClientChannel socketClientChannel = new WebSocketClientChannel(new URI(engineUri),engineMessageHandler,
                sessionId,requestId);
        socketClientChannel.connect();
        if (socketClientChannel.isConnected()){
            engineSessionMap.put(sessionId,socketClientChannel);
            logger.debug("执行引擎连接成功。");
        }else {
            logger.debug("执行引擎连接失败。");
            throw new Exception("连接引擎超时");
        }

    }


    private void sendMsg2Engine(String sessionId,String msg){
        WebSocketClientChannel webSocketClientChannel = engineSessionMap.get(sessionId);
        if (null == webSocketClientChannel){
            logger.warn("channel for sessionId:{} not found",sessionId);
            logger.debug("Try to reconnect ...");
            DebugerRequest request = JsonUtils.fromJsonString(msg, DebugerRequest.class);
            DebugerResponse response = DebugerResponse.newRespone()
                    .cmd(DebugerConst.CommandConst.CMD_DEVICE_STATUS)
                    .sessionId(sessionId).requestId(request.getRequestId());

            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,request.getRequestId());
                    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.KEY_REQUESTID,request.getRequestId());
                //发日志
                sendResultLog2Web(e.getMessage(),response);
                //发送终止标识
                response.setCmd(MobileScriptDebugerConstant.CMD_REPLY);
                response.getData().remove(MobileScriptDebugerConstant.DEBUGER_RESPONSE_LOG);
                response.getData().remove(MobileScriptDebugerConstant.DEBUGER_RESPONSE_LOG_LEVEL);
                response.withData(DebugerConst.ReplyConst.KEY_MSG,MobileScriptDebugerConstant.ENGINE_LOG_ENCOUNTERED_EXECUTION+":"+msg);
                response.withData(DebugerConst.ReplyConst.KEY_FINISH,true);
                response.withData(DebugerConst.ReplyConst.KEY_SUCCESS,false);
            }finally {
                sendMsg2Web(sessionId,response);
            }
        }
        if (null != webSocketClientChannel){
            webSocketClientChannel.send(msg);
        }else{
            logger.warn("Failed to send msg to engine:[sessionId:{},msg:{}]",sessionId,msg);
        }
    }

    private void sendResultLog2Web(String msg,DebugerResponse response){
        //发日志
        response.setCmd(MobileScriptDebugerConstant.CMD_REPLY_LOG);
        response.withData(MobileScriptDebugerConstant.DEBUGER_RESPONSE_LOG_TIME,new Date().getTime());
        response.withData(MobileScriptDebugerConstant.DEBUGER_RESPONSE_LOG,msg);
        response.withData(MobileScriptDebugerConstant.DEBUGER_RESPONSE_LOG_LEVEL,MobileScriptDebugerConstant.DEBUGER_RESPONSE_LOG_ERROR);
        sendMsg2Web(response.getSessionId(),response);
    }

    private void sendFailResult2Web(String msg,DebugerResponse response){
        response.setCmd(MobileScriptDebugerConstant.CMD_REPLY);
        response.getData().remove(MobileScriptDebugerConstant.DEBUGER_RESPONSE_LOG);
        response.getData().remove(MobileScriptDebugerConstant.DEBUGER_RESPONSE_LOG_LEVEL);
        response.withData(MobileScriptDebugerConstant.DEBUGER_RESPONSE_LOG_TIME,new Date().getTime());
        response.withData(MobileScriptDebugerConstant.DEBUGER_RESPONSE_LOG_MSG,MobileScriptDebugerConstant.ENGINE_LOG_ENCOUNTERED_EXECUTION+":"+msg);
        response.withData(DebugerConst.ReplyConst.KEY_FINISH,true);
        response.withData(DebugerConst.ReplyConst.KEY_SUCCESS,false);
        sendMsg2Web(response.getSessionId(),response);
    }

    /**
     * 向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{
           logger.debug("send to web msg:"+msg);
           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;
        }
    }


    /**
     * 接口调试
     * @param request
     */
    private void handleInterfaceDebug(DebugerRequest request) {
        try {
            String scriptPath = request.getDataValue(MobileScriptDebugerConstant.DEBUGER_REQUEST_SCRIPT_PATH).toString();
            if(StringUtils.isBlank(scriptPath)){
                DebugerResponse response =  buildResponse(request,true,false,MobileScriptDebugerConstant.DEBUGER_RESPONSE_SCRIPTID_NOTBLANK);
                sendMsg2Web(request.getSessionId(),JsonUtils.toJson(response));
                return ;
            }
            String[] strings = idToPath(scriptPath);
            if(strings == null){
                DebugerResponse response =  buildResponse(request,true,false,MobileScriptDebugerConstant.DEBUGER_RESPONSE_SCRIPTID_FORMAT_ERROR);
                sendMsg2Web(request.getSessionId(),JsonUtils.toJson(response));
                return ;
            }
            String content = getFileContent(strings[0],"/"+strings[1]);

            logger.debug("初始化接口调试脚本：" + content);
            request.withData(MobileScriptDebugerConstant.DEBUGER_REQUEST_DATA_SCRIPT_DATA,content);
            request.withData(MobileScriptDebugerConstant.DEBUGER_REQUEST_DATA_TYPE,MobileScriptDebugerConstant.DEBUGER_REQUEST_DATA_ALL);
            request.setCmd(DebugerConst.CommandConst.CMD_REPLAY_START);

        }catch (Exception e){
            logger.warn("接口调试失败",e);
        }
    }

    private String getFileContent(String tenantId, String fileId) {
        String msg = null;
        try {
            StringBuilder builder = new StringBuilder();
            InputStream is = storageService.downloadAsStream(tenantId, fileId);
            String line;
            BufferedReader read = new BufferedReader(new InputStreamReader(is));
            while((line = read.readLine())!=null){
                builder.append(line);
            }
            msg = builder.toString();
        } catch (Exception e) {
            throw new RuntimeException("文件下载失败租户id:" + tenantId, e);
        }
        return msg;
    }

    /**
     * 第一个是租户id,第二个是文件id，第三个是文件名
     * @param fileId
     * @return
     */
    private String[] idToPath(String fileId) {
        String[] result = new String[3];
        if (fileId.startsWith("/") || fileId.startsWith("_")) {
            fileId = fileId.substring(1);
        }
        if (fileId.endsWith("/") || fileId.endsWith("_")) {
            fileId = fileId.substring(0, fileId.length() - 1);
        }
        int index = fileId.indexOf("_");
        int lastIndex = fileId.lastIndexOf("_");
        if (index <= 0 || lastIndex <= 0 || index >= lastIndex) {
            logger.debug("传入的id为：{}",fileId);
            return null;
        }
        result[0] = fileId.substring(0, index);  //租户id
        result[1] = fileId.substring(index + 1).replaceAll("_","/");  //文件id
        result[2] = fileId.substring(lastIndex + 1);  //文件名
        return result;
    }

}
