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

#include <hardware/electricity.h>

TestCharging::TestCharging(QObject *parent):
    EnergyTestBase(parent)
{

}

void TestCharging::testAddCharger()
{
    // Add
    QUuid chargerThingId = addCharger("ABC", 16);
    QVERIFY2(!chargerThingId.isNull(), "Did not receive valid ThingId");

    // Remove
    QVariant response = removeDevice(chargerThingId);
    verifyThingError(response);
}

void TestCharging::testAddSimpleCharger()
{
    // Add
    QUuid chargerThingId = addSimpleCharger(20);
    QVERIFY2(!chargerThingId.isNull(), "Did not receive valid ThingId");

    // Remove
    QVariant response = removeDevice(chargerThingId);
    verifyThingError(response);
}

void TestCharging::testAddCar()
{
    // Add
    QUuid carThingId = addCar();
    QVERIFY2(!carThingId.isNull(), "Did not receive valid ThingId");

    // Remove
    QVariant response = removeDevice(carThingId);
    verifyThingError(response);
}

void TestCharging::testAddMeter()
{
    QVariant response, notification;

    QSignalSpy notificationSpy(m_mockTcpServer, &MockTcpServer::outgoingData);;

    // Make sure there is no root meter
    response = injectAndWait("Energy.GetRootMeter");
    QVERIFY(response.toMap().value("params").toMap().isEmpty());
    QVERIFY(response.toMap().value("status").toString() == "success");

    // Add mock meter
    QUuid meterThingId = addMeter();
    QVERIFY2(!meterThingId.isNull(), "Did not receive valid ThingId");

    // Check notification Energy.RootMeterChanged containing our added meter thing id
    if (notificationSpy.count() == 0) notificationSpy.wait();
    notification = checkNotification(notificationSpy, "Energy.RootMeterChanged");
    QCOMPARE(notification.toMap().value("params").toMap().value("rootMeterThingId").toUuid(), meterThingId);

    // Make sure this is our root meter now
    response = injectAndWait("Energy.GetRootMeter");
    QCOMPARE(response.toMap().value("params").toMap().value("rootMeterThingId").toUuid(), meterThingId);

    // Undo all steps

    // Remove meter
    notificationSpy.clear();
    response = removeDevice(meterThingId);
    qDebug() << response.toMap();

    // Check notification Energy.RootMeterChanged containing our added meter thing id
    if (notificationSpy.count() == 0) notificationSpy.wait();
    notification = checkNotification(notificationSpy, "Energy.RootMeterChanged");
    QVERIFY(notification.toMap().value("params").toMap().isEmpty());

    // Make sure there is no root meter
    response = injectAndWait("Energy.GetRootMeter");
    QVERIFY(response.toMap().value("params").toMap().isEmpty());
    QVERIFY(response.toMap().value("status").toString() == "success");
}

void TestCharging::getSetPhaseLimits()
{
    QVariant response, notification;
    QSignalSpy notificationSpy(m_mockTcpServer, &MockTcpServer::outgoingData);;

    uint phasePowerLimit = 0;
    uint newPhasePowerLimit = 30;

    response = injectAndWait("NymeaEnergy.GetPhasePowerLimit");
    phasePowerLimit = response.toMap().value("params").toMap().value("phasePowerLimit").toUInt();
    // Initially 0 since we have no things at all set up
    if (phasePowerLimit == newPhasePowerLimit) {
        newPhasePowerLimit++;
    }

    notificationSpy.clear();
    response = injectAndWait("NymeaEnergy.SetPhasePowerLimit", QVariantMap({{"phasePowerLimit", newPhasePowerLimit}}));
    QVERIFY(response.toMap().value("params").toMap().value("energyError").toString() == "EnergyErrorNoError");
    if (notificationSpy.count() == 0) notificationSpy.wait();
    notification = checkNotification(notificationSpy, "NymeaEnergy.PhasePowerLimitChanged");
    QCOMPARE(notification.toMap().value("params").toMap().value("phasePowerLimit").toUInt(), newPhasePowerLimit);

    response = injectAndWait("NymeaEnergy.GetPhasePowerLimit");
    phasePowerLimit = response.toMap().value("params").toMap().value("phasePowerLimit").toUInt();
    QCOMPARE(phasePowerLimit, newPhasePowerLimit);


    notificationSpy.clear();
    newPhasePowerLimit = 30000;
    response = injectAndWait("NymeaEnergy.SetPhasePowerLimit", QVariantMap({{"phasePowerLimit", newPhasePowerLimit}}));
    QVERIFY(response.toMap().value("params").toMap().value("energyError").toString() == "EnergyErrorNoError");
    if (notificationSpy.count() == 0) notificationSpy.wait();
    notification = checkNotification(notificationSpy, "NymeaEnergy.PhasePowerLimitChanged");
    QCOMPARE(notification.toMap().value("params").toMap().value("phasePowerLimit").toUInt(), newPhasePowerLimit);

    response = injectAndWait("NymeaEnergy.GetPhasePowerLimit");
    phasePowerLimit = response.toMap().value("params").toMap().value("phasePowerLimit").toUInt();
    QCOMPARE(phasePowerLimit, newPhasePowerLimit);
}


void TestCharging::getSetLockOnUnplug_data()
{
    QTest::addColumn<bool>("lockOnUnplug");

    QTest::newRow("Enable") << true;
    QTest::newRow("Disable") << false;
}

void TestCharging::getSetLockOnUnplug()
{
    QFETCH(bool, lockOnUnplug);

    QVariantMap params;
    QVariant response, notification;
    QSignalSpy notificationSpy(m_mockTcpServer, &MockTcpServer::outgoingData);;

    // Set initial lockOnUnplug to oposit, so we get the changed notification for sure
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    params.insert("lockOnUnplug", !lockOnUnplug);
    response = injectAndWait("NymeaEnergy.SetLockOnUnplug", params);
    verifyEnergyError(response);

    // Set actual lockOnUnplug
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    params.insert("lockOnUnplug", lockOnUnplug);
    response = injectAndWait("NymeaEnergy.SetLockOnUnplug", params);
    verifyEnergyError(response);

    // If the value has changed, verify the notification
    if (notificationSpy.count() == 0) notificationSpy.wait();
    notification = checkNotification(notificationSpy, "NymeaEnergy.LockOnUnplugChanged");
    QCOMPARE(notification.toMap().value("params").toMap().value("lockOnUnplug").toDouble(), lockOnUnplug);

    // Get acquisition tolerance
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    response = injectAndWait("NymeaEnergy.GetLockOnUnplug", params);
    QVERIFY2(response.toMap().value("params").toMap().contains("lockOnUnplug"), "Did not get aquisition tolerance.");
    QCOMPARE(response.toMap().value("params").toMap().value("lockOnUnplug").toBool(), lockOnUnplug);
}

void TestCharging::getSetAcquisitionTolerance_data()
{
    QTest::addColumn<double>("initialAcquisitionTolerance");
    QTest::addColumn<double>("acquisitionTolerance");
    QTest::addColumn<EnergyManager::EnergyError>("expectedError");

    QTest::newRow("Default") << 0.1 << 0.5 << EnergyManager::EnergyErrorNoError;
    QTest::newRow("Unchanged") << 0.5 << 0.5 << EnergyManager::EnergyErrorNoError;
    QTest::newRow("< 0.0") << 0.5 << -1.0 << EnergyManager::EnergyErrorInvalidParameter;
    QTest::newRow("> 1.0") << 0.5 << 2.0 << EnergyManager::EnergyErrorInvalidParameter;
}

void TestCharging::getSetAcquisitionTolerance()
{
    QFETCH(double, initialAcquisitionTolerance);
    QFETCH(double, acquisitionTolerance);
    QFETCH(EnergyManager::EnergyError, expectedError);

    QVariantMap params;
    QVariant response, notification;
    QSignalSpy notificationSpy(m_mockTcpServer, &MockTcpServer::outgoingData);;

    // Set initial acquisition tolerance
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    params.insert("acquisitionTolerance", initialAcquisitionTolerance);
    response = injectAndWait("NymeaEnergy.SetAcquisitionTolerance", params);
    verifyEnergyError(response);

    bool testNotificationReceived = (initialAcquisitionTolerance != acquisitionTolerance);

    // Set acquisition tolerance
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    params.insert("acquisitionTolerance", acquisitionTolerance);
    response = injectAndWait("NymeaEnergy.SetAcquisitionTolerance", params);
    verifyEnergyError(response, expectedError);

    // If the value has changed, verify the notification
    if (testNotificationReceived && expectedError == EnergyManager::EnergyErrorNoError) {
        if (notificationSpy.count() == 0) notificationSpy.wait();
        notification = checkNotification(notificationSpy, "NymeaEnergy.AcquisitionToleranceChanged");
        QCOMPARE(notification.toMap().value("params").toMap().value("acquisitionTolerance").toDouble(), acquisitionTolerance);
    }

    // Get acquisition tolerance
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    response = injectAndWait("NymeaEnergy.GetAcquisitionTolerance", params);
    QVERIFY2(response.toMap().value("params").toMap().contains("acquisitionTolerance"), "Did not get aquisition tolerance.");
}

void TestCharging::getSetBatteryLevelConsideration_data()
{
    QTest::addColumn<double>("initialBatteryLevelConsideration");
    QTest::addColumn<double>("batteryLevelConsideration");
    QTest::addColumn<EnergyManager::EnergyError>("expectedError");

    QTest::newRow("Default") << 0.1 << 0.5 << EnergyManager::EnergyErrorNoError;
    QTest::newRow("Unchanged") << 0.5 << 0.5 << EnergyManager::EnergyErrorNoError;
    QTest::newRow("< 0.0") << 0.5 << -1.0 << EnergyManager::EnergyErrorInvalidParameter;
    QTest::newRow("> 1.0") << 0.5 << 2.0 << EnergyManager::EnergyErrorInvalidParameter;
}

void TestCharging::getSetBatteryLevelConsideration()
{
    QFETCH(double, initialBatteryLevelConsideration);
    QFETCH(double, batteryLevelConsideration);
    QFETCH(EnergyManager::EnergyError, expectedError);

    QVariantMap params;
    QVariant response, notification;
    QSignalSpy notificationSpy(m_mockTcpServer, &MockTcpServer::outgoingData);;

    // Set initial acquisition tolerance
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    params.insert("batteryLevelConsideration", initialBatteryLevelConsideration);
    response = injectAndWait("NymeaEnergy.SetBatteryLevelConsideration", params);
    verifyEnergyError(response);

    bool testNotificationReceived = (initialBatteryLevelConsideration != batteryLevelConsideration);

    // Set acquisition tolerance
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    params.insert("batteryLevelConsideration", batteryLevelConsideration);
    response = injectAndWait("NymeaEnergy.SetBatteryLevelConsideration", params);
    verifyEnergyError(response, expectedError);

    // If the value has changed, verify the notification
    if (testNotificationReceived && expectedError == EnergyManager::EnergyErrorNoError) {
        if (notificationSpy.count() == 0) notificationSpy.wait();
        notification = checkNotification(notificationSpy, "NymeaEnergy.BatteryLevelConsiderationChanged");
        QCOMPARE(notification.toMap().value("params").toMap().value("batteryLevelConsideration").toDouble(), batteryLevelConsideration);
    }

    // Get acquisition tolerance
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    response = injectAndWait("NymeaEnergy.GetBatteryLevelConsideration", params);
    QVERIFY2(response.toMap().value("params").toMap().contains("batteryLevelConsideration"), "Did not get battery level consideration.");
}

void TestCharging::addChargingInfo_data()
{
    QTest::addColumn<bool>("addingCharger");
    QTest::addColumn<bool>("addingCar");
    QTest::addColumn<bool>("carValid");
    QTest::addColumn<QString>("chargingMode");
    QTest::addColumn<QDateTime>("endDateTime");
    QTest::addColumn<QList<int>>("repeatDays");
    QTest::addColumn<int>("targetPercentage");
    QTest::addColumn<double>("acquisitionTolerance");
    QTest::addColumn<bool>("jsonSuccess");
    QTest::addColumn<EnergyManager::EnergyError>("expectedError");

    QDateTime endDateTime = QDateTime::currentDateTime().addDays(1);
    endDateTime.setTime(QTime(7, 0));
    QTest::newRow("Default")            << true  << true  << true  << "ChargingModeNormal"  << endDateTime << QList<int>{} << 100 << 0.5 << true << EnergyManager::EnergyErrorNoError;
    QTest::newRow("Default Eco")        << true  << true  << true  << "ChargingModeEco"     << endDateTime << QList<int>{} << 100 << 0.5 << true << EnergyManager::EnergyErrorNoError;
    QTest::newRow("Invalid mode")       << true  << true  << true  << "ChargingModeBlablaa" << endDateTime << QList<int>{} << 100 << 0.5 << false << EnergyManager::EnergyErrorNoError;
    QTest::newRow("Invalid charger")    << false << true  << true  << "ChargingModeNormal"  << endDateTime << QList<int>{} << 100 << 0.5 << true << EnergyManager::EnergyErrorInvalidParameter;
    QTest::newRow("with repeatdays")    << true  << false << true  << "ChargingModeNormal"  << endDateTime << QList<int>{1,3,6} << 100 << 0.5 << true << EnergyManager::EnergyErrorNoError;
    QTest::newRow("Invalid repeatDays") << true  << false << true  << "ChargingModeNormal"  << endDateTime << QList<int>{0,1,2} << 100 << 0.5 << true << EnergyManager::EnergyErrorInvalidParameter;
    QTest::newRow("Invalid car")        << true  << true  << false << "ChargingModeEco"     << endDateTime << QList<int>{} << 100 << 0.5 << true << EnergyManager::EnergyErrorInvalidParameter;
    QTest::newRow("Invalid %")          << true  << true  << true  << "ChargingModeNormal"  << endDateTime << QList<int>{} << 200 << 0.5 << true << EnergyManager::EnergyErrorInvalidParameter;
}

void TestCharging::addChargingInfo()
{
    QFETCH(bool, addingCharger);
    QFETCH(bool, addingCar);
    QFETCH(bool, carValid);
    QFETCH(QString, chargingMode);
    QFETCH(QDateTime, endDateTime);
    QFETCH(QList<int>, repeatDays);
    QFETCH(int, targetPercentage);
    QFETCH(double, acquisitionTolerance);
    QFETCH(bool, jsonSuccess);
    QFETCH(EnergyManager::EnergyError, expectedError);

    QVariant response, notification;
    QSignalSpy notificationSpy(m_mockTcpServer, &MockTcpServer::outgoingData);;

    // Set phase power limit
    uint phasePowerLimit = 25000;
    response = injectAndWait("NymeaEnergy.SetPhasePowerLimit", QVariantMap({{"phasePowerLimit", phasePowerLimit}}));
    verifyEnergyError(response);

    removeDevices();

    // Add mock meter
    QUuid meterThingId = addMeter();
    QVERIFY2(!meterThingId.isNull(), "Did not receive valid ThingId");
    if (notificationSpy.count() == 0) notificationSpy.wait();
    notification = checkNotification(notificationSpy, "Energy.RootMeterChanged");
    QCOMPARE(notification.toMap().value("params").toMap().value("rootMeterThingId").toUuid(), meterThingId);

    // Make sure this is our root meter now
    response = injectAndWait("Energy.GetRootMeter");
    QCOMPARE(response.toMap().value("params").toMap().value("rootMeterThingId").toUuid(), meterThingId);
    notificationSpy.clear();

    // Add the charger
    QUuid evChargerId;
    if (addingCharger) {
        evChargerId = addCharger("ABC", 16);
        QVERIFY2(!evChargerId.isNull(), "Did not receive valid ThingId");
        if (notificationSpy.count() == 0) notificationSpy.wait();
        checkNotification(notificationSpy, "Integrations.ThingAdded");
    } else {
        evChargerId = QUuid::createUuid();
    }

    // Add the car
    QUuid assignedCarId;
    if (addingCar) {
        assignedCarId = addCar();
        QVERIFY2(!assignedCarId.isNull(), "Did not receive valid ThingId");
        if (notificationSpy.count() == 0) notificationSpy.wait();
        checkNotification(notificationSpy, "Integrations.ThingAdded");
    }

    if (!carValid)
        assignedCarId = QUuid::createUuid();

    // Set aquisition tolerance
    QVariantMap params;
    params.insert("acquisitionTolerance", acquisitionTolerance);
    response = injectAndWait("NymeaEnergy.SetAcquisitionTolerance", params);
    verifyEnergyError(response);
    notificationSpy.clear();

    // Set charging info with our charger and car
    QVariantMap chargingInfoMap;
    chargingInfoMap.insert("evChargerId", evChargerId);
    if (!assignedCarId.isNull())
        chargingInfoMap.insert("assignedCarId", assignedCarId);

    chargingInfoMap.insert("chargingMode", chargingMode);
    chargingInfoMap.insert("endDateTime", endDateTime.toMSecsSinceEpoch() / 1000);
    QVariantList repDaysList;
    foreach (int day, repeatDays) {
        repDaysList.append(day);
    }
    chargingInfoMap.insert("repeatDays", repDaysList);
    chargingInfoMap.insert("targetPercentage", targetPercentage);
    //qCDebug(dcTests()) << "Sending charging info:" << qUtf8Printable(QJsonDocument::fromVariant(chargingInfoMap).toJson(QJsonDocument::Indented));
    response = injectAndWait("NymeaEnergy.SetChargingInfo", QVariantMap({{"chargingInfo", chargingInfoMap}}));

    if (jsonSuccess) {
        QCOMPARE(response.toMap().value("status").toString(), QString("success"));
        verifyEnergyError(response, expectedError);

        // Get the charging infos
        params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
        response = injectAndWait("NymeaEnergy.GetChargingInfos", params);
        QVERIFY2(response.toMap().value("params").toMap().contains("chargingInfos"), "Did not get charginginfos.");
        QVariantList chargingInfos = response.toMap().value("params").toMap().value("chargingInfos").toList();
        qDebug() << chargingInfos;

        if (expectedError == EnergyManager::EnergyErrorNoError) {
            QCOMPARE(chargingInfos.count(), 1);
        }

    } else {
        QCOMPARE(response.toMap().value("status").toString(), QString("error"));
    }
}

void TestCharging::testEcoMode_data()
{
    // Houshold info
    QTest::addColumn<int>("phasePowerLimit");

    // ChargingInfo
    QTest::addColumn<double>("acquisitionTolerance");
    QTest::addColumn<int>("targetPercentage");
    QTest::addColumn<int>("hoursLeftToTargetTime");

    // Car information and states
    QTest::addColumn<int>("carBatteryLevel");
    QTest::addColumn<int>("carCapacity");
    QTest::addColumn<int>("carMinChargingCurrent");
    QTest::addColumn<int>("carPhaseCount");

    // Charger information and states
    QTest::addColumn<bool>("chargerConnected");
    QTest::addColumn<bool>("chargerPower");
    QTest::addColumn<QString>("chargerPhases");
    QTest::addColumn<int>("chargerMaxChargingCurrent");
    QTest::addColumn<int>("chargerMaxChargingCurrentMaxValue");

    // Current meter power states
    QTest::addColumn<QVariantMap>("meterPhasesPower");

    // Desired result on the evCharger
    QTest::addColumn<int>("expectedChargerMaxChargingCurrent");
    QTest::addColumn<bool>("expectedChargerPower");

    QTest::newRow("Default: 1 phase, p: 4kW, 16A, ON")
        << 32 // phase limit (A)

        << 0.5 // acquisitionTolerance
        << 100 // targetPercentage
        << 6 // hoursLeftToTargetTime

        << 80 // carBatteryLevel
        << 48 // carCapacity
        << 6  // carMinChargingCurrent
        << 1  // carPhaseCount

        << true // chargerConnected
        << false // chargerPower
        << "A" // chargerPhases
        << 6 // chargerMaxChargingCurrent
        << 16 //chargerMaxChargingCurrentMaxValue

        << QVariantMap({ {"A", 0000}, // W
                        {"B", 2000}, // W
                        {"C", -6000} }) // W

        << 16 // expectedChargerMaxChargingCurrent
        << true; // expectedChargerPower


    QTest::newRow("Default: 1 phase, p: 2kW, 7A, ON")
        << 16 // phase limit (A)

        << 0.5 // acquisitionTolerance
        << 100 // targetPercentage
        << 6 // hoursLeftToTargetTime

        << 80 // carBatteryLevel
        << 48 // carCapacity
        << 6  // carMinChargingCurrent
        << 1  // carPhaseCount

        << true // chargerConnected
        << false // chargerPower
        << "A" // chargerPhases
        << 6 // chargerMaxChargingCurrent
        << 16 //chargerMaxChargingCurrentMaxValue

        << QVariantMap({ {"A", 2000}, // W
                        {"B", -2000}, // W
                        {"C", -2000} }) // W

        << 7 // expectedChargerMaxChargingCurrent
        << true; // expectedChargerPower


    QTest::newRow("Default: 1 phase, p: -6kW, 13A, ON")
        << 16 // phase limit (A)

        << 0.5 // acquisitionTolerance
        << 100 // targetPercentage
        << 6 // hoursLeftToTargetTime

        << 80 // carBatteryLevel
        << 48 // carCapacity
        << 6  // carMinChargingCurrent
        << 1  // carPhaseCount

        << true // chargerConnected
        << false // chargerPower
        << "A" // chargerPhases
        << 6 // chargerMaxChargingCurrent
        << 32 //chargerMaxChargingCurrentMaxValue

        << QVariantMap({ {"A", -3000}, // W
                        {"B", 3000}, // W
                        {"C", -3000} }) // W

        << 13 // expectedChargerMaxChargingCurrent
        << true; // expectedChargerPower

    QTest::newRow("Default: 2 phase, p: -8kW, 17A, ON")
        << 32 // phase limit (A)

        << 0.5 // acquisitionTolerance
        << 100 // targetPercentage
        << 6 // hoursLeftToTargetTime

        << 80 // carBatteryLevel
        << 48 // carCapacity
        << 6  // carMinChargingCurrent
        << 2  // carPhaseCount

        << true // chargerConnected
        << false // chargerPower
        << "AB" // chargerPhases
        << 6 // chargerMaxChargingCurrent
        << 32 //chargerMaxChargingCurrentMaxValue

        << QVariantMap({ {"A", -4000}, // W
                        {"B", -5000}, // W
                        {"C", 1000} }) // W

        << 17 // expectedChargerMaxChargingCurrent
        << true; // expectedChargerPower

    QTest::newRow("Default: 3 phase, p: -3kW, 6A, ON")
        << 32 // phase limit (A)

        << 0.5 // acquisitionTolerance
        << 100 // targetPercentage
        << 6 // hoursLeftToTargetTime

        << 80 // carBatteryLevel
        << 48 // carCapacity
        << 6  // carMinChargingCurrent
        << 3  // carPhaseCount

        << true // chargerConnected
        << false // chargerPower
        << "ABC" // chargerPhases
        << 6 // chargerMaxChargingCurrent
        << 32 //chargerMaxChargingCurrentMaxValue

        << QVariantMap({ {"A", -1000}, // W
                        {"B", -1000}, // W
                        {"C", -1000} }) // W

        << 6 // expectedChargerMaxChargingCurrent
        << true; // expectedChargerPower
}

void TestCharging::testEcoMode()
{
    QFETCH(int, phasePowerLimit);

    QFETCH(double, acquisitionTolerance);
    QFETCH(int, targetPercentage);
    QFETCH(int, hoursLeftToTargetTime);

    QFETCH(int, carBatteryLevel);
    QFETCH(int, carCapacity);
    QFETCH(int, carMinChargingCurrent);
    QFETCH(int, carPhaseCount);

    QFETCH(bool, chargerConnected);
    QFETCH(bool, chargerPower);
    QFETCH(QString, chargerPhases);
    QFETCH(int, chargerMaxChargingCurrent);
    QFETCH(int, chargerMaxChargingCurrentMaxValue);

    QFETCH(QVariantMap, meterPhasesPower);

    QFETCH(int, expectedChargerMaxChargingCurrent);
    QFETCH(bool, expectedChargerPower);

    QVariantMap params;
    QVariant response, notification;
    QNetworkReply *reply = nullptr;
    QSignalSpy notificationSpy(m_mockTcpServer, &MockTcpServer::outgoingData);;

    // Set phase power limit
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    response = injectAndWait("NymeaEnergy.SetPhasePowerLimit", QVariantMap({{"phasePowerLimit", phasePowerLimit}}));
    QVERIFY(response.toMap().value("params").toMap().value("energyError").toString() == "EnergyErrorNoError");

    // Add mock meter
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    QUuid meterThingId = addMeter();
    QVERIFY2(!meterThingId.isNull(), "Did not receive valid ThingId");
    if (notificationSpy.count() == 0) notificationSpy.wait();
    notification = checkNotification(notificationSpy, "Energy.RootMeterChanged");
    QCOMPARE(notification.toMap().value("params").toMap().value("rootMeterThingId").toUuid(), meterThingId);

    // Make sure this is our root meter now
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    response = injectAndWait("Energy.GetRootMeter");
    QCOMPARE(response.toMap().value("params").toMap().value("rootMeterThingId").toUuid(), meterThingId);
    notificationSpy.clear();

    // Set the meter values first so we have the desired execution o charging info changed, and not an extra iteration with 0 W on root meter
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    reply = setMeterStates(meterPhasesPower);
    QSignalSpy setStatesReplySpy(reply, &QNetworkReply::finished);
    if (setStatesReplySpy.count() == 0) setStatesReplySpy.wait();
    QCOMPARE(reply->error(), QNetworkReply::NoError);

    // No evaluation, there is no meter configured yet

    // Add the charger
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    QUuid evChargerId = addCharger(chargerPhases, chargerMaxChargingCurrentMaxValue);
    QVERIFY2(!evChargerId.isNull(), "Did not receive valid ThingId");
    if (notificationSpy.count() == 0) notificationSpy.wait();
    checkNotification(notificationSpy, "Integrations.ThingAdded");

    // Add the car
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    QUuid assignedCarId = addCar();
    QVERIFY2(!assignedCarId.isNull(), "Did not receive valid ThingId");
    if (notificationSpy.count() == 0) notificationSpy.wait();
    checkNotification(notificationSpy, "Integrations.ThingAdded");

    // Set aquisition tolerance
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    params.insert("acquisitionTolerance", acquisitionTolerance);
    response = injectAndWait("NymeaEnergy.SetAcquisitionTolerance", params);
    verifyEnergyError(response);

    // Set states of car
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    reply = setCarStates(carBatteryLevel, carCapacity, carMinChargingCurrent, carPhaseCount);
    QSignalSpy setCarStatesReplySpy(reply, &QNetworkReply::finished);
    if (setCarStatesReplySpy.count() == 0) setCarStatesReplySpy.wait();
    QCOMPARE(reply->error(), QNetworkReply::NoError);

    // Set states of the charger

    uint effectivePhaseCount = qMin((uint)carPhaseCount, Electricity::getPhaseCount(Electricity::convertPhasesFromString(chargerPhases)));
    QString usedPhases;
    if (effectivePhaseCount >= 1)
        usedPhases.append("A");

    if (effectivePhaseCount >= 2)
        usedPhases.append("B");

    if (effectivePhaseCount >= 3)
        usedPhases.append("C");

    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    reply = setChargerStates(chargerConnected, chargerPower, true, usedPhases, chargerMaxChargingCurrent, chargerMaxChargingCurrentMaxValue);
    QSignalSpy setChargerStatesReplySpy(reply, &QNetworkReply::finished);
    if (setChargerStatesReplySpy.count() == 0) setChargerStatesReplySpy.wait();
    QCOMPARE(reply->error(), QNetworkReply::NoError);



    // Set charging info with our charger and car, this should trigger the evaluation
    QVariantMap chargingInfoMap;
    chargingInfoMap.insert("evChargerId", evChargerId);
    if (!assignedCarId.isNull())
        chargingInfoMap.insert("assignedCarId", assignedCarId);

    chargingInfoMap.insert("chargingMode", "ChargingModeEcoWithTargetTime");
    chargingInfoMap.insert("endDateTime", QDateTime::currentDateTime().addSecs(hoursLeftToTargetTime * 3600).toMSecsSinceEpoch() / 1000);
    chargingInfoMap.insert("targetPercentage", targetPercentage);

    response = injectAndWait("NymeaEnergy.SetChargingInfo", QVariantMap({{"chargingInfo", chargingInfoMap}}));
    verifyEnergyError(response);

    // Verify if the charger has been set correctly
    reply = getActionHistory(m_mockChargerDefaultPort);
    QSignalSpy actionHistoryReplySpy(reply, &QNetworkReply::finished);
    if (actionHistoryReplySpy.count() == 0) actionHistoryReplySpy.wait();
    QCOMPARE(reply->error(), QNetworkReply::NoError);
    QByteArray data = reply->readAll();
    QJsonDocument jsonDoc = QJsonDocument::fromJson(data);
    QVariantList actionHistory = jsonDoc.toVariant().toList();
    //qCDebug(dcTests()) << qUtf8Printable(jsonDoc.toJson(QJsonDocument::Indented));

    // Make sure we got the correct actions
    if (expectedChargerMaxChargingCurrent != 0)
        QVERIFY(verifyActionExecuted(actionHistory, "maxChargingCurrent"));

    QVERIFY(verifyActionExecuted(actionHistory, "power"));

    if (expectedChargerMaxChargingCurrent != 0)
        QCOMPARE(getLastValueFromExecutedAction(actionHistory, "maxChargingCurrent", "maxChargingCurrent"), QVariant(expectedChargerMaxChargingCurrent));

    QCOMPARE(getLastValueFromExecutedAction(actionHistory, "power", "power"), QVariant(expectedChargerPower));

    removeDevices();
}

void TestCharging::testEcoModePhaseSwitching_data()
{
    // Houshold info
    QTest::addColumn<int>("phasePowerLimit");

    // ChargingInfo
    QTest::addColumn<double>("acquisitionTolerance");
    QTest::addColumn<int>("targetPercentage");
    QTest::addColumn<int>("hoursLeftToTargetTime");

    // Car information and states
    QTest::addColumn<int>("carBatteryLevel");
    QTest::addColumn<int>("carCapacity");
    QTest::addColumn<int>("carMinChargingCurrent");
    QTest::addColumn<int>("carPhaseCount");

    // Charger information and states
    QTest::addColumn<bool>("chargerConnected");
    QTest::addColumn<bool>("chargerPower");
    QTest::addColumn<QString>("chargerPhases");
    QTest::addColumn<int>("chargerMaxChargingCurrent");
    QTest::addColumn<int>("chargerMaxChargingCurrentMaxValue");
    QTest::addColumn<int>("chargerDesiredPhaseCount");

    // Current meter power states
    QTest::addColumn<QVariantMap>("meterPhasesPower");

    // Desired result on the evCharger
    QTest::addColumn<int>("expectedChargerMaxChargingCurrent");
    QTest::addColumn<bool>("expectedChargerPower");
    QTest::addColumn<int>("expectedChargerDesiredPhaseCount");


    QTest::newRow("1 phase, p: 3,8kW, 10A, ON, No phase switch, no aquisition")
        << 32 // phase limit (A)

        << 1.0 // acquisitionTolerance
        << 100 // targetPercentage
        << 6 // hoursLeftToTargetTime

        << 80 // carBatteryLevel
        << 48 // carCapacity
        << 6  // carMinChargingCurrent
        << 3  // carPhaseCount

        << true   // chargerConnected
        << true   // chargerPower
        << "ABC"  // chargerPhases
        << 10     // chargerMaxChargingCurrent
        << 16     // chargerMaxChargingCurrentMaxValue
        << 1      // chargerDesiredPhaseCount

        // meterPhasesPower [W]
        << QVariantMap({ {"A", 2300},
                        {"B", -1900},
                        {"C", -2000} })

        << 16 // expectedChargerMaxChargingCurrent
        << true // expectedChargerPower
        << 1; // expectedChargerDesiredPhaseCount


    QTest::newRow("1 phase, p: 3,8kW, 6A, ON, Switch 3 phase with aquisition")
        << 32 // phase limit (A)

        << 0.5 // acquisitionTolerance
        << 100 // targetPercentage
        << 6 // hoursLeftToTargetTime

        << 80 // carBatteryLevel
        << 48 // carCapacity
        << 6  // carMinChargingCurrent
        << 3  // carPhaseCount

        << true   // chargerConnected
        << false  // chargerPower
        << "ABC"    // chargerPhases
        << 6      // chargerMaxChargingCurrent
        << 16     // chargerMaxChargingCurrentMaxValue
        << 1      // chargerDesiredPhaseCount

        // meterPhasesPower [W]
        << QVariantMap({ {"A", 0000},
                        {"B", 2000},
                        {"C", -5800} })

        << 6 // expectedChargerMaxChargingCurrent
        << true // expectedChargerPower
        << 3; // expectedChargerDesiredPhaseCount


    QTest::newRow("3 phase, p: 4kW, 6A, ON, Switch 1 -> 3")
        << 32 // phase limit (A)

        << 0.5 // acquisitionTolerance
        << 100 // targetPercentage
        << 6 // hoursLeftToTargetTime

        << 80 // carBatteryLevel
        << 48 // carCapacity
        << 6  // carMinChargingCurrent
        << 3  // carPhaseCount

        << true   // chargerConnected
        << false  // chargerPower
        << "ABC"  // chargerPhases
        << 6      // chargerMaxChargingCurrent
        << 16     // chargerMaxChargingCurrentMaxValue
        << 1      // chargerDesiredPhaseCount

        // meterPhasesPower [W]
        << QVariantMap({ {"A", 0000},
                        {"B", 2000},
                        {"C", -6000} })

        << 6 // expectedChargerMaxChargingCurrent
        << true // expectedChargerPower
        << 3; // expectedChargerDesiredPhaseCount

    QTest::newRow("3 phase, p: 800W, 6A, ON, Switch 3 -> 1")
        << 32 // phase limit (A)

        << 0.5 // acquisitionTolerance
        << 100 // targetPercentage
        << 6 // hoursLeftToTargetTime

        << 80 // carBatteryLevel
        << 48 // carCapacity
        << 6  // carMinChargingCurrent
        << 3  // carPhaseCount

        << true   // chargerConnected
        << false  // chargerPower
        << "ABC"  // chargerPhases
        << 10     // chargerMaxChargingCurrent
        << 16     // chargerMaxChargingCurrentMaxValue
        << 3      // chargerDesiredPhaseCount

        // meterPhasesPower [W]
        << QVariantMap({ {"A", 0000},
                        {"B", 900},
                        {"C", -1700} })

        << 6 // expectedChargerMaxChargingCurrent
        << true // expectedChargerPower
        << 1; // expectedChargerDesiredPhaseCount

    QTest::newRow("3 phase charger, p: 1,38kW, 6A, ON, Start 1-phase")
        << 32 // phase limit (A)

        << 0.01 // acquisitionTolerance
        << 100 // targetPercentage
        << 6 // hoursLeftToTargetTime

        << 80 // carBatteryLevel
        << 48 // carCapacity
        << 6  // carMinChargingCurrent
        << 3  // carPhaseCount

        << true   // chargerConnected
        << false  // chargerPower
        << "ABC"  // chargerPhases
        << 6      // chargerMaxChargingCurrent
        << 16     // chargerMaxChargingCurrentMaxValue
        << 3      // chargerDesiredPhaseCount

        // meterPhasesPower [W]
        << QVariantMap({ {"A", -1400},
                        {"B", 2000},
                        {"C", -2000} })

        << 6 // expectedChargerMaxChargingCurrent
        << true // expectedChargerPower
        << 1; // expectedChargerDesiredPhaseCount

    QTest::newRow("3 phase charger, p: 200W, 6A, ON, Start 1-phase")
        << 32 // phase limit (A)

        << 0.01 // acquisitionTolerance
        << 100 // targetPercentage
        << 6 // hoursLeftToTargetTime

        << 80 // carBatteryLevel
        << 48 // carCapacity
        << 6  // carMinChargingCurrent
        << 3  // carPhaseCount

        << true   // chargerConnected
        << false  // chargerPower
        << "ABC"  // chargerPhases
        << 6      // chargerMaxChargingCurrent
        << 16     // chargerMaxChargingCurrentMaxValue
        << 3      // chargerDesiredPhaseCount

        // meterPhasesPower [W]
        << QVariantMap({ {"A", -200},
                        {"B", 2000},
                        {"C", -2000} })

        << 6 // expectedChargerMaxChargingCurrent
        << true // expectedChargerPower
        << 1; // expectedChargerDesiredPhaseCount

    QTest::newRow("3 phase charger, p: 3,7kW, 6A, ON, Start 3-phase")
        << 32 // phase limit (A)

        << 0.01 // acquisitionTolerance
        << 100 // targetPercentage
        << 6 // hoursLeftToTargetTime

        << 80 // carBatteryLevel
        << 48 // carCapacity
        << 6  // carMinChargingCurrent
        << 3  // carPhaseCount

        << true   // chargerConnected
        << false  // chargerPower
        << "ABC"  // chargerPhases
        << 6      // chargerMaxChargingCurrent
        << 16     // chargerMaxChargingCurrentMaxValue
        << 3      // chargerDesiredPhaseCount

        // meterPhasesPower [W]
        << QVariantMap({ {"A", -3700},
                        {"B", 2000},
                        {"C", -2000} })

        << 6 // expectedChargerMaxChargingCurrent
        << true // expectedChargerPower
        << 3; // expectedChargerDesiredPhaseCount
}

void TestCharging::testEcoModePhaseSwitching()
{
    QFETCH(int, phasePowerLimit);

    QFETCH(double, acquisitionTolerance);
    QFETCH(int, targetPercentage);
    QFETCH(int, hoursLeftToTargetTime);

    QFETCH(int, carBatteryLevel);
    QFETCH(int, carCapacity);
    QFETCH(int, carMinChargingCurrent);
    QFETCH(int, carPhaseCount);

    QFETCH(bool, chargerConnected);
    QFETCH(bool, chargerPower);
    QFETCH(QString, chargerPhases);
    QFETCH(int, chargerMaxChargingCurrent);
    QFETCH(int, chargerMaxChargingCurrentMaxValue);
    QFETCH(int, chargerDesiredPhaseCount);

    QFETCH(QVariantMap, meterPhasesPower);

    QFETCH(int, expectedChargerMaxChargingCurrent);
    QFETCH(bool, expectedChargerPower);
    QFETCH(int, expectedChargerDesiredPhaseCount);

    Q_UNUSED(expectedChargerDesiredPhaseCount)

    QVariantMap params;
    QVariant response, notification;
    QNetworkReply *reply = nullptr;
    QSignalSpy notificationSpy(m_mockTcpServer, &MockTcpServer::outgoingData);;

    // Set phase power limit
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    response = injectAndWait("NymeaEnergy.SetPhasePowerLimit", QVariantMap({{"phasePowerLimit", phasePowerLimit}}));
    QVERIFY(response.toMap().value("params").toMap().value("energyError").toString() == "EnergyErrorNoError");

    // Add mock meter
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    QUuid meterThingId = addMeter();
    QVERIFY2(!meterThingId.isNull(), "Did not receive valid ThingId");
    if (notificationSpy.count() == 0) notificationSpy.wait();
    notification = checkNotification(notificationSpy, "Energy.RootMeterChanged");
    QCOMPARE(notification.toMap().value("params").toMap().value("rootMeterThingId").toUuid(), meterThingId);

    // Make sure this is our root meter now
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    response = injectAndWait("Energy.GetRootMeter");
    QCOMPARE(response.toMap().value("params").toMap().value("rootMeterThingId").toUuid(), meterThingId);
    notificationSpy.clear();

    // Set the meter values first so we have the desired execution o charging info changed, and not an extra iteration with 0 W on root meter
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    reply = setMeterStates(meterPhasesPower);
    QSignalSpy setStatesReplySpy(reply, &QNetworkReply::finished);
    if (setStatesReplySpy.count() == 0) setStatesReplySpy.wait();
    QCOMPARE(reply->error(), QNetworkReply::NoError);

    // No evaluation, there is no meter configured yet

    // Add the charger
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    QUuid evChargerId = addChargerWithPhaseCountSwitching(chargerPhases, chargerMaxChargingCurrentMaxValue);
    QVERIFY2(!evChargerId.isNull(), "Did not receive valid ThingId");
    if (notificationSpy.count() == 0) notificationSpy.wait();
    checkNotification(notificationSpy, "Integrations.ThingAdded");

    // Add the car
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    QUuid assignedCarId = addCar();
    QVERIFY2(!assignedCarId.isNull(), "Did not receive valid ThingId");
    if (notificationSpy.count() == 0) notificationSpy.wait();
    checkNotification(notificationSpy, "Integrations.ThingAdded");

    // Set aquisition tolerance
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    params.insert("acquisitionTolerance", acquisitionTolerance);
    response = injectAndWait("NymeaEnergy.SetAcquisitionTolerance", params);
    verifyEnergyError(response);

    // Set states of car
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    reply = setCarStates(carBatteryLevel, carCapacity, carMinChargingCurrent, carPhaseCount);
    QSignalSpy setCarStatesReplySpy(reply, &QNetworkReply::finished);
    if (setCarStatesReplySpy.count() == 0) setCarStatesReplySpy.wait();
    QCOMPARE(reply->error(), QNetworkReply::NoError);

    // Set states of the charger

    uint effectivePhaseCount = qMin((uint)carPhaseCount, Electricity::getPhaseCount(Electricity::convertPhasesFromString(chargerPhases)));
    QString usedPhases;
    if (effectivePhaseCount >= 1)
        usedPhases.append("A");

    if (effectivePhaseCount >= 2)
        usedPhases.append("B");

    if (effectivePhaseCount >= 3)
        usedPhases.append("C");

    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    reply = setChargerWithPhaseCountSwitchingStates(chargerConnected, chargerPower, true, usedPhases, chargerMaxChargingCurrent, chargerMaxChargingCurrentMaxValue, chargerDesiredPhaseCount);
    QSignalSpy setChargerStatesReplySpy(reply, &QNetworkReply::finished);
    if (setChargerStatesReplySpy.count() == 0) setChargerStatesReplySpy.wait();
    QCOMPARE(reply->error(), QNetworkReply::NoError);

    // Set charging info with our charger and car, this should trigger the evaluation
    QVariantMap chargingInfoMap;
    chargingInfoMap.insert("evChargerId", evChargerId);
    if (!assignedCarId.isNull())
        chargingInfoMap.insert("assignedCarId", assignedCarId);

    chargingInfoMap.insert("chargingMode", "ChargingModeEcoWithTargetTime");
    chargingInfoMap.insert("endDateTime", QDateTime::currentDateTime().addSecs(hoursLeftToTargetTime * 3600).toMSecsSinceEpoch() / 1000);
    chargingInfoMap.insert("targetPercentage", targetPercentage);

    // Trigger evaluation

    response = injectAndWait("NymeaEnergy.SetChargingInfo", QVariantMap({{"chargingInfo", chargingInfoMap}}));
    verifyEnergyError(response);

    // Verify if the charger has been set correctly
    reply = getActionHistory(m_mockChargerWithPhaseCountSwitchingDefaultPort);
    QSignalSpy actionHistoryReplySpy(reply, &QNetworkReply::finished);
    if (actionHistoryReplySpy.count() == 0) actionHistoryReplySpy.wait();
    QCOMPARE(reply->error(), QNetworkReply::NoError);
    QByteArray data = reply->readAll();
    QJsonDocument jsonDoc = QJsonDocument::fromJson(data);
    QVariantList actionHistory = jsonDoc.toVariant().toList();
    //qCDebug(dcTests()) << qUtf8Printable(jsonDoc.toJson(QJsonDocument::Indented));

    // Make sure we got the correct actions
    if (expectedChargerMaxChargingCurrent != 0)
        QVERIFY(verifyActionExecuted(actionHistory, "maxChargingCurrent"));

    QVERIFY(verifyActionExecuted(actionHistory, "power"));

    if (expectedChargerMaxChargingCurrent != 0)
        QCOMPARE(getLastValueFromExecutedAction(actionHistory, "maxChargingCurrent", "maxChargingCurrent"), QVariant(expectedChargerMaxChargingCurrent));

    if (expectedChargerDesiredPhaseCount != chargerDesiredPhaseCount)
        QCOMPARE(getLastValueFromExecutedAction(actionHistory, "desiredPhaseCount", "desiredPhaseCount"), QVariant(expectedChargerDesiredPhaseCount));

    QCOMPARE(getLastValueFromExecutedAction(actionHistory, "power", "power"), QVariant(expectedChargerPower));

    removeDevices();
}

void TestCharging::testBatteryLevelConsideration_data()
{
    // Houshold info
    QTest::addColumn<int>("phasePowerLimit");

    // ChargingInfo
    QTest::addColumn<double>("acquisitionTolerance");
    QTest::addColumn<double>("batteryLevelConsideration");

    // Car information and states
    QTest::addColumn<int>("carBatteryLevel");
    QTest::addColumn<int>("carCapacity");
    QTest::addColumn<int>("carMinChargingCurrent");
    QTest::addColumn<int>("carPhaseCount");

    // Charger information and states
    QTest::addColumn<bool>("chargerConnected");
    QTest::addColumn<bool>("chargerPower");
    QTest::addColumn<QString>("chargerPhases");
    QTest::addColumn<int>("chargerMaxChargingCurrent");
    QTest::addColumn<int>("chargerMaxChargingCurrentMaxValue");
    QTest::addColumn<int>("chargerDesiredPhaseCount");

    // Current meter power states
    QTest::addColumn<QVariantMap>("meterPhasesPower");

    // Current energy storage states
    QTest::addColumn<double>("energyStorageBatteryLevel");
    QTest::addColumn<double>("energyStorageCurrentPower");

    // Desired result on the evCharger
    QTest::addColumn<int>("expectedChargerMaxChargingCurrent");  // 0 for Unchanged / not executed
    QTest::addColumn<bool>("expectedChargerPower");
    QTest::addColumn<int>("expectedChargerDesiredPhaseCount");

    QTest::newRow("Default: 1 phase, battery: 80% 3,6kW -> 16A, ON, 1P")
        << 32 // phase limit (A)

        << 0.5 // acquisitionTolerance
        << 0.8 // batteryLevelConsideration

        << 80 // carBatteryLevel
        << 48 // carCapacity
        << 6  // carMinChargingCurrent
        << 1  // carPhaseCount

        << true   // chargerConnected
        << false  // chargerPower
        << "A"    // chargerPhases
        << 6      // chargerMaxChargingCurrent
        << 16     // chargerMaxChargingCurrentMaxValue
        << 1      // chargerDesiredPhaseCount

        // meterPhasesPower [W]
        << QVariantMap({ {"A", 0},
                        {"B", 0 },
                        {"C", 0} })

        << 80.0 // energyStorageBatteryLevel
        << 3680.0 // energyStorageCurrentPower

        << 16 // expectedChargerMaxChargingCurrent
        << true // expectedChargerPower
        << 1; // expectedChargerDesiredPhaseCount


    QTest::newRow("Default: 1 phase, battery: 79% 3,6kW -> 6A, OFF, 1P")
        << 32 // phase limit (A)

        << 0.5 // acquisitionTolerance
        << 0.8 // batteryLevelConsideration

        << 80 // carBatteryLevel
        << 48 // carCapacity
        << 6  // carMinChargingCurrent
        << 1  // carPhaseCount

        << true   // chargerConnected
        << false  // chargerPower
        << "A"    // chargerPhases
        << 6      // chargerMaxChargingCurrent
        << 16     // chargerMaxChargingCurrentMaxValue
        << 1      // chargerDesiredPhaseCount

        // meterPhasesPower [W]
        << QVariantMap({ {"A", 0},
                        {"B", 0 },
                        {"C", 0} })

        << 79.0 // energyStorageBatteryLevel
        << 3680.0 // energyStorageCurrentPower

        << 0 // expectedChargerMaxChargingCurrent
        << false // expectedChargerPower
        << 1; // expectedChargerDesiredPhaseCount


    QTest::newRow("Default: 1 phase, battery: 81% -600W -> 6A, OFF, 1P")
        << 32 // phase limit (A)

        << 0.5 // acquisitionTolerance
        << 0.8 // batteryLevelConsideration

        << 80 // carBatteryLevel
        << 48 // carCapacity
        << 6  // carMinChargingCurrent
        << 1  // carPhaseCount

        << true   // chargerConnected
        << false  // chargerPower
        << "A"    // chargerPhases
        << 6      // chargerMaxChargingCurrent
        << 16     // chargerMaxChargingCurrentMaxValue
        << 1      // chargerDesiredPhaseCount

        // meterPhasesPower [W]
        << QVariantMap({ {"A", -600},
                        {"B", 0 },
                        {"C", 0} })

        << 81.0 // energyStorageBatteryLevel
        << -600.0 // energyStorageCurrentPower

        << 0 // expectedChargerMaxChargingCurrent
        << false // expectedChargerPower
        << 1; // expectedChargerDesiredPhaseCount


    QTest::newRow("Default: 1 phase, battery: 81% -600W -> 6A, OFF, 1P")
        << 32 // phase limit (A)

        << 0.5 // acquisitionTolerance
        << 0.8 // batteryLevelConsideration

        << 80 // carBatteryLevel
        << 48 // carCapacity
        << 6  // carMinChargingCurrent
        << 1  // carPhaseCount

        << true   // chargerConnected
        << false  // chargerPower
        << "A"    // chargerPhases
        << 6      // chargerMaxChargingCurrent
        << 16     // chargerMaxChargingCurrentMaxValue
        << 1      // chargerDesiredPhaseCount

        // meterPhasesPower [W]
        << QVariantMap({ {"A", -600},
                        {"B", 0 },
                        {"C", 0} })

        << 81.5 // energyStorageBatteryLevel
        << 100.0 // energyStorageCurrentPower

        << 6 // expectedChargerMaxChargingCurrent
        << true // expectedChargerPower
        << 1; // expectedChargerDesiredPhaseCount


}

void TestCharging::testBatteryLevelConsideration()
{
    QFETCH(int, phasePowerLimit);

    QFETCH(double, acquisitionTolerance);
    QFETCH(double, batteryLevelConsideration);

    QFETCH(int, carBatteryLevel);
    QFETCH(int, carCapacity);
    QFETCH(int, carMinChargingCurrent);
    QFETCH(int, carPhaseCount);

    QFETCH(bool, chargerConnected);
    QFETCH(bool, chargerPower);
    QFETCH(QString, chargerPhases);
    QFETCH(int, chargerMaxChargingCurrent);
    QFETCH(int, chargerMaxChargingCurrentMaxValue);
    QFETCH(int, chargerDesiredPhaseCount);

    QFETCH(QVariantMap, meterPhasesPower);

    QFETCH(double, energyStorageBatteryLevel);
    QFETCH(double, energyStorageCurrentPower);

    QFETCH(int, expectedChargerMaxChargingCurrent); // 0 for Unchanged / not executed
    QFETCH(bool, expectedChargerPower);
    QFETCH(int, expectedChargerDesiredPhaseCount);

    Q_UNUSED(expectedChargerDesiredPhaseCount)

    QVariantMap params;
    QVariant response, notification;
    QNetworkReply *reply = nullptr;
    QSignalSpy notificationSpy(m_mockTcpServer, &MockTcpServer::outgoingData);;

    // Set phase power limit
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    response = injectAndWait("NymeaEnergy.SetPhasePowerLimit", QVariantMap({{"phasePowerLimit", phasePowerLimit}}));
    QVERIFY(response.toMap().value("params").toMap().value("energyError").toString() == "EnergyErrorNoError");

    // Add mock meter
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    QUuid meterThingId = addMeter();
    QVERIFY2(!meterThingId.isNull(), "Did not receive valid ThingId");
    if (notificationSpy.count() == 0) notificationSpy.wait();
    notification = checkNotification(notificationSpy, "Energy.RootMeterChanged");
    QCOMPARE(notification.toMap().value("params").toMap().value("rootMeterThingId").toUuid(), meterThingId);

    // Make sure this is our root meter now
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    response = injectAndWait("Energy.GetRootMeter");
    QCOMPARE(response.toMap().value("params").toMap().value("rootMeterThingId").toUuid(), meterThingId);
    notificationSpy.clear();


    // Set the meter values first so we have the desired execution o charging info changed, and not an extra iteration with 0 W on root meter
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    reply = setMeterStates(meterPhasesPower);
    QSignalSpy setStatesReplySpy(reply, &QNetworkReply::finished);
    if (setStatesReplySpy.count() == 0) setStatesReplySpy.wait();
    QCOMPARE(reply->error(), QNetworkReply::NoError);

    // No evaluation, there is no meter configured yet

    // Add the charger
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    QUuid evChargerId = addChargerWithPhaseCountSwitching(chargerPhases, chargerMaxChargingCurrentMaxValue);
    QVERIFY2(!evChargerId.isNull(), "Did not receive valid ThingId");
    if (notificationSpy.count() == 0) notificationSpy.wait();
    checkNotification(notificationSpy, "Integrations.ThingAdded");

    // Add the car
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    QUuid assignedCarId = addCar();
    QVERIFY2(!assignedCarId.isNull(), "Did not receive valid ThingId");
    if (notificationSpy.count() == 0) notificationSpy.wait();
    checkNotification(notificationSpy, "Integrations.ThingAdded");

    // Add energy storage
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    QUuid energyStorageId = addEnergyStorage();
    QVERIFY2(!energyStorageId.isNull(), "Did not receive valid ThingId");
    if (notificationSpy.count() == 0) notificationSpy.wait();
    checkNotification(notificationSpy, "Integrations.ThingAdded");

    // Set aquisition tolerance
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    params.insert("acquisitionTolerance", acquisitionTolerance);
    response = injectAndWait("NymeaEnergy.SetAcquisitionTolerance", params);
    verifyEnergyError(response);

    // Set battery level consideration
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    params.insert("batteryLevelConsideration", batteryLevelConsideration);
    response = injectAndWait("NymeaEnergy.SetBatteryLevelConsideration", params);
    verifyEnergyError(response);

    // Set states of car
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    reply = setCarStates(carBatteryLevel, carCapacity, carMinChargingCurrent, carPhaseCount);
    QSignalSpy setCarStatesReplySpy(reply, &QNetworkReply::finished);
    if (setCarStatesReplySpy.count() == 0) setCarStatesReplySpy.wait();
    QCOMPARE(reply->error(), QNetworkReply::NoError);

    // Set states of the charger

    uint effectivePhaseCount = qMin((uint)carPhaseCount, Electricity::getPhaseCount(Electricity::convertPhasesFromString(chargerPhases)));
    QString usedPhases;
    if (effectivePhaseCount >= 1)
        usedPhases.append("A");

    if (effectivePhaseCount >= 2)
        usedPhases.append("B");

    if (effectivePhaseCount >= 3)
        usedPhases.append("C");

    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    reply = setChargerWithPhaseCountSwitchingStates(chargerConnected, chargerPower, true, usedPhases, chargerMaxChargingCurrent, chargerMaxChargingCurrentMaxValue, chargerDesiredPhaseCount);
    QSignalSpy setChargerStatesReplySpy(reply, &QNetworkReply::finished);
    if (setChargerStatesReplySpy.count() == 0) setChargerStatesReplySpy.wait();
    QCOMPARE(reply->error(), QNetworkReply::NoError);


    // Set energy storage
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    reply = setEnergyStorageStates(energyStorageBatteryLevel, energyStorageCurrentPower);
    QSignalSpy setEnergyStorageStatesReplySpy(reply, &QNetworkReply::finished);
    if (setEnergyStorageStatesReplySpy.count() == 0) setEnergyStorageStatesReplySpy.wait();
    QCOMPARE(reply->error(), QNetworkReply::NoError);


    // Set charging info with our charger and car, this should trigger the evaluation
    QVariantMap chargingInfoMap;
    chargingInfoMap.insert("evChargerId", evChargerId);
    if (!assignedCarId.isNull())
        chargingInfoMap.insert("assignedCarId", assignedCarId);

    chargingInfoMap.insert("chargingMode", "ChargingModeEco");

    response = injectAndWait("NymeaEnergy.SetChargingInfo", QVariantMap({{"chargingInfo", chargingInfoMap}}));
    verifyEnergyError(response);


    // Verify if the charger has been set correctly
    reply = getActionHistory(m_mockChargerWithPhaseCountSwitchingDefaultPort);
    QSignalSpy actionHistoryReplySpy(reply, &QNetworkReply::finished);
    if (actionHistoryReplySpy.count() == 0) actionHistoryReplySpy.wait();
    QCOMPARE(reply->error(), QNetworkReply::NoError);
    QByteArray data = reply->readAll();
    QJsonDocument jsonDoc = QJsonDocument::fromJson(data);
    QVariantList actionHistory = jsonDoc.toVariant().toList();
    //qCDebug(dcTests()) << qUtf8Printable(jsonDoc.toJson(QJsonDocument::Indented));

    // Make sure we got the correct actions
    if (expectedChargerMaxChargingCurrent != 0)
        QVERIFY(verifyActionExecuted(actionHistory, "maxChargingCurrent"));

    QVERIFY(verifyActionExecuted(actionHistory, "power"));

    if (expectedChargerMaxChargingCurrent != 0)
        QCOMPARE(getLastValueFromExecutedAction(actionHistory, "maxChargingCurrent", "maxChargingCurrent"), QVariant(expectedChargerMaxChargingCurrent));

    QCOMPARE(getLastValueFromExecutedAction(actionHistory, "power", "power"), QVariant(expectedChargerPower));

    removeDevices();
}

void TestCharging::batteryLevelConsiderationStopCharging_data()
{
    // Houshold info
    QTest::addColumn<int>("phasePowerLimit");

    // ChargingConfiguration
    QTest::addColumn<double>("acquisitionTolerance");
    QTest::addColumn<double>("batteryLevelConsideration");

    // Car information and states
    QTest::addColumn<int>("carBatteryLevel");
    QTest::addColumn<int>("carCapacity");
    QTest::addColumn<int>("carMinChargingCurrent");
    QTest::addColumn<int>("carPhaseCount");

    // Charger information and states
    QTest::addColumn<bool>("chargerConnected");
    QTest::addColumn<bool>("chargerPower");
    QTest::addColumn<QString>("chargerPhases");
    QTest::addColumn<int>("chargerMaxChargingCurrent");
    QTest::addColumn<int>("chargerMaxChargingCurrentMaxValue");
    QTest::addColumn<int>("chargerDesiredPhaseCount");

    // Current meter power states
    QTest::addColumn<QVariantMap>("meterPhasesPower");

    // Current energy storage states
    QTest::addColumn<double>("energyStorageBatteryLevel");
    QTest::addColumn<double>("energyStorageCurrentPower");

    QTest::newRow("Default:")
        << 32 // phase limit (A)

        << 0.5 // acquisitionTolerance
        << 0.8 // batteryLevelConsideration

        << 80 // carBatteryLevel
        << 48 // carCapacity
        << 6  // carMinChargingCurrent
        << 1  // carPhaseCount

        << true  // chargerConnected
        << true  // chargerPower
        << "ABC" // chargerPhases
        << 6     // chargerMaxChargingCurrent
        << 16    // chargerMaxChargingCurrentMaxValue
        << 1     // chargerDesiredPhaseCount

        // meterPhasesPower [W]
        << QVariantMap({ {"A", 0.0},
                        {"B", 0 },
                        {"C", 0} })

        << 20.0 // energyStorageBatteryLevel
        << 0.0; // energyStorageCurrentPower

}

void TestCharging::batteryLevelConsiderationStopCharging()
{
    QFETCH(int, phasePowerLimit);

    QFETCH(double, acquisitionTolerance);
    QFETCH(double, batteryLevelConsideration);

    QFETCH(int, carBatteryLevel);
    QFETCH(int, carCapacity);
    QFETCH(int, carMinChargingCurrent);
    QFETCH(int, carPhaseCount);

    QFETCH(bool, chargerConnected);
    QFETCH(bool, chargerPower);
    QFETCH(QString, chargerPhases);
    QFETCH(int, chargerMaxChargingCurrent);
    QFETCH(int, chargerMaxChargingCurrentMaxValue);
    QFETCH(int, chargerDesiredPhaseCount);

    QFETCH(QVariantMap, meterPhasesPower);

    QFETCH(double, energyStorageBatteryLevel);
    QFETCH(double, energyStorageCurrentPower);


    QVariantMap params;
    QVariant response, notification;
    QNetworkReply *reply = nullptr;
    QSignalSpy notificationSpy(m_mockTcpServer, &MockTcpServer::outgoingData);;

    // Set phase power limit
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    response = injectAndWait("NymeaEnergy.SetPhasePowerLimit", QVariantMap({{"phasePowerLimit", phasePowerLimit}}));
    QVERIFY(response.toMap().value("params").toMap().value("energyError").toString() == "EnergyErrorNoError");

    // Add mock meter
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    QUuid meterThingId = addMeter();
    QVERIFY2(!meterThingId.isNull(), "Did not receive valid ThingId");
    if (notificationSpy.count() == 0) notificationSpy.wait();
    notification = checkNotification(notificationSpy, "Energy.RootMeterChanged");
    QCOMPARE(notification.toMap().value("params").toMap().value("rootMeterThingId").toUuid(), meterThingId);

    // Make sure this is our root meter now
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    response = injectAndWait("Energy.GetRootMeter");
    QCOMPARE(response.toMap().value("params").toMap().value("rootMeterThingId").toUuid(), meterThingId);
    notificationSpy.clear();


    // Set the meter values first so we have the desired execution o charging configuration changed, and not an extra iteration with 0 W on root meter
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    reply = setMeterStates(meterPhasesPower);
    QSignalSpy setStatesReplySpy(reply, &QNetworkReply::finished);
    if (setStatesReplySpy.count() == 0) setStatesReplySpy.wait();
    QCOMPARE(reply->error(), QNetworkReply::NoError);

    // No evaluation, there is no meter configured yet

    // Add the charger
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    QUuid evChargerId = addChargerWithPhaseCountSwitching(chargerPhases, chargerMaxChargingCurrentMaxValue);
    QVERIFY2(!evChargerId.isNull(), "Did not receive valid ThingId");
    if (notificationSpy.count() == 0) notificationSpy.wait();
    checkNotification(notificationSpy, "Integrations.ThingAdded");

    // Add the car
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    QUuid assignedCarId = addCar();
    QVERIFY2(!assignedCarId.isNull(), "Did not receive valid ThingId");
    if (notificationSpy.count() == 0) notificationSpy.wait();
    checkNotification(notificationSpy, "Integrations.ThingAdded");

    // Add energy storage
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    QUuid energyStorageId = addEnergyStorage();
    QVERIFY2(!energyStorageId.isNull(), "Did not receive valid ThingId");
    if (notificationSpy.count() == 0) notificationSpy.wait();
    checkNotification(notificationSpy, "Integrations.ThingAdded");

    // Set aquisition tolerance
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    params.insert("acquisitionTolerance", acquisitionTolerance);
    response = injectAndWait("NymeaEnergy.SetAcquisitionTolerance", params);
    verifyEnergyError(response);

    // Set battery level consideration
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    params.insert("batteryLevelConsideration", batteryLevelConsideration);
    response = injectAndWait("NymeaEnergy.SetBatteryLevelConsideration", params);
    verifyEnergyError(response);

    // Set states of car
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    reply = setCarStates(carBatteryLevel, carCapacity, carMinChargingCurrent, carPhaseCount);
    QSignalSpy setCarStatesReplySpy(reply, &QNetworkReply::finished);
    if (setCarStatesReplySpy.count() == 0) setCarStatesReplySpy.wait();
    QCOMPARE(reply->error(), QNetworkReply::NoError);


    // Set states of the charger
    uint effectivePhaseCount = qMin((uint)carPhaseCount, Electricity::getPhaseCount(Electricity::convertPhasesFromString(chargerPhases)));
    QString usedPhases;
    if (effectivePhaseCount >= 1)
        usedPhases.append("A");

    if (effectivePhaseCount >= 2)
        usedPhases.append("B");

    if (effectivePhaseCount >= 3)
        usedPhases.append("C");

    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    reply = setChargerWithPhaseCountSwitchingStates(chargerConnected, chargerPower, true, usedPhases, chargerMaxChargingCurrent, chargerMaxChargingCurrentMaxValue, chargerDesiredPhaseCount);
    QSignalSpy setChargerStatesReplySpy(reply, &QNetworkReply::finished);
    if (setChargerStatesReplySpy.count() == 0) setChargerStatesReplySpy.wait();
    QCOMPARE(reply->error(), QNetworkReply::NoError);

    // Set energy storage
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    reply = setEnergyStorageStates(energyStorageBatteryLevel, energyStorageCurrentPower);
    QSignalSpy setEnergyStorageStatesReplySpy(reply, &QNetworkReply::finished);
    if (setEnergyStorageStatesReplySpy.count() == 0) setEnergyStorageStatesReplySpy.wait();
    QCOMPARE(reply->error(), QNetworkReply::NoError);

    // Make sure we are in normal mode
    QVariantMap chargingInfoMap;
    chargingInfoMap.insert("evChargerId", evChargerId);
    chargingInfoMap.insert("assignedCarId", assignedCarId);
    chargingInfoMap.insert("chargingMode", "ChargingModeNormal");
    response = injectAndWait("NymeaEnergy.SetChargingInfo", QVariantMap({{"chargingInfo", chargingInfoMap}}));
    verifyEnergyError(response);


    // Set charging info with our charger and car, this should trigger the evaluation
    chargingInfoMap.clear();
    chargingInfoMap.insert("evChargerId", evChargerId);
    chargingInfoMap.insert("assignedCarId", assignedCarId);
    chargingInfoMap.insert("chargingMode", "ChargingModeEco");

    response = injectAndWait("NymeaEnergy.SetChargingInfo", QVariantMap({{"chargingInfo", chargingInfoMap}}));
    verifyEnergyError(response);

    // Verify if the charger has been set correctly
    reply = getActionHistory(m_mockChargerWithPhaseCountSwitchingDefaultPort);
    QSignalSpy actionHistoryReplySpy(reply, &QNetworkReply::finished);
    if (actionHistoryReplySpy.count() == 0) actionHistoryReplySpy.wait();
    QCOMPARE(reply->error(), QNetworkReply::NoError);
    QByteArray data = reply->readAll();
    QJsonDocument jsonDoc = QJsonDocument::fromJson(data);
    QVariantList actionHistory = jsonDoc.toVariant().toList();
    qCDebug(dcTests()) << qUtf8Printable(jsonDoc.toJson(QJsonDocument::Indented));

    // Make sure we got the correct actions
    QVERIFY(verifyActionExecuted(actionHistory, "power"));
    QCOMPARE(getLastValueFromExecutedAction(actionHistory, "power", "power"), false);
}


void TestCharging::testOverloadProtectionManualMode_data()
{
    // Houshold info
    QTest::addColumn<int>("phasePowerLimit");

    // Car
    QTest::addColumn<int>("carPhaseCount");

    // Charger information and states
    QTest::addColumn<bool>("chargerPower");
    QTest::addColumn<QString>("chargerPhases");
    QTest::addColumn<int>("chargerMaxChargingCurrent");
    QTest::addColumn<int>("chargerMaxChargingCurrentMaxValue");

    // Current meter power states
    QTest::addColumn<QVariantMap>("meterPhasesPower");
    QTest::addColumn<QVariantMap>("triggerMeterPhasesPower");

    // Desired result on the evCharger
    QTest::addColumn<int>("expectedChargerMaxChargingCurrent");
    QTest::addColumn<bool>("expectedChargerPower");

    QTest::newRow("Default: 1 phase, 16A -> 10A")
        << 32 // phase limit (A) 7360 W

        << 1 // carPhaseCount

        << true // chargerPower
        << "A" // chargerPhases
        << 16 // chargerMaxChargingCurrent
        << 16 //chargerMaxChargingCurrentMaxValue

        << QVariantMap({{"A", 4680}, // W
                        {"B", 2000}, // W
                        {"C", -6000} }) // W

        << QVariantMap({ {"A", 8680}, // W
                        {"B", 2000}, // W
                        {"C", -6000}}) // W

        << 10 // expectedChargerMaxChargingCurrent
        << true; // expectedChargerPower


    QTest::newRow("Default: 1 phase, 16A -> OFF")
        << 32 // phase limit (A) 7360 W

        << 1 // carPhaseCount

        << true // chargerPower
        << "A" // chargerPhases
        << 16 // chargerMaxChargingCurrent
        << 16 //chargerMaxChargingCurrentMaxValue

        << QVariantMap({{"A", 4680}, // W
                        {"B", 2000}, // W
                        {"C", -6000}}) // W

        << QVariantMap({{"A", 9820}, // W
                        {"B", 2000}, // W
                        {"C", -6000}}) // W

        << 16 // expectedChargerMaxChargingCurrent
        << false; // expectedChargerPower

    QTest::newRow("Default: 3 phase, 16A -> 10A")
        << 32 // phase limit (A) 7360 W

        << 3 // carPhaseCount

        << true // chargerPower
        << "ABC" // chargerPhases
        << 16 // chargerMaxChargingCurrent
        << 16 //chargerMaxChargingCurrentMaxValue

        << QVariantMap({{"A", 4680}, // W
                        {"B", 2000}, // W
                        {"C", 3200}}) // W

        << QVariantMap({{"A", 4680}, // W
                        {"B", 8280}, // W
                        {"C", 3200}}) // W

        << 12 // expectedChargerMaxChargingCurrent
        << true; // expectedChargerPower
}

void TestCharging::testOverloadProtectionManualMode()
{
    QFETCH(int, phasePowerLimit);

    QFETCH(int, carPhaseCount);

    QFETCH(bool, chargerPower);
    QFETCH(QString, chargerPhases);
    QFETCH(int, chargerMaxChargingCurrent);
    QFETCH(int, chargerMaxChargingCurrentMaxValue);

    QFETCH(QVariantMap, meterPhasesPower);
    QFETCH(QVariantMap, triggerMeterPhasesPower);

    QFETCH(int, expectedChargerMaxChargingCurrent);
    QFETCH(bool, expectedChargerPower);

    QVariantMap params;
    QVariant response, notification;
    QNetworkReply *reply = nullptr;
    QSignalSpy notificationSpy(m_mockTcpServer, &MockTcpServer::outgoingData);;

    // Set phase power limit
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    response = injectAndWait("NymeaEnergy.SetPhasePowerLimit", QVariantMap({{"phasePowerLimit", phasePowerLimit}}));
    QVERIFY(response.toMap().value("params").toMap().value("energyError").toString() == "EnergyErrorNoError");

    // Add mock meter
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    QUuid meterThingId = addMeter();
    QVERIFY2(!meterThingId.isNull(), "Did not receive valid ThingId");
    if (notificationSpy.count() == 0) notificationSpy.wait();
    notification = checkNotification(notificationSpy, "Energy.RootMeterChanged");
    QCOMPARE(notification.toMap().value("params").toMap().value("rootMeterThingId").toUuid(), meterThingId);

    // Make sure this is our root meter now
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    response = injectAndWait("Energy.GetRootMeter");
    QCOMPARE(response.toMap().value("params").toMap().value("rootMeterThingId").toUuid(), meterThingId);
    notificationSpy.clear();

    // Set the meter values first so we have the desired execution o charging info changed, and not an extra iteration with 0 W on root meter
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    reply = setMeterStates(meterPhasesPower);
    QSignalSpy setStatesReplySpy(reply, &QNetworkReply::finished);
    if (setStatesReplySpy.count() == 0) setStatesReplySpy.wait();
    QCOMPARE(reply->error(), QNetworkReply::NoError);

    // No evaluation, there is no meter configured yet

    // Add the charger
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    QUuid evChargerId = addCharger(chargerPhases, chargerMaxChargingCurrentMaxValue);
    QVERIFY2(!evChargerId.isNull(), "Did not receive valid ThingId");
    if (notificationSpy.count() == 0) notificationSpy.wait();
    checkNotification(notificationSpy, "Integrations.ThingAdded");

    // Add the car
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    QUuid assignedCarId = addCar();
    QVERIFY2(!assignedCarId.isNull(), "Did not receive valid ThingId");
    if (notificationSpy.count() == 0) notificationSpy.wait();
    checkNotification(notificationSpy, "Integrations.ThingAdded");

    // Set states of the charger

    uint effectivePhaseCount = qMin((uint)carPhaseCount, Electricity::getPhaseCount(Electricity::convertPhasesFromString(chargerPhases)));
    QString usedPhases;
    if (effectivePhaseCount >= 1)
        usedPhases.append("A");

    if (effectivePhaseCount >= 2)
        usedPhases.append("B");

    if (effectivePhaseCount >= 3)
        usedPhases.append("C");

    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    reply = setChargerStates(true, chargerPower, true, usedPhases, chargerMaxChargingCurrent, chargerMaxChargingCurrentMaxValue);
    QSignalSpy setChargerStatesReplySpy(reply, &QNetworkReply::finished);
    if (setChargerStatesReplySpy.count() == 0) setChargerStatesReplySpy.wait();
    QCOMPARE(reply->error(), QNetworkReply::NoError);

    // Set charging info with our charger and car, this should trigger the evaluation
    QVariantMap chargingInfoMap;
    chargingInfoMap.insert("evChargerId", evChargerId);
    if (!assignedCarId.isNull())
        chargingInfoMap.insert("assignedCarId", assignedCarId);

    chargingInfoMap.insert("chargingMode", "ChargingModeNormal");
    response = injectAndWait("NymeaEnergy.SetChargingInfo", QVariantMap({{"chargingInfo", chargingInfoMap}}));
    verifyEnergyError(response);


    // Now add the trigger load on the meter to trigger the overload protection
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    reply = setMeterStates(triggerMeterPhasesPower);
    QSignalSpy setMeterStatesReplySpy(reply, &QNetworkReply::finished);
    if (setMeterStatesReplySpy.count() == 0) setMeterStatesReplySpy.wait();
    QCOMPARE(reply->error(), QNetworkReply::NoError);

    // Veify overload protection active

    // Verify if the charger has been set correctly
    reply = getActionHistory(m_mockChargerDefaultPort);
    QSignalSpy actionHistoryReplySpy(reply, &QNetworkReply::finished);
    if (actionHistoryReplySpy.count() == 0) actionHistoryReplySpy.wait();
    QCOMPARE(reply->error(), QNetworkReply::NoError);
    QByteArray data = reply->readAll();
    QJsonDocument jsonDoc = QJsonDocument::fromJson(data);
    QVariantList actionHistory = jsonDoc.toVariant().toList();
    //qCDebug(dcTests()) << qUtf8Printable(jsonDoc.toJson(QJsonDocument::Indented));

    // Make sure we got the correct actions
    if (expectedChargerMaxChargingCurrent != 0)
        QVERIFY(verifyActionExecuted(actionHistory, "maxChargingCurrent"));

    QVERIFY(verifyActionExecuted(actionHistory, "power"));

    if (expectedChargerMaxChargingCurrent != 0)
        QCOMPARE(getLastValueFromExecutedAction(actionHistory, "maxChargingCurrent", "maxChargingCurrent"), QVariant(expectedChargerMaxChargingCurrent));

    QCOMPARE(getLastValueFromExecutedAction(actionHistory, "power", "power"), QVariant(expectedChargerPower));

    removeDevices();
}

void TestCharging::testOverloadProtectionEcoMode_data()
{
    // Houshold info
    QTest::addColumn<int>("phasePowerLimit");

    // Car
    QTest::addColumn<int>("carPhaseCount");

    // Charger information and states
    QTest::addColumn<bool>("chargerPower");
    QTest::addColumn<QString>("chargerPhases");
    QTest::addColumn<int>("chargerMaxChargingCurrent");
    QTest::addColumn<int>("chargerMaxChargingCurrentMaxValue");

    // Current meter power states
    QTest::addColumn<QVariantMap>("meterPhasesPower");
    QTest::addColumn<QVariantMap>("triggerMeterPhasesPower");

    // Desired result on the evCharger
    QTest::addColumn<int>("expectedChargerMaxChargingCurrent");
    QTest::addColumn<bool>("expectedChargerPower");

    QTest::newRow("Default: 1 phase, 16A -> 10A")
        << 32 // phase limit (A) 7360 W

        << 3 // carPhaseCount

        << true // chargerPower
        << "ABC" // chargerPhases
        << 16 // chargerMaxChargingCurrent
        << 16 //chargerMaxChargingCurrentMaxValue

        << QVariantMap({{"A", 4680}, // W
                        {"B", 2000}, // W
                        {"C", -6000} }) // W

        << QVariantMap({ {"A", 8680}, // W
                        {"B", 2000}, // W
                        {"C", -6000}}) // W

        << 6 // expectedChargerMaxChargingCurrent
        << true; // expectedChargerPower
}

void TestCharging::testOverloadProtectionEcoMode()
{
    QFETCH(int, phasePowerLimit);

    QFETCH(int, carPhaseCount);

    QFETCH(bool, chargerPower);
    QFETCH(QString, chargerPhases);
    QFETCH(int, chargerMaxChargingCurrent);
    QFETCH(int, chargerMaxChargingCurrentMaxValue);

    QFETCH(QVariantMap, meterPhasesPower);
    QFETCH(QVariantMap, triggerMeterPhasesPower);

    QFETCH(int, expectedChargerMaxChargingCurrent);
    QFETCH(bool, expectedChargerPower);

    QVariantMap params;
    QVariant response, notification;
    QNetworkReply *reply = nullptr;
    QSignalSpy notificationSpy(m_mockTcpServer, &MockTcpServer::outgoingData);;

    // Set phase power limit
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    response = injectAndWait("NymeaEnergy.SetPhasePowerLimit", QVariantMap({{"phasePowerLimit", phasePowerLimit}}));
    QVERIFY(response.toMap().value("params").toMap().value("energyError").toString() == "EnergyErrorNoError");

    // Add mock meter
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    QUuid meterThingId = addMeter();
    QVERIFY2(!meterThingId.isNull(), "Did not receive valid ThingId");
    if (notificationSpy.count() == 0) notificationSpy.wait();
    notification = checkNotification(notificationSpy, "Energy.RootMeterChanged");
    QCOMPARE(notification.toMap().value("params").toMap().value("rootMeterThingId").toUuid(), meterThingId);

    // Make sure this is our root meter now
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    response = injectAndWait("Energy.GetRootMeter");
    QCOMPARE(response.toMap().value("params").toMap().value("rootMeterThingId").toUuid(), meterThingId);
    notificationSpy.clear();

    // Set the meter values first so we have the desired execution o charging info changed, and not an extra iteration with 0 W on root meter
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    reply = setMeterStates(meterPhasesPower);
    QSignalSpy setStatesReplySpy(reply, &QNetworkReply::finished);
    if (setStatesReplySpy.count() == 0) setStatesReplySpy.wait();
    QCOMPARE(reply->error(), QNetworkReply::NoError);

    // No evaluation, there is no meter configured yet

    // Add the charger
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    QUuid evChargerId = addCharger(chargerPhases, chargerMaxChargingCurrentMaxValue);
    QVERIFY2(!evChargerId.isNull(), "Did not receive valid ThingId");
    if (notificationSpy.count() == 0) notificationSpy.wait();
    checkNotification(notificationSpy, "Integrations.ThingAdded");

    // Add the car
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    QUuid assignedCarId = addCar();
    QVERIFY2(!assignedCarId.isNull(), "Did not receive valid ThingId");
    if (notificationSpy.count() == 0) notificationSpy.wait();
    checkNotification(notificationSpy, "Integrations.ThingAdded");

    // Set states of the charger

    uint effectivePhaseCount = qMin((uint)carPhaseCount, Electricity::getPhaseCount(Electricity::convertPhasesFromString(chargerPhases)));
    QString usedPhases;
    if (effectivePhaseCount >= 1)
        usedPhases.append("A");

    if (effectivePhaseCount >= 2)
        usedPhases.append("B");

    if (effectivePhaseCount >= 3)
        usedPhases.append("C");

    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    reply = setChargerStates(true, chargerPower, true, usedPhases, chargerMaxChargingCurrent, chargerMaxChargingCurrentMaxValue);
    QSignalSpy setChargerStatesReplySpy(reply, &QNetworkReply::finished);
    if (setChargerStatesReplySpy.count() == 0) setChargerStatesReplySpy.wait();
    QCOMPARE(reply->error(), QNetworkReply::NoError);

    // Set charging info with our charger and car, this should trigger the evaluation
    QVariantMap chargingInfoMap;
    chargingInfoMap.insert("evChargerId", evChargerId);
    if (!assignedCarId.isNull())
        chargingInfoMap.insert("assignedCarId", assignedCarId);

    chargingInfoMap.insert("chargingMode", "ChargingModeEco");
    response = injectAndWait("NymeaEnergy.SetChargingInfo", QVariantMap({{"chargingInfo", chargingInfoMap}}));
    verifyEnergyError(response);

    // Now add the trigger load on the meter to trigger the overload protection
    QTest::qSleep(1500);
    params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
    reply = setMeterStates(triggerMeterPhasesPower);
    QSignalSpy setMeterStatesReplySpy(reply, &QNetworkReply::finished);
    if (setMeterStatesReplySpy.count() == 0) setMeterStatesReplySpy.wait();
    QCOMPARE(reply->error(), QNetworkReply::NoError);

    // Verify overload protection active
    // Verify if the charger has been set correctly
    reply = getActionHistory(m_mockChargerDefaultPort);
    QSignalSpy actionHistoryReplySpy(reply, &QNetworkReply::finished);
    if (actionHistoryReplySpy.count() == 0) actionHistoryReplySpy.wait();
    QCOMPARE(reply->error(), QNetworkReply::NoError);
    QByteArray data = reply->readAll();
    QJsonDocument jsonDoc = QJsonDocument::fromJson(data);
    QVariantList actionHistory = jsonDoc.toVariant().toList();
    //qCDebug(dcTests()) << qUtf8Printable(jsonDoc.toJson(QJsonDocument::Indented));

    // Make sure we got the correct actions
    if (expectedChargerMaxChargingCurrent != 0)
        QVERIFY(verifyActionExecuted(actionHistory, "maxChargingCurrent"));

    QVERIFY(verifyActionExecuted(actionHistory, "power"));

    if (expectedChargerMaxChargingCurrent != 0)
        QCOMPARE(getLastValueFromExecutedAction(actionHistory, "maxChargingCurrent", "maxChargingCurrent"), QVariant(expectedChargerMaxChargingCurrent));

    QCOMPARE(getLastValueFromExecutedAction(actionHistory, "power", "power"), QVariant(expectedChargerPower));

    removeDevices();
}


QTEST_MAIN(TestCharging)
