package com.jsowell.netty.server.mqtt;


import com.alibaba.fastjson2.JSONObject;
import com.jsowell.common.util.StringUtils;
import com.jsowell.common.util.bean.BeanUtils;
import com.jsowell.netty.domain.MqttRequest;
import com.jsowell.netty.service.camera.CameraBusinessService;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.handler.codec.mqtt.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.concurrent.ConcurrentHashMap;

/**
 * MQTT服务端I/O数据读写处理类
 *
 * @author Lemon
 * @Date 2023/12/14 11:33:03
 */

@ChannelHandler.Sharable
@Slf4j
@Component
public class BootNettyMqttChannelInboundHandler extends ChannelInboundHandlerAdapter {

    /**
     * Key: channelId
     * Value: ctx
     */
    private static final ConcurrentHashMap<String, ChannelHandlerContext> CHANNEL_MAP = new ConcurrentHashMap<>();

    /**
     * Key: channelId
     * Value: 客户端id
     */
    private static final ConcurrentHashMap<String, String> CLIENT_MAP = new ConcurrentHashMap<>();

    /**
     * 从客户端收到新的数据时，这个方法会在收到消息时被调用
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception, IOException {
        if (null != msg) {
            BootNettyMqttMsgBack msgBack = BeanUtils.getBean(BootNettyMqttMsgBack.class);
            MqttMessage mqttMessage = (MqttMessage) msg;
            log.info("MqttServer收到消息:{}", mqttMessage.toString());
            MqttFixedHeader mqttFixedHeader = mqttMessage.fixedHeader();
            Channel channel = ctx.channel();

            if (MqttMessageType.CONNECT.equals(mqttFixedHeader.messageType())) {
                //	在一个网络连接上，客户端只能发送一次CONNECT报文。服务端必须将客户端发送的第二个CONNECT报文当作协议违规处理并断开客户端的连接
                //	to do 建议connect消息单独处理，用来对客户端进行认证管理等 这里先直接返回一个CONNACK消息
                msgBack.connack(channel, mqttMessage);
                // 如果map中不包含此连接，就保存连接
                String channelId = channel.id().asShortText();
                System.out.println(channelId);
                if (CHANNEL_MAP.get(channelId) != null) {
                    log.info("客户端【{}】是连接状态，连接通道数量: {}", channelId, CHANNEL_MAP.size());
                } else {
                    //保存连接
                    CHANNEL_MAP.put(channelId, ctx);
                    // System.out.println(channel.remoteAddress()); // /192.168.2.100:39396
                    log.info("客户端【{}】, 连接Mqtt服务器, 连接通道数量: {}", channelId, CHANNEL_MAP.size());
                    CameraBusinessService cameraBusinessService = BeanUtils.getBean(CameraBusinessService.class);
                    cameraBusinessService.processConnectMsg(channel);
                }
                // 保存客户端id
                String clientIdentifier = parsePayLoadInfo(mqttMessage.payload().toString());
                if (StringUtils.isNotBlank(clientIdentifier)) {
                    CLIENT_MAP.put(channelId, clientIdentifier);
                }
            }

            switch (mqttFixedHeader.messageType()) {
                case PUBLISH:        //	客户端发布消息
                    //	PUBACK报文是对QoS 1等级的PUBLISH报文的响应
                    System.out.println("客户端发布消息");
                    msgBack.puback(channel, mqttMessage);
                    break;
                case PUBREL:        //	发布释放
                    //	PUBREL报文是对PUBREC报文的响应
                    //	to do
                    msgBack.pubcomp(channel, mqttMessage);
                    break;
                case SUBSCRIBE:        //	客户端订阅主题
                    //	客户端向服务端发送SUBSCRIBE报文用于创建一个或多个订阅，每个订阅注册客户端关心的一个或多个主题。
                    //	为了将应用消息转发给与那些订阅匹配的主题，服务端发送PUBLISH报文给客户端。
                    //	SUBSCRIBE报文也（为每个订阅）指定了最大的QoS等级，服务端根据这个发送应用消息给客户端
                    // 	to do
                    msgBack.suback(channel, mqttMessage);
                    break;
                case UNSUBSCRIBE:    //	客户端取消订阅
                    //	客户端发送UNSUBSCRIBE报文给服务端，用于取消订阅主题
                    //	to do
                    msgBack.unsuback(channel, mqttMessage);
                    break;
                case PINGREQ:        //	客户端发起心跳
                    //	客户端发送PINGREQ报文给服务端的
                    //	在没有任何其它控制报文从客户端发给服务的时，告知服务端客户端还活着
                    //	请求服务端发送 响应确认它还活着，使用网络以确认网络连接没有断开
                    msgBack.pingresp(channel, mqttMessage);
                    break;
                case DISCONNECT:    //	客户端主动断开连接
                    //	DISCONNECT报文是客户端发给服务端的最后一个控制报文， 服务端必须验证所有的保留位都被设置为0
                    //	to do
                    break;
                default:
                    break;
            }
        }
    }

    /**
     * 从客户端收到新的数据、读取完成时调用
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws IOException {
    }

    /**
     * 客户端与服务端第一次建立连接时执行 在channelActive方法之前执行
     */
    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        super.channelRegistered(ctx);
    }

    /**
     * 客户端与服务端 断连时执行 channelInactive方法之后执行
     */
    @Override
    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
        super.channelUnregistered(ctx);
    }

    /**
     * 当出现 Throwable 对象才会被调用，即当 Netty 由于 IO 错误或者处理器在处理事件时抛出的异常时
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        super.exceptionCaught(ctx, cause);
        ctx.close();
    }

    /**
     * 客户端与服务端第一次建立连接时执行
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        super.channelActive(ctx);
    }

    /**
     * 客户端与服务端 断连时执行
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception, IOException {
        // 删除连接
        removeConnect(ctx);
        super.channelInactive(ctx);
    }

    /**
     * 服务端 当读超时时 会调用这个方法
     */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception, IOException {
        // 删除连接
        removeConnect(ctx);
        super.userEventTriggered(ctx, evt);
    }


    @Override
    public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
        super.channelWritabilityChanged(ctx);
    }



    /**
     * 删除连接
     * @param ctx
     */
    public void removeConnect(ChannelHandlerContext ctx) {
        InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
        String clientIp = insocket.getAddress().getHostAddress();
        String channelId = ctx.channel().id().asShortText();
        //包含此客户端才去删除
        if (CHANNEL_MAP.get(channelId) != null) {
            //删除连接
            CHANNEL_MAP.remove(channelId);
            CLIENT_MAP.remove(channelId);
            log.info("客户端【{}】, 退出Mqtt服务器[IP:{}--->PORT:{}], 连接通道数量: {}", channelId, clientIp, insocket.getPort(), CHANNEL_MAP.size());
        }
    }

    /**
     * 解析 payload 字段，并将 clientIdentifier （客户端id）返回
     * @param payloadText
     * @return 客户端id
     */
    private String parsePayLoadInfo(String payloadText) {
        if (StringUtils.isBlank(payloadText)) {
            return null;
        }
        JSONObject payload = new JSONObject();
        // 使用逗号和等号进行分割
        String[] parts = payloadText.split("[,\\[\\]]");
        for (String part : parts) {
            String[] keyValue = part.split("=");

            if (keyValue.length == 2) {
                String key = keyValue[0].trim();
                String value = keyValue[1].trim();

                payload.put(key, value);
            }
        }
        return payload.getString("clientIdentifier");
    }

    /**
     * 通过 channelId 获取 Channel
     * @param channelId
     * @return
     */
    public Channel getChannel(String channelId) {
        if (channelId != null) {
            ChannelHandlerContext ctx = CHANNEL_MAP.get(channelId);
            return ctx.channel();
        } else {
            return null;
        }
    }

    /**
     * 服务器端向某Topic发送消息
     * @param channelId 发送的通道id
     * @param topic 主题Topic
     * @param requestMsg 要发送的Json对象
     * @return
     * @throws InterruptedException
     */
    public ChannelFuture sendMsg(String channelId, String topic, String requestMsg) throws InterruptedException {
        MqttRequest request = new MqttRequest((requestMsg.toString().getBytes()));
        MqttPublishMessage pubMessage = (MqttPublishMessage) MqttMessageFactory.newMessage(
                new MqttFixedHeader(MqttMessageType.PUBLISH,
                        request.isDup(),
                        request.getQos(),
                        request.isRetained(),
                        0),
                new MqttPublishVariableHeader(topic, 0),
                Unpooled.buffer().writeBytes(request.getPayload()));

        Channel channel = getChannel(channelId);
        // 超过高水位，则采取同步模式
        if (channel.isWritable()) {
            return channel.writeAndFlush(pubMessage);
        }
        return channel.writeAndFlush(pubMessage).sync();
    }

}
