// 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-energy-plugin-nymea.
*
* nymea-energy-plugin-nymea.s 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-energy-plugin-nymea.s 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-energy-plugin-nymea. If not, see <https://www.gnu.org/licenses/>.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include "energyjsonhandler.h"
#include "energymanagermock.h"

Q_DECLARE_LOGGING_CATEGORY(dcEnergyExperience)

EnergyJsonHandler::EnergyJsonHandler(EnergyManager *energyManager, QObject *parent):
    JsonHandler(parent),
    m_energyManager(energyManager)
{
    registerEnum<EnergyManager::EnergyError>();
    registerEnum<EnergyLogs::SampleRate>();

    registerObject<PowerBalanceLogEntry, PowerBalanceLogEntries>();
    registerObject<ThingPowerLogEntry, ThingPowerLogEntries>();

    QVariantMap params, returns;
    QString description;

    params.clear(); returns.clear();
    description = "Get the root meter ID. If there is no root meter set, the params will be empty.";
    returns.insert("o:rootMeterThingId", enumValueName(Uuid));
    registerMethod("GetRootMeter", description, params, returns);

    params.clear(); returns.clear();
    description = "Set the root meter.";
    params.insert("rootMeterThingId", enumValueName(Uuid));
    returns.insert("energyError", enumRef<EnergyManager::EnergyError>());
    registerMethod("SetRootMeter", description, params, returns);

    params.clear(); returns.clear();
    description = "Get the current power balance. That is, production, consumption and acquisition.";
    returns.insert("currentPowerConsumption", enumValueName(Double));
    returns.insert("currentPowerProduction", enumValueName(Double));
    returns.insert("currentPowerAcquisition", enumValueName(Double));
    returns.insert("currentPowerStorage", enumValueName(Double));
    returns.insert("totalConsumption", enumValueName(Double));
    returns.insert("totalProduction", enumValueName(Double));
    returns.insert("totalAcquisition", enumValueName(Double));
    returns.insert("totalReturn", enumValueName(Double));
    registerMethod("GetPowerBalance", description, params, returns);

    params.clear(); returns.clear();
    description = "Get logs for the power balance. If from is not give, the log will start at the beginning of "
                  "recording. If to is not given, the logs will and at the last sample for this sample rate before now.";
    params.insert("sampleRate", enumRef<EnergyLogs::SampleRate>());
    params.insert("o:from", enumValueName(Uint));
    params.insert("o:to", enumValueName(Uint));
    returns.insert("powerBalanceLogEntries", objectRef<PowerBalanceLogEntries>());
    registerMethod("GetPowerBalanceLogs", description, params, returns);

    params.clear(); returns.clear();
    description = "Get logs for one or more things power values. If thingIds is not given, logs for all energy related "
                  "things will be returned. If from is not given, the log will start at the beginning of recording. If "
                  "to is not given, the logs will and at the last sample for this sample rate before now. If the parameter "
                  "\"includeCurrent\" is set to true, the result will contain the newest log entries available, regardless "
                  "of the sample rate (that is, 1 minute). This may be useful to calculate the difference to the newest "
                  "entry of the fetched sample rate and the current values to display the live value until the current sample "
                  "is completed.";
    params.insert("sampleRate", enumRef<EnergyLogs::SampleRate>());
    params.insert("o:thingIds", QVariantList() << enumValueName(Uuid));
    params.insert("o:from", enumValueName(Uint));
    params.insert("o:to", enumValueName(Uint));
    params.insert("o:includeCurrent", enumValueName(Bool));
    returns.insert("o:currentEntries", objectRef<ThingPowerLogEntries>());
    returns.insert("thingPowerLogEntries", objectRef<ThingPowerLogEntries>());
    registerMethod("GetThingPowerLogs", description, params, returns);

    params.clear();
    description = "Emitted whenever the root meter id changes. If the root meter has been unset, the params will be empty.";
    params.insert("o:rootMeterThingId", enumValueName(Uuid));
    registerNotification("RootMeterChanged", description, params);

    params.clear();
    description = "Emitted whenever the energy balance changes. That is, when the current consumption, production or "
                  "acquisition changes. Typically they will all change at the same time.";
    params.insert("currentPowerConsumption", enumValueName(Double));
    params.insert("currentPowerProduction", enumValueName(Double));
    params.insert("currentPowerAcquisition", enumValueName(Double));
    params.insert("currentPowerStorage", enumValueName(Double));
    params.insert("totalConsumption", enumValueName(Double));
    params.insert("totalProduction", enumValueName(Double));
    params.insert("totalAcquisition", enumValueName(Double));
    params.insert("totalReturn", enumValueName(Double));
    registerNotification("PowerBalanceChanged", description, params);

    params.clear();
    description = "Emitted whenever an entry is added to the power balance log.";
    params.insert("sampleRate", enumRef<EnergyLogs::SampleRate>());
    params.insert("powerBalanceLogEntry", objectRef<PowerBalanceLogEntry>());
    registerNotification("PowerBalanceLogEntryAdded", description, params);

    params.clear();
    description = "Emitted whenever an entry is added to the thing power log.";
    params.insert("sampleRate", enumRef<EnergyLogs::SampleRate>());
    params.insert("thingPowerLogEntry", objectRef<ThingPowerLogEntry>());
    registerNotification("ThingPowerLogEntryAdded", description, params);

    connect(m_energyManager, &EnergyManager::rootMeterChanged, this, [=](){
        QVariantMap params;
        if (m_energyManager->rootMeter()) {
            params.insert("rootMeterThingId", m_energyManager->rootMeter()->id());
        }
        emit RootMeterChanged(params);
    });

    connect(m_energyManager, &EnergyManager::powerBalanceChanged, this, [=](){
        QVariantMap params;
        params.insert("currentPowerConsumption", m_energyManager->currentPowerConsumption());
        params.insert("currentPowerProduction", m_energyManager->currentPowerProduction());
        params.insert("currentPowerAcquisition", m_energyManager->currentPowerAcquisition());
        params.insert("currentPowerStorage", m_energyManager->currentPowerStorage());
        params.insert("totalConsumption", m_energyManager->totalConsumption());
        params.insert("totalProduction", m_energyManager->totalProduction());
        params.insert("totalAcquisition", m_energyManager->totalAcquisition());
        params.insert("totalReturn", m_energyManager->totalReturn());
        emit PowerBalanceChanged(params);
    });

    connect(m_energyManager->logs(), &EnergyLogs::powerBalanceEntryAdded, this, [=](EnergyLogs::SampleRate sampleRate, const PowerBalanceLogEntry &entry){
        QVariantMap params;
        params.insert("sampleRate", enumValueName(sampleRate));
        params.insert("powerBalanceLogEntry", pack(entry));
        emit PowerBalanceLogEntryAdded(params);
    });

    connect(m_energyManager->logs(), &EnergyLogs::thingPowerEntryAdded, this, [=](EnergyLogs::SampleRate sampleRate, const ThingPowerLogEntry &entry){
        QVariantMap params;
        params.insert("sampleRate", enumValueName(sampleRate));
        params.insert("thingPowerLogEntry", pack(entry));
        emit ThingPowerLogEntryAdded(params);
    });
}

QString EnergyJsonHandler::name() const
{
    return "Energy";
}

JsonReply *EnergyJsonHandler::GetRootMeter(const QVariantMap &params)
{
    Q_UNUSED(params)
    QVariantMap ret;
    if (m_energyManager->rootMeter()) {
        ret.insert("rootMeterThingId", m_energyManager->rootMeter()->id());
    }
    return createReply(ret);
}

JsonReply *EnergyJsonHandler::SetRootMeter(const QVariantMap &params)
{
    QVariantMap returns;

    if (!params.contains("rootMeterThingId")) {
        returns.insert("energyError", enumValueName(EnergyManager::EnergyErrorMissingParameter));
        return createReply(returns);
    }
    EnergyManager::EnergyError status = m_energyManager->setRootMeter(params.value("rootMeterThingId").toUuid());
    returns.insert("energyError", enumValueName(status));
    return createReply(returns);
}

JsonReply *EnergyJsonHandler::GetPowerBalance(const QVariantMap &params)
{
    Q_UNUSED(params)
    QVariantMap ret;
    ret.insert("currentPowerConsumption", m_energyManager->currentPowerConsumption());
    ret.insert("currentPowerProduction", m_energyManager->currentPowerProduction());
    ret.insert("currentPowerAcquisition", m_energyManager->currentPowerAcquisition());
    ret.insert("currentPowerStorage", m_energyManager->currentPowerStorage());
    ret.insert("totalConsumption", m_energyManager->totalConsumption());
    ret.insert("totalProduction", m_energyManager->totalProduction());
    ret.insert("totalAcquisition", m_energyManager->totalAcquisition());
    ret.insert("totalReturn", m_energyManager->totalReturn());
    return createReply(ret);
}

JsonReply *EnergyJsonHandler::GetPowerBalanceLogs(const QVariantMap &params)
{
    EnergyLogs::SampleRate sampleRate = enumNameToValue<EnergyLogs::SampleRate>(params.value("sampleRate").toString());
    QDateTime from = params.contains("from") ? QDateTime::fromMSecsSinceEpoch(params.value("from").toLongLong() * 1000) : QDateTime();
    QDateTime to = params.contains("to") ? QDateTime::fromMSecsSinceEpoch(params.value("to").toLongLong() * 1000) : QDateTime();
    QVariantMap returns;
    returns.insert("powerBalanceLogEntries", pack(m_energyManager->logs()->powerBalanceLogs(sampleRate, from, to)));
    return createReply(returns);
}

JsonReply *EnergyJsonHandler::GetThingPowerLogs(const QVariantMap &params)
{
    EnergyLogs::SampleRate sampleRate = enumNameToValue<EnergyLogs::SampleRate>(params.value("sampleRate").toString());
    QList<ThingId> thingIds;
    foreach (const QVariant &thingId, params.value("thingIds").toList()) {
        thingIds.append(thingId.toUuid());
    }
    QDateTime from = params.contains("from") ? QDateTime::fromMSecsSinceEpoch(params.value("from").toLongLong() * 1000) : QDateTime();
    QDateTime to = params.contains("to") ? QDateTime::fromMSecsSinceEpoch(params.value("to").toLongLong() * 1000) : QDateTime();
    QVariantMap returns;
    returns.insert("thingPowerLogEntries", pack(m_energyManager->logs()->thingPowerLogs(sampleRate, thingIds, from, to)));

    if (params.contains("includeCurrent") && params.value("includeCurrent").toBool()) {
        returns.insert("currentEntries", pack(m_energyManager->logs()->thingPowerLogs(EnergyLogs::SampleRate1Min, thingIds, QDateTime::currentDateTime().addSecs(-60))));
    }

    return createReply(returns);
}
