package net.northking.cctp.designer.ws.client;


import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.nacos.common.utils.StringUtils;
import com.alibaba.nacos.common.utils.ThreadUtils;
import net.northking.cctp.common.util.UUIDUtil;
import net.northking.cctp.designer.ws.InstructSetMsg;
import net.northking.cctp.designer.ws.RobotEngineConnetStatusBo;
import net.northking.cctp.designer.ws.WsManyWaysBo;
import net.northking.cctp.designer.ws.WsUtils;
import net.northking.cctp.designer.ws.constants.PcuiDesignerConstant;
import net.northking.cctp.designer.ws.server.InstructSetTransetionEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.websocket.*;
import java.io.IOException;
import java.net.URI;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

@ClientEndpoint
public class RobotWsClient {

    private String sessionId;
    private static final Logger logger = LoggerFactory.getLogger(RobotWsClient.class);

    private Session session;

    private  URI uri;

    private List<Map<String, Object>> filterList;

    private boolean closeFlag = false;

    public String getSessionId() {
        return sessionId;
    }

    public void setSessionId(String sessionId) {
        this.sessionId = sessionId;
    }

    //计算重新连接次数
    private AtomicInteger atomicInteger = new AtomicInteger(0);

    public RobotWsClient(URI uri,String sessionId,List<Map<String, Object>> filterList) {
        this.filterList = filterList;
        this.uri = uri;
        try {
            this.session = ContainerProvider.getWebSocketContainer().connectToServer(this, uri);
            //设置传输数据的大小
            this.session.setMaxTextMessageBufferSize(5 * 1024 *1024);
            this.sessionId = sessionId;
            //发送心跳
            this.sendHeart();
        } catch (Exception e) {
            logger.error("第一次连接机器人ws异常:",e);
            //第一次连接不上直接结束不在重新连接

        }
    }
    private boolean reconnect(){
        //通知前端重新连接机器人中
        WsManyWaysBo wsManyWaysBo = WsUtils.sessionMap.get(this.sessionId);
        if (null != wsManyWaysBo){
            RobotEngineConnetStatusBo statusBo = WsUtils.connectStatus.get(wsManyWaysBo.getSessionId());
            if (null != statusBo){
                //判断执行引擎是否已经发送过重新连接中
                int robotStatus = statusBo.getRobotStatus();
                int engineStatus = statusBo.getEngineStatus();
                statusBo.setRobotStatus(PcuiDesignerConstant.ROB_STA_CONNECTING);//置为连接中
                if (robotStatus == PcuiDesignerConstant.ROB_STA_CONNECTED && engineStatus == PcuiDesignerConstant.ENG_STA_CONNECTED){
                    WsUtils.sendMsgFrontConnetStatus(wsManyWaysBo,InstructSetMsg.connecting,InstructSetMsg.robot);
                }
            }
        }

        while (!this.isNormal()){
            //当为正常关闭时直接返回false无需重连了.
            if (this.closeFlag){
                return false;
            }
            if (atomicInteger.get() >= 3){
                //如果三次还连接不上将关闭标识置为正常关闭
                this.closeFlag = true;
                //关闭机器人和引擎的ws
                WsUtils.closeRobotAndEngineWs(this.sessionId,"重连失败关闭");
                //连接失败通知前端
                if (null != wsManyWaysBo){
                    //需要调用资源释放
                    wsManyWaysBo.getFrontDatasWebsocket().release();
                    RobotEngineConnetStatusBo statusBo = WsUtils.connectStatus.get(wsManyWaysBo.getSessionId());
                    if (null != statusBo){
                        //判断执行引擎是否已经发送过重新连接中
                        int robotStatus = statusBo.getRobotStatus();
                        int engineStatus = statusBo.getEngineStatus();
                        statusBo.setRobotStatus(PcuiDesignerConstant.ROB_STA_DISCONNECTED);//置为连接失败
                        if ((robotStatus == PcuiDesignerConstant.ROB_STA_CONNECTING && engineStatus == PcuiDesignerConstant.ENG_STA_CONNECTED) || (robotStatus == PcuiDesignerConstant.ROB_STA_CONNECTING && engineStatus == PcuiDesignerConstant.ENG_STA_CONNECTING)){
                            WsUtils.sendMsgFrontConnetStatus(wsManyWaysBo,InstructSetMsg.disconnect,InstructSetMsg.robot);
                        }
                    }
                }
                return false;
            }

            String oldSessionId = this.session.getId();
            ScheduledFuture<?> scheduledFuture = WsUtils.scheduledFutures.get(oldSessionId);
            try {
                this.session = ContainerProvider.getWebSocketContainer().connectToServer(this, uri);
            } catch (Exception e) {
                logger.error("重试连接机器人ws异常:", e);
                //连接失败增加次数增加重连间隔3秒
                atomicInteger.incrementAndGet();
                //每隔三秒
                ThreadUtils.sleep(3000);
                continue;
            }
            //设置传输数据的大小
            this.session.setMaxTextMessageBufferSize(5 * 1024 * 1024);
            if (null != scheduledFuture) {
                //重新连接上后需要重新将心跳线程任务放入map,删除老加入新的
                WsUtils.scheduledFutures.remove(oldSessionId);
                WsUtils.scheduledFutures.put(this.session.getId(), scheduledFuture);
            }
            //连接上了重置计数器
            atomicInteger.set(0);
            //连接成功通知前端
            if (null != wsManyWaysBo){
                RobotEngineConnetStatusBo statusBo = WsUtils.connectStatus.get(wsManyWaysBo.getSessionId());
                if (null != statusBo){
                    //判断执行引擎是否已经发送过重新连接中
                    int robotStatus = statusBo.getRobotStatus();
                    statusBo.setRobotStatus(PcuiDesignerConstant.ROB_STA_CONNECTED);//置为连接成功
                    if (statusBo.getEngineStatus() == PcuiDesignerConstant.ENG_STA_CONNECTED && robotStatus == PcuiDesignerConstant.ROB_STA_CONNECTING){
                        WsUtils.sendMsgFrontConnetStatus(wsManyWaysBo,InstructSetMsg.connected,InstructSetMsg.robot);
                    }
                }
            }
        }
        return true;
    }

    private void sendHeart() {
        ScheduledFuture<?> scheduledFuture = WsUtils.scheduledExecutorService.scheduleAtFixedRate(()->{
            if (this.isNormal()){
                //每次发送心跳前检查map中是否还包含该对象
                WsManyWaysBo wsManyWaysBo = WsUtils.sessionMap.get(this.sessionId);
                if (null != wsManyWaysBo){
                    JSONObject msg = new JSONObject();
                    msg.put(InstructSetMsg.cmd, PcuiDesignerConstant.VALUE_MSG_PING);
                    msg.put(InstructSetMsg.seqId, UUIDUtil.create32UUID());
                    this.session.getAsyncRemote().sendText(msg.toJSONString());
                }else{
                    WsUtils.closeRobotAndEngineWs(this.sessionId,PcuiDesignerConstant.RP_ENG_DISCONNECTED + this.sessionId);
                    ScheduledFuture<?> scheduledFutureMap = WsUtils.scheduledFutures.get(this.session.getId());
                    if (null != scheduledFutureMap){
                        WsUtils.scheduledFutures.remove(this.session.getId());
                        scheduledFutureMap.cancel(true);
                        logger.info("机器人心跳任务结束:{}",this.sessionId);
                    }
                }
            }else{
                if (this.closeFlag || !this.reconnect()){
                    ScheduledFuture<?> scheduledFutureMap = WsUtils.scheduledFutures.get(this.session.getId());
                    if (null != scheduledFutureMap){
                        WsUtils.scheduledFutures.remove(this.session.getId());
                        scheduledFutureMap.cancel(true);
                        logger.info("机器人心跳任务结束:{}",this.sessionId);
                    }
                }else if (this.session.isOpen()){
                    //说明重新连接成功发送心跳
                    JSONObject msg = new JSONObject();
                    msg.put(InstructSetMsg.cmd, PcuiDesignerConstant.VALUE_MSG_PING);
                    msg.put(InstructSetMsg.seqId, UUIDUtil.create32UUID());
                    this.session.getAsyncRemote().sendText(msg.toJSONString());
                }
            }
        },PcuiDesignerConstant.INITIAL_DELAY,PcuiDesignerConstant.PERIOD,TimeUnit.SECONDS);
        //将定时器心跳任务存入map
        WsUtils.scheduledFutures.put(this.session.getId(),scheduledFuture);
    }

    public void sendMessage(String msg) {
        if (this.session.isOpen()){
            JSONObject jsonObject = JSONObject.parseObject(msg);
            if (InstructSetMsg.detect_input.equals(jsonObject.getString(InstructSetMsg.cmd))){
                //如果为输入控件按如下处理
                this.detectIputTransetion(jsonObject);
            }else{
                jsonObject = this.instructTransetion(jsonObject);
                if (null != jsonObject){
                    this.session.getAsyncRemote().sendText(jsonObject.toJSONString());
                    logger.info("推送给机器人的指令:{}",jsonObject.toJSONString());
                }
            }

        }
    }

    private void detectIputTransetion(JSONObject jsonObject) {
        JSONObject data = jsonObject.getJSONObject(InstructSetMsg.data);
        String seqId = data.getString(InstructSetMsg.requestId);

        String value = data.getString(InstructSetMsg.value);
        JSONArray targets = data.getJSONArray(InstructSetMsg.targets);
        JSONObject json = new JSONObject();
        json.put(InstructSetMsg.cmd, InstructSetMsg.setTextEx);
        json.put(InstructSetMsg.targets, targets);
        json.put(InstructSetMsg.seqId,seqId);
        json.put(InstructSetMsg.setValue, value);
        json.put(InstructSetMsg.waitForReady, 0);
        json.put(InstructSetMsg.timeOut, 30);
        json.put(InstructSetMsg.delayBefore, 3.5);
        json.put(InstructSetMsg.delayAfter, 3.5);
        json.put(InstructSetMsg.continueOnError, false);
        this.session.getAsyncRemote().sendText(json.toJSONString());
        logger.info("推送给机器人的指令:{}",json.toJSONString());
    }




    private JSONObject instructTransetion(JSONObject jsonObject) {
        String string = jsonObject.getString(InstructSetMsg.cmd);
        String code = InstructSetTransetionEnum.getCode(string);
        JSONObject data = jsonObject.getJSONObject(InstructSetMsg.data);
        JSONObject json = new JSONObject();
        json.put(InstructSetMsg.cmd,code);
        json.put(InstructSetMsg.seqId,data.getString(InstructSetMsg.requestId));
        //开始录制
        if (InstructSetTransetionEnum.START.getCode().equals(code)){
            String type = data.getString(InstructSetMsg.type);
            json.put(InstructSetMsg.highlight,data.getBooleanValue(InstructSetMsg.highlight, true));
            //添加额外一个参数
            json.put("FilterList",filterList);
            if (InstructSetMsg.element.equals(type)){
                json.put(InstructSetMsg.detectType,1);
            }else if (InstructSetMsg.point.equals(type)){
                json.put(InstructSetMsg.detectType,5);
            }else if(InstructSetMsg.image.equals(type)){
                json.put(InstructSetMsg.detectType,2);
            }
            return json;
            //初始化
        }else if(InstructSetTransetionEnum.INIT_ENVT.getCode().equals(code)){
            String browserType = data.getString(InstructSetMsg.browserType);
            String url = data.getString(InstructSetMsg.url);
            json.put(InstructSetMsg.evnType,data.getIntValue(InstructSetMsg.type));
            if (null != browserType){
                json.put(InstructSetMsg.browserType,browserType);
            }
            if (null != url){
                json.put(InstructSetMsg.url,url);
            }
            json.put(InstructSetMsg.enableRecord,false);
            json.put(InstructSetMsg.frameRate,20);
            json.put(InstructSetMsg.takeScreen,0);
            return json;
        }else if(InstructSetTransetionEnum.COMPLETE.getCode().equals(code)){
            return json;
        }else if(InstructSetTransetionEnum.STOP.getCode().equals(code) ||
                InstructSetTransetionEnum.DETECT_DELAY.getCode().equals(code) ||
                InstructSetTransetionEnum.CUTTING_SCREEN.getCode().equals(code)){
            return json;
        }else if(InstructSetTransetionEnum.CLOSE_BROWSER_TABS.getCode().equals(code)){
            JSONArray tabs = data.getJSONArray(PcuiDesignerConstant.K_TABS);
            json.put(PcuiDesignerConstant.K_TABS, tabs);
            return json;
        } else if(InstructSetTransetionEnum.DETECT_ELEMENT.getCode().equals(code)){
            String type = data.getString(InstructSetMsg.type);
            String component = data.getString(InstructSetMsg.component);
            if (InstructSetMsg.selector.equals(type)){
                json.put(InstructSetMsg.cmd,InstructSetMsg.detectByElement);
            }else if (InstructSetMsg.point.equals(type)){
                json.put(InstructSetMsg.cmd,InstructSetMsg.detectByPoint);
            }else if (InstructSetMsg.image.equals(type)){
                json.put(InstructSetMsg.cmd,InstructSetMsg.detectByImage);
            }else if (InstructSetMsg.ocr.equals(type)){
                json.put(InstructSetMsg.cmd,InstructSetMsg.detectByOcr);
            }else if (InstructSetMsg.hzocr.equals(type)){
                json.put(InstructSetMsg.cmd,InstructSetMsg.detectByOcr);
            }
            if (null != component){
                json.put(InstructSetMsg.methodName,component);
            }
            return json;
        }else if(InstructSetTransetionEnum.DETECT_MODE.getCode().equals(code)){
            String fetchTypeFront = data.getString(InstructSetMsg.fetchTypeFront);
            json.put(InstructSetMsg.fetchType,fetchTypeFront);
            return json;
        }else{
            return null;
        }
    }

    @OnMessage
    public void onMessage(String message) {
        if (StringUtils.isNotBlank(message)){
            JSONObject jsonObject = JSONObject.parseObject(message);
            if (!"Pong".equals(jsonObject.getString(InstructSetMsg.cmd))){
                //收到服务端的消息,发送给前端
                logger.info("机器人返回指令:{}",message);
                //将消息标注为机器人发送的
                jsonObject.put(InstructSetMsg.robot_engine,InstructSetMsg.robot);
                WsUtils.sendMsgFront(this,jsonObject.toJSONString());
            }
        }
    }

    @OnClose
    public void onClose(CloseReason cr) {
        if (null != cr && cr.getReasonPhrase() != null && cr.getReasonPhrase().contains(InstructSetMsg.sit)){
            this.closeFlag = true;
            //说明是正常关闭
            logger.info("机器人通道正常关闭:{},{}",this.sessionId,cr.getReasonPhrase());
            //已正常关闭或者重新连接不上需要断开前端ws和执行机的ws
            WsUtils.closeRobotAndEngineWs(this.sessionId,"不重连关闭");
        }else if(!this.closeFlag && (null == cr || cr.getReasonPhrase() == null || !cr.getReasonPhrase().contains(InstructSetMsg.sit))){
            if (null != cr){
                logger.info("机器人通道异常关闭:{},:{}",cr.getCloseCode(),cr.getReasonPhrase());
            }
            this.reconnect();

        }
    }

    @OnError
    public void onError(Throwable xb) {
        logger.error("机器人ws通道异常:",xb);
    }

    public void optClose(String msg){
        if (this.session.isOpen()){
            try {
                //正常关闭
                this.session.close(new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE,msg + InstructSetMsg.sit));
            } catch (IOException e) {
                logger.error("关闭机器人ws异常:",e);
            }
        }
    }

    public boolean isNormal(){
        return this.session != null && this.session.isOpen();
    }
}
