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

#include <nymeasettings.h>

#include "energylogger.h"

#include <qmath.h>

Q_DECLARE_LOGGING_CATEGORY(dcEnergyExperience)

EnergyManagerMock::EnergyManagerMock(ThingManager *thingManager, const QString &energyLogDbFilePath, QObject *parent):
    EnergyManager(parent),
    m_thingManager(thingManager),
    m_logger(new EnergyLogger(energyLogDbFilePath, this))

{
    // Most of the time we get a bunch of state changes (currentPower, totals, for inverter, battery, rootmeter)
    // at the same time if they're implemented by the same plugin.
    // In order to decrease some load on the system, we'll wait for the event loop pass to finish until we actually
    // update to accumulate those changes and calculate the change in one go.
    m_balanceUpdateTimer.setInterval(0);
    m_balanceUpdateTimer.setSingleShot(true);
    connect(&m_balanceUpdateTimer, &QTimer::timeout, this, &EnergyManagerMock::updatePowerBalance);

    QSettings settings(NymeaSettings::settingsPath() + "/energy.conf", QSettings::IniFormat);
    ThingId rootMeterThingId = settings.value("rootMeterThingId").toUuid();
    EnergyManagerMock::setRootMeter(rootMeterThingId);
    qCDebug(dcEnergyExperience()) << "Loaded root meter" << rootMeterThingId;

    PowerBalanceLogEntry latestEntry = m_logger->latestLogEntry(EnergyLogs::SampleRateAny);
    m_totalConsumption = latestEntry.totalConsumption();
    m_totalProduction = latestEntry.totalProduction();
    m_totalAcquisition = latestEntry.totalAcquisition();
    m_totalReturn = latestEntry.totalReturn();
    qCDebug(dcEnergyExperience()) << "Loaded power balance totals. Consumption:" << m_totalConsumption << "Production:" << m_totalProduction << "Acquisition:" << m_totalAcquisition << "Return:" << m_totalReturn;
}

Thing *EnergyManagerMock::rootMeter() const
{
    return m_rootMeter;
}

EnergyManager::EnergyError EnergyManagerMock::setRootMeter(const ThingId &rootMeterId)
{
    Thing *rootMeter = m_thingManager->findConfiguredThing(rootMeterId);
    if (!rootMeter || !rootMeter->thingClass().interfaces().contains("energymeter")) {
        return EnergyErrorInvalidParameter;
    }

    if (m_rootMeter != rootMeter) {
        qCDebug(dcEnergyExperience()) << "Setting root meter to" << rootMeter->name();
        m_rootMeter = rootMeter;

        QSettings settings(NymeaSettings::settingsPath() + "/energy.conf", QSettings::IniFormat);
        settings.setValue("rootMeterThingId", rootMeter->id());

        emit rootMeterChanged();
    }
    return EnergyErrorNoError;
}

double EnergyManagerMock::currentPowerConsumption() const
{
    return m_currentPowerConsumption;
}

double EnergyManagerMock::currentPowerProduction() const
{
    return m_currentPowerProduction;
}

double EnergyManagerMock::currentPowerAcquisition() const
{
    return m_currentPowerAcquisition;
}

double EnergyManagerMock::currentPowerStorage() const
{
    return m_currentPowerStorage;
}

double EnergyManagerMock::totalConsumption() const
{
    return m_totalConsumption;
}

double EnergyManagerMock::totalProduction() const
{
    return m_totalProduction;
}

double EnergyManagerMock::totalAcquisition() const
{
    return m_totalAcquisition;
}

double EnergyManagerMock::totalReturn() const
{
    return m_totalReturn;
}

EnergyLogs *EnergyManagerMock::logs() const
{
    return m_logger;
}

void EnergyManagerMock::updatePowerBalance()
{

}

void EnergyManagerMock::logDumpConsumers()
{

}

//void EnergyManagerMock::watchThing(Thing *thing)
//{
//    // If we don't have a root meter yet, we'll be auto-setting the first energymeter that appears.
//    // It may be changed by the user through an API call later.
//    if (!m_rootMeter && thing->thingClass().interfaces().contains("energymeter")) {
//        setRootMeter(thing->id());
//    }

//    qCDebug(dcEnergyExperience()) << "Watching thing:" << thing->name();

//    // React on things that require us updating the power balance
//    if (thing->thingClass().interfaces().contains("energymeter")
//            || thing->thingClass().interfaces().contains("smartmeterproducer")
//            || thing->thingClass().interfaces().contains("energystorage")) {
//        connect(thing, &Thing::stateValueChanged, this, [=](const StateTypeId &stateTypeId){
//            if (thing->thingClass().getStateType(stateTypeId).name() == "currentPower") {
//                m_balanceUpdateTimer.start();
//            }
//        });
//    }

//    // React on things that need to be logged
//    if (thing->thingClass().interfaces().contains("energymeter")
//            || thing->thingClass().interfaces().contains("smartmeterconsumer")
//            || thing->thingClass().interfaces().contains("smartmeterproducer")
//            || thing->thingClass().interfaces().contains("energystorage")) {

//        // Initialize caches used to calculate diffs
//        ThingPowerLogEntry entry = m_logger->latestLogEntry(EnergyLogs::SampleRateAny, {thing->id()});
//        ThingPowerLogEntry stateEntry = m_logger->cachedThingEntry(thing->id());

//        m_powerBalanceTotalEnergyConsumedCache[thing] = stateEntry.totalConsumption();
//        m_powerBalanceTotalEnergyProducedCache[thing] = stateEntry.totalProduction();

//        m_thingsTotalEnergyConsumedCache[thing] = qMakePair<double, double>(stateEntry.totalConsumption(), entry.totalConsumption());
//        m_thingsTotalEnergyProducedCache[thing] = qMakePair<double, double>(stateEntry.totalProduction(), entry.totalProduction());
//        qCDebug(dcEnergyExperience()) << "Loaded thing power totals for" << thing->name() << "Consumption:" << entry.totalConsumption() << "Production:" << entry.totalProduction() << "Last thing state consumption:" << stateEntry.totalConsumption() << "production:" << stateEntry.totalProduction();

//        connect(thing, &Thing::stateValueChanged, this, [=](const StateTypeId &stateTypeId, const QVariant &/*value*/){
//            if (QStringList({"currentPower", "totalEnergyConsumed", "totalEnergyProduced"}).contains(thing->thingClass().getStateType(stateTypeId).name())) {

//                // We'll be keeping our own counters, starting from 0 at the time they're added to nymea and increasing with the things counters.
//                // This way we'll have proper logs even if the thing counter is reset (some things may reset their counter on power loss, factory reset etc)
//                // and also won't start with huge values if the thing has been counting for a while and only added to nymea later on


//                // Consumption
//                double oldThingConsumptionState = m_thingsTotalEnergyConsumedCache.value(thing).first;
//                double oldThingConsumptionInternal = m_thingsTotalEnergyConsumedCache.value(thing).second;
//                double newThingConsumptionState = thing->stateValue("totalEnergyConsumed").toDouble();
//                // For the very first cycle (oldConsumption is 0) we'll sync up on the meter, without actually adding it to our diff
//                if (oldThingConsumptionState == 0 && newThingConsumptionState != 0) {
//                    qInfo(dcEnergyExperience()) << "Don't have a consumption counter for" << thing->name() << "Synching internal counters to initial value:" << newThingConsumptionState;
//                    oldThingConsumptionState = newThingConsumptionState;
//                }
//                // If the thing's meter has been reset in the meantime (newConsumption < oldConsumption) we'll sync down, taking the whole diff from 0 to new value
//                if (newThingConsumptionState < oldThingConsumptionState) {
//                    qCInfo(dcEnergyExperience()) << "Thing meter for" << thing->name() << "seems to have been reset. Re-synching internal consumption counter.";
//                    oldThingConsumptionState = newThingConsumptionState;
//                }
//                double consumptionDiff = newThingConsumptionState - oldThingConsumptionState;
//                double newThingConsumptionInternal = oldThingConsumptionInternal + consumptionDiff;
//                m_thingsTotalEnergyConsumedCache[thing] = qMakePair<double, double>(newThingConsumptionState, newThingConsumptionInternal);


//                // Production
//                double oldThingProductionState = m_thingsTotalEnergyProducedCache.value(thing).first;
//                double oldThingProductionInternal = m_thingsTotalEnergyProducedCache.value(thing).second;
//                double newThingProductionState = thing->stateValue("totalEnergyProduced").toDouble();
//                // For the very first cycle (oldProductino is 0) we'll sync up on the meter, without actually adding it to our diff
//                if (oldThingProductionState == 0 && newThingProductionState != 0) {
//                    qInfo(dcEnergyExperience()) << "Don't have a production counter for" << thing->name() << "Synching internal counter to initial value:" << newThingProductionState;
//                    oldThingProductionState = newThingProductionState;
//                }
//                // If the thing's meter has been reset in the meantime (newProduction < oldProduction) we'll sync down, taking the whole diff from 0 to new value
//                if (newThingProductionState < oldThingProductionState) {
//                    qCInfo(dcEnergyExperience()) << "Thing meter for" << thing->name() << "seems to have been reset. Re-synching internal production counter.";
//                    oldThingProductionState = newThingProductionState;
//                }
//                double productionDiff = newThingProductionState - oldThingProductionState;
//                double newThingProductionInternal = oldThingProductionInternal + productionDiff;
//                m_thingsTotalEnergyProducedCache[thing] = qMakePair<double, double>(newThingProductionState, newThingProductionInternal);


//                // Write to log
//                qCDebug(dcEnergyExperience()) << "Logging thing" << thing->name() << "total consumption:" << newThingConsumptionInternal << "production:" << newThingProductionInternal;
//                m_logger->logThingPower(thing->id(), thing->state("currentPower").value().toDouble(), newThingConsumptionInternal, newThingProductionInternal);

//                // Cache the thing state values in case nymea is restarted
//                m_logger->cacheThingEntry(thing->id(), newThingConsumptionState, newThingProductionState);
//            }
//        });
//    }
//}

//void EnergyManagerMock::unwatchThing(const ThingId &thingId)
//{
//    if (m_rootMeter && m_rootMeter->id() == thingId) {
//        m_rootMeter = nullptr;
//        emit rootMeterChanged();
//    }

//    m_logger->removeThingLogs(thingId);
//}

//void EnergyManagerMock::updatePowerBalance()
//{
//    double currentPowerAcquisition = 0;
//    if (m_rootMeter) {
//        currentPowerAcquisition = m_rootMeter->stateValue("currentPower").toDouble();

//        double oldAcquisition = m_powerBalanceTotalEnergyConsumedCache.value(m_rootMeter);
//        double newAcquisition = m_rootMeter->stateValue("totalEnergyConsumed").toDouble();
//        // For the very first cycle (oldAcquisition is 0) we'll sync up on the meter values without actually adding them to our balance.
//        if (oldAcquisition == 0) {
//            oldAcquisition = newAcquisition;
//        }
//        // If the root meter has been reset in the meantime (newConsumption < oldConsumption) we'll sync down, taking the whole diff from 0 to new value
//        if (newAcquisition < oldAcquisition) {
//            qCInfo(dcEnergyExperience()) << "Root meter seems to have been reset. Re-synching internal consumption counter.";
//            oldAcquisition = newAcquisition;
//        }
//        qCDebug(dcEnergyExperience()) << "Root meter total consumption: Previous value:" << oldAcquisition << "New value:" << newAcquisition << "Diff:" << (newAcquisition -oldAcquisition);
//        m_totalAcquisition += newAcquisition - oldAcquisition;
//        m_powerBalanceTotalEnergyConsumedCache[m_rootMeter] = newAcquisition;

//        double oldReturn = m_powerBalanceTotalEnergyProducedCache.value(m_rootMeter);
//        double newReturn = m_rootMeter->stateValue("totalEnergyProduced").toDouble();
//        // For the very first cycle (oldReturn is 0) we'll sync up on the meter values without actually adding them to our balance.
//        if (oldReturn == 0) {
//            oldReturn = newReturn;
//        }
//        if (newReturn < oldReturn) {
//            qCInfo(dcEnergyExperience()) << "Root meter seems to have been reset. Re-synching internal production counter.";
//            oldReturn = newReturn;
//        }
//        qCDebug(dcEnergyExperience()) << "Root meter total production: Previous value:" << oldReturn << "New value:" << newReturn << "Diff:" << (newReturn - oldReturn);
//        m_totalReturn += newReturn - oldReturn;
//        m_powerBalanceTotalEnergyProducedCache[m_rootMeter] = newReturn;
//    }

//    double currentPowerProduction = 0;
//    foreach (Thing* thing, m_thingManager->configuredThings().filterByInterface("smartmeterproducer")) {
//        currentPowerProduction += thing->stateValue("currentPower").toDouble();
//        double oldProduction = m_powerBalanceTotalEnergyProducedCache.value(thing);
//        double newProduction = thing->stateValue("totalEnergyProduced").toDouble();
//        // For the very first cycle (oldProduction is 0) we'll sync up on the producer values without actually adding them to our balance.
//        if (oldProduction == 0) {
//            oldProduction = newProduction;
//        }
//        if (newProduction < oldProduction) {
//            oldProduction = newProduction;
//        }
//        qCDebug(dcEnergyExperience()) << "Producer" << thing->name() << "total production: Previous value:" << oldProduction << "New value:" << newProduction << "Diff:" << (newProduction - oldProduction);
//        m_totalProduction += newProduction - oldProduction;
//        m_powerBalanceTotalEnergyProducedCache[thing] = newProduction;
//    }

//    double currentPowerStorage = 0;
//    double totalFromStorage = 0;
//    foreach (Thing *thing, m_thingManager->configuredThings().filterByInterface("energystorage")) {
//        currentPowerStorage += thing->stateValue("currentPower").toDouble();
//        double oldProduction = m_powerBalanceTotalEnergyProducedCache.value(thing);
//        double newProduction = thing->stateValue("totalEnergyProduced").toDouble();
//        // For the very first cycle (oldProdction is 0) we'll sync up on the meter values without actually adding them to our balance.
//        if (oldProduction == 0) {
//            oldProduction = newProduction;
//        }
//        if (newProduction < oldProduction) {
//            oldProduction = newProduction;
//        }
//        qCDebug(dcEnergyExperience()) << "Storage" << thing->name() << "total storage: Previous value:" << oldProduction << "New value:" << newProduction << "Diff:" << (newProduction - oldProduction);
//        totalFromStorage += newProduction - oldProduction;
//        m_powerBalanceTotalEnergyProducedCache[thing] = newProduction;
//    }

//    double currentPowerConsumption = currentPowerAcquisition + qAbs(qMin(0.0, currentPowerProduction)) - currentPowerStorage;
//    m_totalConsumption = m_totalAcquisition + m_totalProduction + totalFromStorage - m_totalReturn;

//    qCDebug(dcEnergyExperience()).noquote().nospace() << "Power balance: " << "🔥: " << currentPowerConsumption << " W, 🌞: " << currentPowerProduction << " W, 💵: " << currentPowerAcquisition << " W, 🔋: " << currentPowerStorage << " W. Totals: 🔥: " << m_totalConsumption << " kWh, 🌞: " << m_totalProduction << " kWh, 💵↓: " << m_totalAcquisition << " kWh, 💵↑: " << m_totalReturn << " kWh";
//    if (currentPowerAcquisition != m_currentPowerAcquisition
//            || currentPowerConsumption != m_currentPowerConsumption
//            || currentPowerProduction != m_currentPowerProduction
//            || currentPowerStorage != m_currentPowerStorage) {
//        m_currentPowerAcquisition = currentPowerAcquisition;
//        m_currentPowerProduction = currentPowerProduction;
//        m_currentPowerConsumption = currentPowerConsumption;
//        m_currentPowerStorage = currentPowerStorage;
//        emit powerBalanceChanged();
//        m_logger->logPowerBalance(m_currentPowerConsumption, m_currentPowerProduction, m_currentPowerAcquisition, m_currentPowerStorage, m_totalConsumption, m_totalProduction, m_totalAcquisition, m_totalReturn);
//    }
//}

//void EnergyManagerMock::logDumpConsumers()
//{
//    foreach (Thing *consumer, m_thingManager->configuredThings().filterByInterface("smartmeterconsumer")) {
//        qCDebug(dcEnergyExperience()).nospace().noquote() << consumer->name() << ": " << (consumer->stateValue("currentPower").toDouble() / 230) << "A (" << consumer->stateValue("currentPower").toDouble() << "W)";
//    }
//}


