#include "mqtt_cli.h"

#include <utility>

#include "app/app.h"
#include "common/mitt/mitt.h"
#include "common/utils/str_util.h"

namespace zsy
{
    MQTTCli::MQTTCli(const MQTTConfig_Cli &config)
        : running(true),
          config(config)
    {
        reportInitializing();
        try
        {
            LOGGER_INFO("MQTT 客户端：{} 正在连接到服务器：{}:{}", config.name(), config.ip(), config.port());
            ioc = std::make_shared<boost::asio::io_context>();
            cli = MQTT_NS::make_sync_client(*ioc, config.ip(), config.port());
            cli->set_keep_alive_sec(30);
            cli->set_pingresp_timeout(std::chrono::seconds(10));
            cli->set_client_id(config.client_id());
            cli->set_user_name(config.username());
            cli->set_password(config.passwd());
            cli->set_clean_session(true);
            cli->set_connack_handler([this](bool sp, MQTT_NS::connect_return_code connack_return_code)
            {
                return onConnAck(sp, connack_return_code);
            });
            cli->set_close_handler([this]
            {
                onClose();
            });
            cli->set_error_handler([this](MQTT_NS::error_code ec)
            {
                onError(ec);
            });
            cli->set_publish_handler([this](MQTT_NS::optional<packet_id_t> packet_id, MQTT_NS::publish_options pubopts, MQTT_NS::buffer topic_, MQTT_NS::buffer payload_)
            {
                return onMessage(packet_id, pubopts, topic_, payload_);
            });
            eventLoop = std::make_unique<std::jthread>([this] { this->run(); });
        } catch (const std::exception &e)
        {
            LOGGER_ERROR("MQTT 客户端：{} 连接失败：{}", config.name(), e.what());
        } catch (...)
        {
            LOGGER_ERROR("MQTT 客户端：{} 连接失败", config.name());
        }
        waitStarted();
    }

    void MQTTCli::reconnect()
    {
        doConnect();
    }

    void MQTTCli::doConnect()
    {
        do
        {
            LOGGER_INFO("MQTT 客户端：{} 开始连接", config.name());
            mqtt::error_code ec;
            cli->connect(ec);
            if (!ec)
            {
                LOGGER_INFO("MQTT 客户端：{} 连接成功", config.name());
                state = BEAN_Available;
                break;
            }
            LOGGER_ERROR("MQTT 客户端：{} 连接失败：{}， 3 秒后重新连接", config.name(), StrUtil::str_w_a(StrUtil::str_a_w(ec.message(), "GBK")));
            std::this_thread::sleep_for(std::chrono::seconds(3));
        } while (running);
    }

    void MQTTCli::run()
    {
        try
        {
            doConnect();
            ioc->run();
            LOGGER_INFO("MQTT 客户端：{} 已退出", config.name());
        } catch (std::exception &e)
        {
            LOGGER_ERROR("MQTT 客户端：{}，连接失败：{}", config.name(), e.what());
        } catch (...)
        {
            LOGGER_ERROR("MQTT 客户端：{}，连接失败，未知异常", config.name());
        }
    }

    bool MQTTCli::onConnAck(bool, const mqtt::connect_return_code &connack_return_code)
    {
        try
        {
            if (connack_return_code == MQTT_NS::connect_return_code::accepted)
            {
                if (!eventPool.empty())
                {
                    for (const auto topic: eventPool | std::views::keys)
                    {
                        cli->subscribe(topic, MQTT_NS::qos::at_most_once);
                    }
                }
                reportAvailable();
                LOGGER_INFO("MQTT 客户端：{}，连接成功", config.name());
            } else
            {
                LOGGER_ERROR("MQTT 客户端：{}，连接失败，连接响应码：{}", config.name(), connect_return_code_to_str(connack_return_code));
            }
        } catch (...)
        {
            LOGGER_ERROR("MQTT 客户端：{}，未知异常", config.name());
        }
        return true;
    }

    void MQTTCli::onClose()
    {
        if (!running)return;
        LOGGER_INFO("MQTT 客户端：{} 连接已断开，准备重连", config.name());
        reconnect();
    }

    void MQTTCli::onError(const mqtt::error_code &ec)
    {
        if (!running)return;
        LOGGER_ERROR("MQTT 客户端：{}，出现错误：{}，准备重连", config.name(), StrUtil::str_w_a(StrUtil::str_a_w(ec.message(), "GBK")));
        reconnect();
    }

    bool MQTTCli::onMessage(const mqtt::optional<packet_id_t> &, const mqtt::publish_options &, const mqtt::buffer &topic_, const mqtt::buffer &payload_)
    {
        const auto topic = std::string(topic_.begin(), topic_.end());
        const auto payload = std::string(payload_.begin(), payload_.end());
        // LOGGER_INFO("MQTT 客户端：{}，收到消息：【{} {}】", config.client_id(), topic, payload);
        const auto it = eventPool.find(topic);
        if (it == eventPool.end())
        {
            LOGGER_INFO("MQTT 客户端：{}，未订阅主题：【{}】，消息：{}", config.client_id(), topic, payload);
            return true;
        }
        it->second->$emit({
            config.client_id(),
            topic,
            payload,
        });

        return true;
    }

    void MQTTCli::subscribe(std::string topic, const std::function<void(const MQTTMsg &)> &handler, uint8_t qos)
    {
        if (!running)return;
        cli->subscribe(topic, static_cast<MQTT_NS::qos>(qos));
        eventPool[topic] = Mitt::MittEvent<const MQTTMsg &>::make_event();
        eventPool[topic]->$on(handler);
        LOGGER_INFO("MQTT：{} 已订阅：【{} {}】", config.client_id(), topic, qos);
    }

    void MQTTCli::publish(std::string topic, std::string payload, uint8_t qos)
    {
        if (!running)return;
        cli->publish(topic, payload, static_cast<MQTT_NS::qos>(qos));
    }

    std::string MQTTCli::name() const noexcept
    {
        return config.name();
    }

    std::string MQTTCli::sn() const noexcept
    {
        return config.sn();
    }

    void MQTTCli::init()
    {
        std::unique_lock lock(MQTT_CLIS_MTX);
        if (!App::APP_CONFIG->has_mqtt_config())return;
        if (App::APP_CONFIG->mqtt_config().clis().empty())return;
        for (const auto &cli: App::APP_CONFIG->mqtt_config().clis())
        {
            MQTT_CLIS.emplace(cli.sn(), std::make_shared < MQTTCli > (cli));
        }
    }

    void MQTTCli::destroy(const std::string &sn)
    {
        std::unique_lock lock(MQTT_CLIS_MTX);
        if (MQTT_CLIS.empty())return;
        if (sn.empty())
        {
            MQTT_CLIS.clear();
        } else
        {
            MQTT_CLIS.unsafe_erase(sn);
        }
    }

    std::shared_ptr<MQTTCli> MQTTCli::get(const std::string &sn)
    {
        std::shared_lock lock(MQTT_CLIS_MTX);
        if (!MQTT_CLIS.contains(sn))return nullptr;
        return MQTT_CLIS.at(sn);
    }

    std::shared_ptr<MQTTCli> MQTTCli::getRemoteCli()
    {
        return get("0");
    }

    std::shared_ptr<MQTTCli> MQTTCli::getLocalCli()
    {
        return get("1");
    }

    MQTTCli::~MQTTCli()
    {
        running = false;
        reportTerminated();
        LOGGER_INFO("准备停止 MQTT 客户端：{}", config.name());
        cli->disconnect();
        ioc->stop();
        eventLoop->join();
        LOGGER_INFO("已停止 MQTT 客户端：{}", config.name());
    }
}