package cn.iocoder.yudao.framework.websocket.core.handler;

import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.TypeUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
import cn.iocoder.yudao.framework.websocket.core.attendance.AttendanceConstants;
import cn.iocoder.yudao.framework.websocket.core.listener.WebSocketMessageListener;
import cn.iocoder.yudao.framework.websocket.core.message.JsonWebSocketMessage;
import cn.iocoder.yudao.framework.websocket.core.session.WebSocketSessionManager;
import cn.iocoder.yudao.framework.websocket.core.util.WebSocketFrameworkUtils;
import com.github.yulichang.toolkit.SpringContentUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.lang.reflect.Type;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;


/**
 * JSON 格式 {@link WebSocketHandler} 实现类
 *
 * 基于 {@link JsonWebSocketMessage#getType()} 消息类型，调度到对应的 {@link WebSocketMessageListener} 监听器。
 *

 */
@Slf4j
public class JsonWebSocketMessageHandler extends TextWebSocketHandler {

    /**
     * type 与 WebSocketMessageListener 的映射
     */
    private final Map<String, WebSocketMessageListener<Object>> listeners = new HashMap<>();

    @SuppressWarnings({"rawtypes", "unchecked"})
    public JsonWebSocketMessageHandler(List<? extends WebSocketMessageListener> listenersList) {
        listenersList.forEach((Consumer<WebSocketMessageListener>)
                listener -> listeners.put(listener.getType(), listener));
    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {

        // 1.1 空消息，跳过
        if (message.getPayloadLength() == 0) {
            return;
        }

        String payload = message.getPayload() ;
        boolean isTypeJSON = JSONUtil.isTypeJSON(payload) ;
        if( isTypeJSON ) {
            JSONObject object = new JSONObject(payload);
            String cmd = object.getStr("cmd") ; //设备请求方法
            String sn = object.getStr("sn") ;   //设备编号
            // 此指令服务端不需要回复  客户端声明
            if (  cmd !=null && cmd.equals("declare") ) {
                log.info("[客户端声明]"+ "||"+ session.getId()+ "||"+ message.getPayload(), session.getId(), message.getPayload());
                //此处需要将sessionId 与sn 绑定
                WebSocketSessionManager webSocketSessionManager = SpringContentUtils.getBean(WebSocketSessionManager.class);
                webSocketSessionManager.addSession(session,sn);
                return;
            }

            // 1.2 ping 心跳消息，直接返回 pong 消息。
            if (  cmd != null && cmd.equals("ping") ) {
                log.info("[设备心跳]"+ "||"+ session.getId()+ "||"+ message.getPayload(), session.getId(), message.getPayload());
                session.sendMessage(new TextMessage("{\"cmd\": \"pong\"}"));
                // 更新设备心跳请求时间
                // 传入设备sn编码，更新对应sn的本次请求时间至redis。
                StringRedisTemplate redisTemplate = SpringContentUtils.getBean(StringRedisTemplate.class);
                redisTemplate.opsForValue().set(sn + "_1", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
                redisTemplate.expire(sn + "_1", 30, TimeUnit.SECONDS);
                return;
            }
        } else {
            // 1.2 ping 心跳消息，直接返回 pong 消息。
            if (message.getPayloadLength() == 4 && Objects.equals(message.getPayload(), "ping")) {
//                log.info("[WEB心跳]"+ "||"+ session.getId()+ "||"+ message.getPayload(), session.getId(), message.getPayload());
                session.sendMessage(new TextMessage("pong"));
                return;
            }
        }

        try {
            JsonWebSocketMessage jsonMessage = new JsonWebSocketMessage();

            //此处 因为web端和设备返回的信息格式不一致
            //所以先判断 信息来源
            JSONObject object = new JSONObject(message.getPayload());
            String cmd = object.getStr("cmd");
            if (StrUtil.isEmpty( cmd )) {

                cmd = new JSONObject(object.get("content")).getStr("cmd");
            }

            if( cmd.equals(AttendanceConstants.CMD_TO_CLIENT)  ) {

                jsonMessage.setType("attendance-message-send") ;
                jsonMessage.setContent(object.toString());
            }else if (cmd.equals(AttendanceConstants.CMD_TO_DEVICE)){
                jsonMessage = JsonUtils.parseObject(message.getPayload(), JsonWebSocketMessage.class);
            }

            // 2.2 获得对应的 WebSocketMessageListener  根据Type找到对应的处理监听器
            WebSocketMessageListener<Object> messageListener = listeners.get(jsonMessage.getType());
            if (messageListener == null) {
                log.error("[handleTextMessage][session({}) message({}) 监听器为空]", session.getId(), message.getPayload());
                return;
            }
            // 2.3 处理消息
            Type type = TypeUtil.getTypeArgument(messageListener.getClass(), 0);
            Object messageObj;
            if( cmd.equals(AttendanceConstants.CMD_TO_CLIENT)  ) {

                object.set("data", object.toString());
                messageObj = JsonUtils.parseObject(object.toString(), type);
            }else if (cmd.equals(AttendanceConstants.CMD_TO_DEVICE)){

                Object to = object.get("to");
                Object data = object.get("content");
                object.set("data",data.toString());
                object.set("to", to.toString());
                String string = object.toString();
                messageObj = JsonUtils.parseObject(string, type);
            } else {
                messageObj = null;
            }

            Long tenantId = WebSocketFrameworkUtils.getTenantId(session);
            TenantUtils.execute(tenantId, () -> messageListener.onMessage(session, messageObj));
        } catch (Exception ex) {
            log.error("[handleTextMessage][session({}) message({}) 处理异常]", session.getId(), message.getPayload());
        }
    }

}
