// SPDX-License-Identifier: GPL-3.0-or-later

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright (C) 2013 - 2024, nymea GmbH
* Copyright (C) 2024 - 2025, chargebyte austria GmbH
*
* This file is part of nymea-plugins.
*
* nymea-plugins is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* nymea-plugins is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with nymea-plugins. If not, see <https://www.gnu.org/licenses/>.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include "integrationplugingaradget.h"
#include "plugininfo.h"

#include <integrations/thing.h>
#include <network/networkaccessmanager.h>
#include <network/mqtt/mqttprovider.h>
#include <mqttclient.h>

#include <QJsonDocument>


void IntegrationPluginGaradget::setupThing(ThingSetupInfo *info)
{
    Thing *thing = info->thing();

    pluginStorage()->beginGroup(thing->id().toString());
    pluginStorage()->setValue("lastWillAvailable", false);
    pluginStorage()->endGroup();

    MqttClient *client = nullptr;
    client = hardwareManager()->mqttProvider()->createInternalClient(thing->id().toString());
    m_mqttClients.insert(thing, client);

    connect(client, &MqttClient::connected, this, [this, thing](){
        subscribe(thing);
    });
    connect(client, &MqttClient::subscribeResult, info, [info](quint16 /*packetId*/, const Mqtt::SubscribeReturnCodes returnCodes){
        info->finish(returnCodes.first() == Mqtt::SubscribeReturnCodeFailure ? Thing::ThingErrorHardwareFailure : Thing::ThingErrorNoError);
    });
    connect(client, &MqttClient::publishReceived, this, &IntegrationPluginGaradget::publishReceived);
    // In case we're already connected, manually call subscribe now
    if (client->isConnected()) {
        subscribe(thing);
    }
}

void IntegrationPluginGaradget::postSetupThing(Thing *thing)
{

    if (!m_pluginTimer) {
        int updatetime = 30;
        int lwtupdatetime = 300 / updatetime;
        m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(updatetime);

        connect(m_pluginTimer, &PluginTimer::timeout, this, [=](){
            m_statuscounter[thing] += 1;
            foreach (Thing *thing, myThings()) {
                pluginStorage()->beginGroup(thing->id().toString());
                bool lastWillAvailable = pluginStorage()->value("lastWillAvailable").toBool();
                pluginStorage()->endGroup();
                if ((lastWillAvailable == false) && (m_lastActivityTimeStamps[thing].msecsTo(QDateTime::currentDateTime()) > 2000 * updatetime) && (thing->stateValue(garadgetConnectedStateTypeId).toBool() == true)) {
                    qCDebug(dcGaradget) << "disconnect device" << thing->paramValue(garadgetThingDeviceNameParamTypeId).toString();
                    thing->setStateValue(garadgetConnectedStateTypeId, false);
                }
                if ( ((lastWillAvailable == false) && (thing->stateValue(garadgetConnectedStateTypeId).toBool() == true)) || m_statuscounter[thing] > lwtupdatetime) {
                    m_mqttClients.value(thing)->publish("garadget/" + thing->paramValue(garadgetThingDeviceNameParamTypeId).toString() + "/command", "get-status");
                }
            }
            if (m_statuscounter[thing] > lwtupdatetime) {
                m_statuscounter[thing] = 1;
            }
        });

        connect(thing, &Thing::settingChanged, this, [=](const ParamTypeId &settingTypeId){
            foreach (Thing *thing, myThings()) {
                QJsonObject garadgetobj;

                if ((thing->stateValue(garadgetConnectedStateTypeId).toBool() == true) && (settingTypeId == garadgetSettingsRdtParamTypeId)){
                    garadgetobj.insert("rdt", thing->setting(garadgetSettingsRdtParamTypeId).toInt());
                    garadgetobj.insert("rlp", thing->setting(garadgetSettingsRlpParamTypeId).toInt());
                    garadgetobj.insert("rlt",thing->setting(garadgetSettingsRltParamTypeId).toInt());
                    garadgetobj.insert("mtt", thing->setting(garadgetSettingsMttParamTypeId).toInt());
                    QJsonDocument garadgetdoc(garadgetobj);
                    QByteArray garadgetdata = garadgetdoc.toJson(QJsonDocument::Compact);
                    QString jsonDoc(garadgetdata);
                    qCDebug(dcGaradget()) << "Changing Configuration" << garadgetdata;
                    m_mqttClients.value(thing)->publish("garadget/" + thing->paramValue(garadgetThingDeviceNameParamTypeId).toString() + "/set-config", garadgetdata);
                }
            }
        });

    }
}



void IntegrationPluginGaradget::thingRemoved(Thing *thing)
{
    qCDebug(dcGaradget) << "device " << thing->paramValue(garadgetThingDeviceNameParamTypeId).toString() << "Removed";
    m_mqttClients.take(thing)->deleteLater();
    if (m_pluginTimer && myThings().isEmpty()) {
        hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer);
        m_pluginTimer = nullptr;
    }
}

void IntegrationPluginGaradget::executeAction(ThingActionInfo *info)
{
    Thing *thing = info->thing();
    Action action = info->action();

    QString name = "garadget/" + thing->paramValue(garadgetThingDeviceNameParamTypeId).toString();
    MqttClient *client = m_mqttClients.value(thing);
    if (!client) {
        qCWarning(dcGaradget) << "No valid MQTT client for thing" << thing->name();
        return info->finish(Thing::ThingErrorThingNotFound);
    }
    QString act = "";
    QByteArray actarray;
    if (action.actionTypeId() == garadgetOpenActionTypeId) {
        act = "open";
    }
    if (action.actionTypeId() == garadgetCloseActionTypeId) {
        act = "close";
    }
    if (action.actionTypeId() == garadgetStopActionTypeId) {
        act = "stop";
    }
    if (act != "" ) {
        name = name + "/command";
    }
    QString conftype = "";
    int actint = 0;
    if (action.actionTypeId() == garadgetSrtActionTypeId) {
        if (action.paramValue(garadgetSrtActionSrtParamTypeId).toInt() > -1) {
            actint = action.paramValue( garadgetSrtActionSrtParamTypeId).toInt();
            conftype = "srt";
        } else {
            name = name + "/command";
            act = "get-config";
        }
    }
    if (conftype != "") {
        name = name + "/set-config";
        QJsonObject garadgetobj;
        garadgetobj.insert(conftype, actint);
        QJsonDocument garadgetdoc(garadgetobj);
        QByteArray garadgetdata = garadgetdoc.toJson(QJsonDocument::Compact);
        QString jsonDoc(garadgetdata);
        act = garadgetdata;
    }
    if (act != "" ) {
        actarray = act.toUtf8();
        qCDebug(dcGaradget) << "Publishing:" << name << act;
        quint16 packetId = client->publish(name, actarray, Mqtt::QoS1, false);
        connect(client, &MqttClient::published, info, [info, packetId](quint16 packetIdResult){
            if (packetId == packetIdResult) {
                info->finish(Thing::ThingErrorNoError);
            }
        });
    }
    info->finish(Thing::ThingErrorNoError);
    return;
}

void IntegrationPluginGaradget::subscribe(Thing *thing)
{
    MqttClient *client = m_mqttClients.value(thing);
    if (!client) {
        // Device might have been removed
        return;
    }
    client->subscribe("garadget/" + thing->paramValue(garadgetThingDeviceNameParamTypeId).toString() + "/#");
}

void IntegrationPluginGaradget::publishReceived(const QString &topic, const QByteArray &payload, bool retained)
{
    MqttClient* client = static_cast<MqttClient*>(sender());
    Thing *thing = m_mqttClients.key(client);
    if (!thing) {
        qCWarning(dcGaradget) << "Received a publish message from a client but don't have a matching thing" << retained;
        return;
    }
    if (topic.endsWith("/status")) {
        if (thing->stateValue(garadgetConnectedStateTypeId) == false) {
            qCDebug(dcGaradget) << "Setting" << thing->paramValue(garadgetThingDeviceNameParamTypeId).toString() << "to Online" ;
            thing->setStateValue(garadgetConnectedStateTypeId, true);
        }
        m_lastActivityTimeStamps[thing] = QDateTime::currentDateTime();
        QJsonParseError error;
        QJsonDocument jsonDoc = QJsonDocument::fromJson(payload, &error);
        if (error.error != QJsonParseError::NoError) {
            qCWarning(dcGaradget) << "Cannot parse JSON from Garadget device" << error.errorString();
            return;
        }
        QJsonObject jo = jsonDoc.object();
        thing->setStateValue(garadgetSignalStrengthStateTypeId, (90 + jo.value(QString("signal")).toInt()) * 2.4 );
        thing->setStateValue(garadgetSensorlevelStateTypeId, jo.value(QString("sensor")).toInt());
        thing->setStateValue(garadgetBrightlevelStateTypeId, jo.value(QString("bright")).toInt());
        if (jo.value(QString("status")).toString().contains(QString("stopped"))) {
            thing->setStateValue(garadgetStateStateTypeId, "intermediate");
            qCDebug(dcGaradget) << thing->paramValue(garadgetThingDeviceNameParamTypeId).toString() << "is" << jo.value(QString("status")).toString() ;
        } else {
            thing->setStateValue(garadgetStateStateTypeId, jo.value(QString("status")).toString());
        }
    }
    if (topic.endsWith("/config")) {
        QJsonParseError error;
        QJsonDocument jsonDoc = QJsonDocument::fromJson(payload, &error);
        if (error.error != QJsonParseError::NoError) {
            qCWarning(dcGaradget) << "Cannot parse JSON from Garadget device" << error.errorString();
            return;
        }
        QJsonObject jo = jsonDoc.object();
        thing->setStateValue(garadgetSrtStateTypeId,jo.value(QString("srt")).toInt());
        thing->setSettingValue(garadgetSettingsRltParamTypeId,jo.value(QString("rlt")).toInt());
        thing->setSettingValue(garadgetSettingsMttParamTypeId,jo.value(QString("mtt")).toInt());
        thing->setSettingValue(garadgetSettingsRdtParamTypeId,jo.value(QString("rdt")).toInt());
        thing->setSettingValue(garadgetSettingsRlpParamTypeId,jo.value(QString("rlp")).toInt());
        qCDebug(dcGaradget) << thing->paramValue(garadgetThingDeviceNameParamTypeId).toString() << "System Configuration" << "srt =" << thing->stateValue(garadgetSrtStateTypeId).toInt() << "rlt =" << thing->setting(garadgetSettingsRltParamTypeId).toInt()<< "mtt =" << thing->setting(garadgetSettingsMttParamTypeId).toInt() << "rdt ="  << thing->setting(garadgetSettingsRdtParamTypeId).toUInt() << "rlp =" << thing->setting(garadgetSettingsRlpParamTypeId).toUInt();
    }
    if (topic.endsWith("/set-config")){
        if ( (payload.contains("mqip"))  or (payload.contains("mqpt")) or (payload.contains("nme")) ) {
            thing->setStateValue(garadgetConnectedStateTypeId, false);
            qCDebug(dcGaradget) << thing->paramValue(garadgetThingDeviceNameParamTypeId).toString() << "Detected change of Broker msg - set connected to false";
        }
    }
    if (topic.endsWith("/LWT")){
        if (payload.contains("Online") or payload.contains("Offline")) {
            qCDebug(dcGaradget()) << "Setting support for LWT";
            pluginStorage()->beginGroup(thing->id().toString());
            pluginStorage()->setValue("lastWillAvailable", true);
            pluginStorage()->endGroup();
        }
        if (payload.contains("Online")) {
            if (thing->stateValue(garadgetConnectedStateTypeId) == false) {
                qCDebug(dcGaradget) << "Setting" << thing->paramValue(garadgetThingDeviceNameParamTypeId).toString() << "to Online" ;
                thing->setStateValue(garadgetConnectedStateTypeId, true);
            }
        }
        if (payload.contains("Offline")) {
            if (thing->stateValue(garadgetConnectedStateTypeId) == true) {
                qCDebug(dcGaradget) << "Setting" << thing->paramValue(garadgetThingDeviceNameParamTypeId).toString() << "to Offline" ;
                thing->setStateValue(garadgetConnectedStateTypeId, false);
            }
        }
    }
}
