package com.njzscloud.common.mqtt.support;

import cn.hutool.core.util.StrUtil;
import com.njzscloud.common.core.jackson.Jackson;
import com.njzscloud.common.mqtt.config.MqttProperties;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.*;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;

@Slf4j
public class Mqtt implements BeanPostProcessor {
    private static final Map<String, Consumer<MqttMsg>> fn = new ConcurrentHashMap<>();
    private MqttClient client;

    public Mqtt(MqttProperties mqttProperties) {
        String broker = mqttProperties.getBroker();
        String clientId = mqttProperties.getClientId();
        String username = mqttProperties.getUsername();
        String password = mqttProperties.getPassword();
        try {
            client = new MqttClient(broker, clientId);
            MqttConnectOptions options = new MqttConnectOptions();
            if (StrUtil.isNotBlank(username) && StrUtil.isNotBlank(password)) {
                options.setUserName(username);
                options.setPassword(password.toCharArray());
            }

            client.connect(options);
            client.setCallback(new MsgHandler());
        } catch (Exception e) {
            client = null;
            log.error("mqtt连接失败", e);
        }
    }

    public void shutdown() {
        try {
            if (client != null) {
                client.disconnect();
            }
        } catch (Exception e) {
            log.error("mqtt关闭失败", e);
        }
    }

    public void subscribe(String topic, int qos) {
        try {
            client.subscribe(topic, qos);
        } catch (Exception e) {
            log.error("mqtt 订阅失败：{} {}", topic, qos, e);
        }
    }

    public void subscribe(String topic) {
        this.subscribe(topic, 0);
    }

    public void subscribe(String topic, int qos, Consumer<MqttMsg> handler) {
        fn.put(topic, handler);
        this.subscribe(topic, qos);
    }

    public void subscribe(String topic, Consumer<MqttMsg> handler) {
        this.subscribe(topic, 0, handler);
    }

    public void publish(String topic, int qos, Object msg) {
        String jsonStr = null;
        try {
            jsonStr = Jackson.toJsonStr(msg);
            MqttMessage message = new MqttMessage(jsonStr.getBytes());
            message.setQos(qos);
            client.publish(topic, message);
        } catch (Exception e) {
            log.error("mqtt 发布消息失败：{} {} {}", topic, qos, jsonStr, e);
        }
    }

    public void publish(String topic, Object msg) {
        this.publish(topic, 0, msg);
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        Class<?> clazz = bean.getClass();
        for (Method method : clazz.getDeclaredMethods()) {
            MqttHandler mqttHandler = method.getAnnotation(MqttHandler.class);
            if (mqttHandler == null) continue;
            String topic = mqttHandler.topic();
            int qos = mqttHandler.qos();
            this.subscribe(topic, qos);
            Consumer<MqttMsg> handler = (msg) -> {
                try {
                    method.invoke(bean, msg);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            };
            fn.put(topic, handler);
        }
        return bean;
    }

    public static class MsgHandler implements MqttCallback {
        public void messageArrived(String topic, MqttMessage message) throws Exception {
            Consumer<MqttMsg> handler = fn.get(topic);
            if (handler == null) return;
            try {
                handler.accept(new MqttMsg(message.getPayload()));
            } catch (Exception e) {
                log.error("mqtt 消息处理失败：{} {}", topic, new String(message.getPayload()), e);
            }
        }

        public void connectionLost(Throwable cause) {
            log.error("mqtt 连接已断开", cause);
        }

        public void deliveryComplete(IMqttDeliveryToken token) {
            log.info("消息投递结果：{}", token.isComplete());
        }
    }
}
