#include "mqtt_service.h"

#include "mqtt_config_service.h"

#include <nlohmann/json.hpp>

#include <chrono>
#include <iostream>
#include <utility>

namespace mqtt {

Service::Service(std::string device_id)
    : device_id_(std::move(device_id)), topics_(make_topics(device_id_)) {}

Service::~Service(){
    stop();
}

bool Service::start(){
    stop();
    const RemoteConfig config = ConfigService().fetch(device_id_);
    client_ = std::make_unique<Client>(topics_, router_);
    if(!client_->start(config)){
        client_.reset();
        return false;
    }

    {
        std::lock_guard<std::mutex> lock(state_mutex_);
        running_ = true;
        initial_retry_rounds_ = 0;
    }
    heartbeat_thread_ = std::thread(&Service::heartbeat_loop, this);
    return true;
}

void Service::stop(){
    {
        std::lock_guard<std::mutex> lock(state_mutex_);
        running_ = false;
    }
    wakeup_.notify_all();
    if(heartbeat_thread_.joinable()) heartbeat_thread_.join();
    if(client_){
        client_->stop();
        client_.reset();
    }
}

MessageRouter &Service::router(){
    return router_;
}

int Service::publish_device_message(const std::string &payload, int qos){
    if(!client_) return 0;
    int sent = client_->publish_all(topics_.device_to_app, payload, qos);
    sent += client_->publish_all(topics_.device, payload, qos);
    return sent;
}

void Service::heartbeat_loop(){
    bool first = true;
    std::unique_lock<std::mutex> lock(state_mutex_);
    while(running_){
        const auto delay = first ? std::chrono::seconds(5)
                                 : std::chrono::seconds(30);
        first = false;
        if(wakeup_.wait_for(lock, delay, [&]{ return !running_; })) break;
        lock.unlock();

        if(client_ && initial_retry_rounds_ < 3){
            const int recovered = client_->retry_failed();
            ++initial_retry_rounds_;
            if(recovered > 0)
                std::cerr << "[mqtt] recovered initial broker clients="
                          << recovered << "\n";
            if(initial_retry_rounds_ == 3 && recovered == 0)
                std::cerr << "[mqtt] initial broker retry limit reached\n";
        }

        nlohmann::json heartbeat = {
            {"head", {{"message_type", 1}}},
            {"body", {
                {"ID", topics_.app_to_device},
                {"version", "1.0.1"}
            }}
        };
        const int sent = publish_device_message(heartbeat.dump(), 0);
        std::cerr << "[mqtt] heartbeat publish operations=" << sent << "\n";
        lock.lock();
    }
}

} // namespace mqtt
