package cn.reghao.autodop.common.mqtt;

import cn.reghao.autodop.common.msg.Message;
import cn.reghao.autodop.common.msg.rpc.RpcMsg;
import cn.reghao.jdkutil.machine.MachineId;
import cn.reghao.jdkutil.serializer.JsonConverter;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.*;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * @author reghao
 * @date 2021-05-21 08:27:41
 */
@Slf4j
@Component
public class DefaultMqttClient implements AutoCloseable {
    private final MosquittoProperties properties;
    private final MqttClient client;
    // 记录 RPC 调用, size 为 0 时表示所有 pub 消息的结果都已 sub
    private final Map<String, RpcMsg> rpcRecorder = new HashMap<>();
    private final Map<String, IMqttMessageListener> subMap = new HashMap<>();
    private final MqttCallback callback = new MqttClientCallback();

    public DefaultMqttClient(MosquittoProperties properties) throws MqttException, IOException {
        this.properties = properties;
        String clientId = properties.getClientId() + MachineId.id();
        this.client = new MqttClient(properties.getBroker(), clientId, new MemoryPersistence());
    }

    private MqttConnectOptions connectOptions() {
        MqttConnectOptions options = new MqttConnectOptions();
        options.setUserName(properties.getUsername());
        options.setPassword(properties.getPassword().toCharArray());
        // 在服务端保存状态
        options.setCleanSession(false);
        // 自动重连
        options.setAutomaticReconnect(true);
        return options;
    }

    @Override
    public void close() throws MqttException {
        if (client.isConnected()) {
            client.disconnect();
        }
        client.close();
    }

    public void putRecord(String msgId, RpcMsg rpcMsg) {
        rpcRecorder.put(msgId, rpcMsg);
    }

    public void removeRecord(String msgId) {
        rpcRecorder.remove(msgId);
    }

    public boolean isConnected() {
        return client.isConnected();
    }

    // TODO 处理系统启动时没有成功连接的情况
    private void connect() throws MqttException {
        if (!client.isConnected()) {
            client.setCallback(callback);
            client.connect(connectOptions());
        }
    }

    /**
     * 确保能订阅到离线消息的设置：
     * - qos=1
     * - clientId 确定且唯一，不能使用随机的 UUID
     * - CleanSession=false
     *
     * @param
     * @return
     * @date 2021-06-16 下午8:39
     */
    public void sub(String topic, MqttCallback callback) throws MqttException {
        connect();
        client.setCallback(callback);
        client.subscribe(topic, 1);
    }

    private void resub(String topic, IMqttMessageListener messageListener) throws MqttException {
        client.subscribe(topic, 1, messageListener);
        log.info("重新订阅 {} 成功", topic);
    }

    /**
     * qos=0 的消息不能离线
     *
     * @param
     * @return
     * @date 2021-06-16 下午8:40
     */
    public void pub(String topic, int qos, String payload) throws MqttException {
        MqttMessage message = new MqttMessage();
        message.setQos(qos);
        message.setPayload(payload.getBytes());

        connect();
        client.publish(topic, message);
    }

    /**
     * pub 消息后，等 sub 端处理后在 pub 结果
     *
     * @param
     * @return
     * @date 2021-06-30 上午11:22
     */
    public void pubWithResult(String topic, int qos, Message message) throws MqttException {
        MqttMessage mqttMessage = new MqttMessage();
        mqttMessage.setQos(qos);
        mqttMessage.setPayload(JsonConverter.objectToJson(message).getBytes());

        connect();
        client.publish(topic, mqttMessage);
        rpcRecorder.put(message.getRpcMsg().getMsgId(), message.getRpcMsg());
    }

    class MqttClientCallback implements MqttCallbackExtended {
        @Override
        public void connectComplete(boolean reconnect, String serverUri) {
            // 重连完成后自动订阅 topic
            subMap.forEach((topic, listener) -> {
                try {
                    resub(topic, listener);
                } catch (MqttException e) {
                    log.error("重新订阅 {} 失败，原因: {}", topic, e.getMessage());
                }
            });
        }

        @Override
        public void connectionLost(Throwable cause) {
            log.error("MQTT connection lost {}", cause.getMessage());
        }

        @Override
        public void messageArrived(String topic, MqttMessage message) throws Exception {
        }

        @Override
        public void deliveryComplete(IMqttDeliveryToken token) {
        }
    }
}
