// 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 "energytestbase.h"

#include <QTimeZone>

EnergyTestBase::EnergyTestBase(QObject *parent) :
    NymeaTestBase(parent),
    m_networkAccessManager(new QNetworkAccessManager(this))
{
    qCDebug(dcTests()) << "Running in:" << QDir::currentPath();
    QString mockPluginEnvPath = QDir(QDir::currentPath() + "/../../mocks/plugins/energymocks/").absolutePath();
    qCDebug(dcTests()) << "Mock plugin path:" << mockPluginEnvPath;

    QString energyPluginEnvPath = QDir(QDir::currentPath() + "/../../../energyplugin/").absolutePath();
    qCDebug(dcTests()) << "Energy plugin path:" << energyPluginEnvPath;

    // Add our mock plugin to the loading path
    if (QString(qgetenv("NYMEA_PLUGINS_EXTRA_PATH")).isEmpty()) {
        qputenv("NYMEA_PLUGINS_EXTRA_PATH", mockPluginEnvPath.toUtf8());
    } else {
        qputenv("NYMEA_PLUGINS_EXTRA_PATH", QString(QString(qgetenv("NYMEA_PLUGINS_EXTRA_PATH")) + ":" + mockPluginEnvPath).toUtf8());
    }

    QFileInfo configurationFileInfo(":/energy-manager-configuration.json");
    if (configurationFileInfo.exists()) {
        qputenv("NYMEA_ENERGY_MANAGER_CONFIG", ":/energy-manager-configuration.json");
    }

    // Add this enery plugin to the paths
    qputenv("NYMEA_ENERGY_PLUGINS_EXTRA_PATH", energyPluginEnvPath.toUtf8());

    qCDebug(dcTests()).nospace() << "NYMEA_PLUGINS_EXTRA_PATH=" << qgetenv("NYMEA_PLUGINS_EXTRA_PATH");
    qCDebug(dcTests()).nospace() << "NYMEA_ENERGY_PLUGINS_EXTRA_PATH=" << qgetenv("NYMEA_ENERGY_PLUGINS_EXTRA_PATH");
    qCDebug(dcTests()).nospace() << "NYMEA_EXPERIENCE_PLUGINS_PATH=" << qgetenv("NYMEA_EXPERIENCE_PLUGINS_PATH");
}

QDateTime EnergyTestBase::utcDateTime(const QDate &date, const QTime &time)
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
    return QDateTime(date, time, QTimeZone::UTC);
#else
    return QDateTime(date, time, Qt::UTC);
#endif
}

void EnergyTestBase::initTestCase(const QString &loggingRules, bool checkExperienceLoaded, bool disableLogEngine)
{
    QDir dir(NymeaSettings::translationsPath());
    dir.mkpath(NymeaSettings::translationsPath());
    QStringList languages = {"de", "en_US"};
    foreach (const QString &language, languages) {
        QFile f(NymeaSettings::translationsPath().append("/nymead-" + language + ".qm"));
        QVERIFY2(f.open(QFile::WriteOnly), "Could not create translation file.");
        f.write(" ");
        f.close();
    }

    // If testcase asserts cleanup won't do. Lets clear any previous test run settings leftovers
    QSettings energySettings(NymeaSettings::settingsPath() +  "/energy.conf", QSettings::IniFormat);
    energySettings.clear();

    if (loggingRules.isEmpty()) {
        NymeaTestBase::initTestCase("*.info=true\n*.debug=false\nApplication.debug=true\nTests.debug=true\nExperiences.debug=true\nNymeaEnergy.debug=true\nEnergyMocks.debug=true\nDBus.warning=false", disableLogEngine);
    } else {
        NymeaTestBase::initTestCase(loggingRules, disableLogEngine);
    }

    QVariant reply = injectAndWait("JSONRPC.Hello");
    qCDebug(dcTests()) << qUtf8Printable(QJsonDocument::fromVariant(reply).toJson());

    if (checkExperienceLoaded) {
        QString experienceVersion = "1.0"; // TODO: Perhaps this should be defined in the project file somewhere
        QVERIFY2(reply.toMap().value("params").toMap().contains("experiences"),
                 QString("No experience plugins loaded!\n%1")
                 .arg(QString(QJsonDocument::fromVariant(reply).toJson())).toUtf8());
        QVariantMap experienceDefinition;
        experienceDefinition.insert("name", "NymeaEnergy");
        experienceDefinition.insert("version", experienceVersion);

        QVERIFY2(reply.toMap().value("params").toMap().value("experiences").toList().contains(experienceDefinition),
                 QString("NymeaEnergy %1 experience plugins not loaded!\n%2")
                 .arg(experienceVersion)
                 .arg(QString(QJsonDocument::fromVariant(reply).toJson())).toUtf8());

        enableNotifications({"Integrations", "Energy", "NymeaEnergy"});
    }

    removeDevices();
}

void EnergyTestBase::cleanupTestCase()
{
    removeDevices();
    NymeaTestBase::cleanupTestCase();
}

void EnergyTestBase::init()
{
    removeDevices();
}

void EnergyTestBase::cleanup()
{
    removeDevices();
}

QNetworkReply *EnergyTestBase::setMeterStates(const QVariantMap &phasesPower, bool connected,  quint16 port)
{
    QUrl requestUrl;
    requestUrl.setScheme("http");
    requestUrl.setHost("127.0.0.1");
    requestUrl.setPort(port);
    requestUrl.setPath("/setstates");

    QUrlQuery query;
    query.addQueryItem("connected", connected ? "true" : "false");
    query.addQueryItem("currentPowerPhaseA", QString::number(phasesPower.value("A").toInt()));
    query.addQueryItem("currentPowerPhaseB", QString::number(phasesPower.value("B").toInt()));
    query.addQueryItem("currentPowerPhaseC", QString::number(phasesPower.value("C").toInt()));
    requestUrl.setQuery(query);

    QNetworkRequest request(requestUrl);
    request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");

    return m_networkAccessManager->post(request, QByteArray());
}

QNetworkReply *EnergyTestBase::setCarStates(uint batteryLevel, uint capacity, uint minChargingCurrent, uint phaseCount, quint16 port)
{
    QUrl requestUrl;
    requestUrl.setScheme("http");
    requestUrl.setHost("127.0.0.1");
    requestUrl.setPort(port);
    requestUrl.setPath("/setstates");

    QUrlQuery query;
    query.addQueryItem("batteryLevel", QString::number(batteryLevel));
    query.addQueryItem("capacity", QString::number(capacity));
    query.addQueryItem("minChargingCurrent", QString::number(minChargingCurrent));
    query.addQueryItem("phaseCount", QString::number(phaseCount));
    requestUrl.setQuery(query);

    QNetworkRequest request(requestUrl);
    request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");

    return m_networkAccessManager->post(request, QByteArray());
}

QNetworkReply *EnergyTestBase::setChargerStates(bool connected, bool power, bool pluggedIn, const QString &phases, int maxChargingCurrent, int maxChargingCurrentMaxValue, quint16 port)
{
    QUrl requestUrl;
    requestUrl.setScheme("http");
    requestUrl.setHost("127.0.0.1");
    requestUrl.setPort(port);
    requestUrl.setPath("/setstates");

    QUrlQuery query;
    query.addQueryItem("connected", connected ? "true" : "false");
    query.addQueryItem("power", power ? "true" : "false");
    query.addQueryItem("pluggedIn", pluggedIn ? "true" : "false");
    query.addQueryItem("usedPhases", phases);
    query.addQueryItem("maxChargingCurrent", QString::number(maxChargingCurrent));
    query.addQueryItem("maxChargingCurrentMaxValue", QString::number(maxChargingCurrentMaxValue));
    requestUrl.setQuery(query);

    QNetworkRequest request(requestUrl);
    request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");

    return m_networkAccessManager->post(request, QByteArray());
}

QNetworkReply *EnergyTestBase::setChargerWithPhaseCountSwitchingStates(bool connected, bool power, bool pluggedIn, const QString &phases, int maxChargingCurrent, int maxChargingCurrentMaxValue, uint desiredPhaseCount, quint16 port)
{
    QUrl requestUrl;
    requestUrl.setScheme("http");
    requestUrl.setHost("127.0.0.1");
    requestUrl.setPort(port);
    requestUrl.setPath("/setstates");

    QUrlQuery query;
    query.addQueryItem("connected", connected ? "true" : "false");
    query.addQueryItem("power", power ? "true" : "false");
    query.addQueryItem("pluggedIn", pluggedIn ? "true" : "false");
    query.addQueryItem("usedPhases", phases);
    query.addQueryItem("maxChargingCurrent", QString::number(maxChargingCurrent));
    query.addQueryItem("maxChargingCurrentMaxValue", QString::number(maxChargingCurrentMaxValue));
    query.addQueryItem("desiredPhaseCount", QString::number(desiredPhaseCount));
    requestUrl.setQuery(query);

    QNetworkRequest request(requestUrl);
    request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");

    return m_networkAccessManager->post(request, QByteArray());
}

QNetworkReply *EnergyTestBase::setSimpleChargerStates(bool connected, bool power, bool pluggedIn, int phaseCount, int maxChargingCurrent, quint16 port)
{
    QUrl requestUrl;
    requestUrl.setScheme("http");
    requestUrl.setHost("127.0.0.1");
    requestUrl.setPort(port);
    requestUrl.setPath("/setstates");

    QUrlQuery query;
    query.addQueryItem("connected", connected ? "true" : "false");
    query.addQueryItem("power", power ? "true" : "false");
    query.addQueryItem("pluggedIn", pluggedIn ? "true" : "false");
    query.addQueryItem("phaseCount", QString::number(phaseCount));
    query.addQueryItem("maxChargingCurrent", QString::number(maxChargingCurrent));
    requestUrl.setQuery(query);

    QNetworkRequest request(requestUrl);
    request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");

    return m_networkAccessManager->post(request, QByteArray());
}

QNetworkReply *EnergyTestBase::setEnergyStorageStates(uint batteryLevel, int currentPower, quint16 port)
{
    QUrl requestUrl;
    requestUrl.setScheme("http");
    requestUrl.setHost("127.0.0.1");
    requestUrl.setPort(port);
    requestUrl.setPath("/setstates");

    QUrlQuery query;
    query.addQueryItem("batteryLevel", QString::number(batteryLevel));
    query.addQueryItem("currentPower", QString::number(currentPower));
    requestUrl.setQuery(query);

    QNetworkRequest request(requestUrl);
    request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");

    return m_networkAccessManager->post(request, QByteArray());
}

QNetworkReply *EnergyTestBase::getActionHistory(quint16 port)
{
    QUrl requestUrl;
    requestUrl.setScheme("http");
    requestUrl.setHost("127.0.0.1");
    requestUrl.setPort(port);
    requestUrl.setPath("/actionhistory");

    QNetworkRequest request(requestUrl);
    request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");

    return m_networkAccessManager->get(request);
}

QNetworkReply *EnergyTestBase::clearActionHistroy(quint16 port)
{
    QUrl requestUrl;
    requestUrl.setScheme("http");
    requestUrl.setHost("127.0.0.1");
    requestUrl.setPort(port);
    requestUrl.setPath("/clearactionhistory");

    QNetworkRequest request(requestUrl);
    request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");

    return m_networkAccessManager->get(request);
}

QUuid EnergyTestBase::addMeter(quint16 port)
{
    QVariantList thingParams;
    QVariantMap portParam;
    portParam.insert("paramTypeId", "{7abcc8a1-08b1-45bc-9116-10f9848359f9}");
    portParam.insert("value", port);
    thingParams.append(portParam);

    QVariantMap params;
    params.insert("thingClassId", mockMeterThingClassId.toString());
    params.insert("name", "Meter");
    params.insert("thingParams", thingParams);

    QVariant response = injectAndWait("Integrations.AddThing", params);
    verifyThingError(response);

    return response.toMap().value("params").toMap().value("thingId").toUuid();
}

QUuid EnergyTestBase::addCar(quint16 port)
{
    QVariantList thingParams;
    QVariantMap portParam;
    portParam.insert("paramTypeId", "{29533bc8-d71e-4ce5-8ce3-bf87f0370391}");
    portParam.insert("value", port);
    thingParams.append(portParam);

    QVariantMap params;
    params.insert("thingClassId", mockCarThingClassId.toString());
    params.insert("name", "Tschitti Tschitti Bäng Bäng");
    params.insert("thingParams", thingParams);

    QVariant response = injectAndWait("Integrations.AddThing", params);
    verifyThingError(response);
    return response.toMap().value("params").toMap().value("thingId").toUuid();
}

QUuid EnergyTestBase::addCharger(const QString &phases, double maxChargingCurrentUpperLimit, quint16 port)
{
    QVariantList thingParams;
    QVariantMap portParam;
    portParam.insert("paramTypeId", "{652624a2-8f9a-4bc3-b34f-5e3492af4d30}");
    portParam.insert("value", port);

    QVariantMap phasesParam;
    phasesParam.insert("paramTypeId", "{facd5c76-d15e-4e29-9929-5e1764ae05dc}");
    phasesParam.insert("value", phases);

    QVariantMap maxCurrentParam;
    maxCurrentParam.insert("paramTypeId", "{234c6676-1ec0-4eff-bed0-ecee7ce82074}");
    maxCurrentParam.insert("value", maxChargingCurrentUpperLimit);

    thingParams.append(portParam);
    thingParams.append(phasesParam);
    thingParams.append(maxCurrentParam);

    QVariantMap params;
    params.insert("thingClassId", mockChargerThingClassId.toString());
    params.insert("name", "Charger");
    params.insert("thingParams", thingParams);

    QVariant response = injectAndWait("Integrations.AddThing", params);
    verifyThingError(response);
    return response.toMap().value("params").toMap().value("thingId").toUuid();
}

QUuid EnergyTestBase::addChargerWithPhaseCountSwitching(const QString &phases, double maxChargingCurrentUpperLimit, quint16 port)
{
    QVariantList thingParams;
    QVariantMap portParam;
    portParam.insert("paramTypeId", "{d4492038-51bf-4f3c-8b93-89af4d8edd6b}");
    portParam.insert("value", port);

    QVariantMap phasesParam;
    phasesParam.insert("paramTypeId", "{fb64b557-d4ee-4a89-9141-0c585ee476c9}");
    phasesParam.insert("value", phases);

    QVariantMap maxCurrentParam;
    maxCurrentParam.insert("paramTypeId", "{7c1a941d-3eb2-4c6b-90b5-b0faf82dcb73}");
    maxCurrentParam.insert("value", maxChargingCurrentUpperLimit);

    thingParams.append(portParam);
    thingParams.append(phasesParam);
    thingParams.append(maxCurrentParam);

    QVariantMap params;
    params.insert("thingClassId", mockChargerWithPhaseSwitchingThingClassId.toString());
    params.insert("name", "Charger with phase count switching");
    params.insert("thingParams", thingParams);

    QVariant response = injectAndWait("Integrations.AddThing", params);
    verifyThingError(response);
    return response.toMap().value("params").toMap().value("thingId").toUuid();
}

QUuid EnergyTestBase::addSimpleCharger(double maxChargingCurrentUpperLimit, quint16 port)
{
    QVariantList thingParams;
    QVariantMap portParam;
    portParam.insert("paramTypeId", "{e94f863b-47a2-44e4-8103-bb4b8a817f47}");
    portParam.insert("value", port);

    QVariantMap maxCurrentParam;
    maxCurrentParam.insert("paramTypeId", "{61943026-985b-4728-bfaa-299d0fbcdcae}");
    maxCurrentParam.insert("value", maxChargingCurrentUpperLimit);

    thingParams.append(portParam);
    thingParams.append(maxCurrentParam);

    QVariantMap params;
    params.insert("thingClassId", mockSimpleChargerThingClassId.toString());
    params.insert("name", "Simple charger");
    params.insert("thingParams", thingParams);

    QVariant response = injectAndWait("Integrations.AddThing", params);
    verifyThingError(response);
    return response.toMap().value("params").toMap().value("thingId").toUuid();
}

QUuid EnergyTestBase::addEnergyStorage(uint capacity, double maxChargingPowerUpperLimit, double maxDischargingPowerUpperLimit, quint16 port)
{
    QVariantList thingParams;
    QVariantMap portParam;
    portParam.insert("paramTypeId", "{b074a76f-6382-48b9-b101-1d13904f30c8}");
    portParam.insert("value", port);

    QVariantMap capacityParam;
    capacityParam.insert("paramTypeId", "{fd56c49e-98e2-4373-bf23-fd8b5a58e339}");
    capacityParam.insert("value", capacity);

    QVariantMap maxChargingPowerParam;
    maxChargingPowerParam.insert("paramTypeId", "{769391a8-b801-42fc-af63-90367bf46bdf}");
    maxChargingPowerParam.insert("value", maxChargingPowerUpperLimit);

    QVariantMap maxDischargingPowerParam;
    maxDischargingPowerParam.insert("paramTypeId", "{559d9434-9b31-4e0d-a654-2ea1e5f65a82}");
    maxDischargingPowerParam.insert("value", maxDischargingPowerUpperLimit);

    thingParams.append(portParam);
    thingParams.append(capacityParam);
    thingParams.append(maxChargingPowerParam);
    thingParams.append(maxDischargingPowerParam);

    QVariantMap params;
    params.insert("thingClassId", mockEnergyStorageThingClassId.toString());
    params.insert("name", "Energy storage");
    params.insert("thingParams", thingParams);

    QVariant response = injectAndWait("Integrations.AddThing", params);
    verifyThingError(response);
    return response.toMap().value("params").toMap().value("thingId").toUuid();
}

void EnergyTestBase::removeDevices()
{
    QVariant configuredDevices = injectAndWait("Integrations.GetThings");
    foreach (const QVariant &dev, configuredDevices.toMap().value("params").toMap().value("things").toList()) {
        qCDebug(dcTests()) << "Removing thing" << dev.toMap().value("name").toString() << dev.toMap().value("id").toUuid();
        QVariant response = removeDevice(dev.toMap().value("id").toUuid());
        verifyThingError(response);
    }
}

QVariant EnergyTestBase::removeDevice(const QUuid &thingId)
{
    qCDebug(dcTests()) << "Remove device" << thingId.toString();
    QVariantMap params;
    params.insert("thingId", thingId.toString());
    return injectAndWait("Integrations.RemoveThing", params);
}

bool EnergyTestBase::verifyActionExecuted(const QVariantList &actionHistory, const QString &actionName)
{
    foreach (const QVariant &actionHistoryVariant, actionHistory) {
        QVariantMap actionHistoryMap = actionHistoryVariant.toMap();
        if (actionHistoryMap.value("name").toString() == actionName) {
            return true;
        }
    }

    return false;
}

QVariant EnergyTestBase::getLastValueFromExecutedAction(const QVariantList &actionHistory, const QString &actionName, const QString &paramName)
{
    QVariant paramValue;
    foreach (const QVariant &actionHistoryVariant, actionHistory) {
        QVariantMap actionHistoryMap = actionHistoryVariant.toMap();
        if (actionHistoryMap.value("name").toString() == actionName) {
            foreach (const QVariant &paramVariant, actionHistoryMap.value("params").toList()) {
                if (paramVariant.toMap().value("name").toString() == paramName) {
                    paramValue = paramVariant.toMap().value("value");
                }
            }
        }
    }

    return paramValue;
}
