// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2025 - 2026, Patrick Schurig / ETM PowerSync

#include "energyarbitrator.h"
#include "adapters/evadapter.h"
#include "adapters/ecsrelayadapter.h"
#include "scheduler/rulebasedscheduler.h"
#include "types/surpluscontext.h"
#include "types/plan.h"
#include "../rootmeter.h"

#include "plugininfo.h"

EnergyArbitrator::EnergyArbitrator(EnergyManager *em, ThingManager *tm,
                                    SpotMarketManager *sm,
                                    EnergyManagerConfiguration *conf,
                                    QObject *parent)
    : SmartChargingManager(em, tm, sm, conf, parent)
    , m_scheduler(new RuleBasedScheduler(this, this))
{
    qCDebug(dcNymeaEnergy()) << "[EnergyArbitrator] Arbitre ETM initialisé.";
}

void EnergyArbitrator::runSurplusPlanning(const QDateTime &now)
{
    planSurplusCharging(now);
}

void EnergyArbitrator::runSpotMarketPlanning(const QDateTime &now)
{
    planSpotMarketCharging(now);
}

const QHash<EvCharger *, ChargingActions> &EnergyArbitrator::scheduledActions() const
{
    return internalChargingActions();
}

void EnergyArbitrator::doExecuteChargingAction(EvCharger *charger,
                                                const ChargingAction &action,
                                                const QDateTime &now)
{
    executeChargingAction(charger, action, now);
}

const QHash<ThingId, EvCharger *> &EnergyArbitrator::registeredEvChargers() const
{
    return internalEvChargers();
}

RootMeter *EnergyArbitrator::registeredRootMeter() const
{
    return internalRootMeter();
}

void EnergyArbitrator::registerEcsAdapter(EcsRelayAdapter *adapter)
{
    const QString id = adapter->descriptor().id;
    if (m_ecsAdapters.contains(id)) {
        qCWarning(dcNymeaEnergy()) << "[EnergyArbitrator] EcsRelayAdapter déjà enregistré:" << id;
        return;
    }
    adapter->setParent(this);
    m_ecsAdapters[id] = adapter;
    qCDebug(dcNymeaEnergy()) << "[EnergyArbitrator] EcsRelayAdapter enregistré:" << adapter->descriptor().label;
}

void EnergyArbitrator::update(const QDateTime &currentDateTime)
{
    qCDebug(dcNymeaEnergy()) << "Updating smart charging";
    // Ordre IDENTIQUE à SmartChargingManager::update() — INTERDIT de réordonner.
    // SCM : 1.updateManual  2.prepareInfo  3.verifyOverload  4.verifyRecovery
    //        5.planSpot     6.planSurplus  7.adjustEv
    // ETM : idem 1-4 ; insertions ETM entre 4 et 7 ;
    //        planSpot + planSurplus appelés via m_scheduler->getPlan() (position 5-6).

    // 1-4 : préparation + sécurité (même ordre que l'amont)
    updateManualSoCsWithoutMeter(currentDateTime);
    prepareInformation(currentDateTime);
    verifyOverloadProtection(currentDateTime);
    verifyOverloadProtectionRecovery(currentDateTime);

    // ETM-only : sync adapters + proxy planification → log [Arbitre]
    // getPlan() appelle planSpotMarketCharging() + planSurplusCharging() (position 5-6 amont).
    syncAdapters();
    SurplusContext ctx = buildContext(currentDateTime);
    Plan plan = m_scheduler->getPlan(ctx);
    Slot slot = plan.slotCovering(currentDateTime);

    for (const LoadAction &action : slot.actions) {
        qCInfo(dcNymeaEnergy()) << "[Arbitre]"
            << action.loadId << "→" << action.reason
            << "| activé:" << action.chargingEnabled
            << "| courant:" << action.currentA << "A"
            << "| phases:" << action.phaseCount
            << "| stratégie:" << plan.strategy;
    }

    // 7 : dispatch matériel (même position que l'amont — m_chargingActions rempli par getPlan())
    applyActionsToAdapters(slot);       // ECS (kind==Stage) → m_ecsAdapters
    adjustEvChargers(currentDateTime);  // EV (kind==Setpoint) → proxy amont jusqu'à 3g
}

SurplusContext EnergyArbitrator::buildContext(const QDateTime &now) const
{
    SurplusContext ctx;
    ctx.timestamp = now;

    // --- Compteur principal (AGENTS invariant 8 : mesure brute, aucune déduction) ---
    RootMeter *meter = internalRootMeter();
    if (meter) {
        // currentPower() < 0 → export ; > 0 → import (convention amont SCM l.1141)
        const double p = meter->currentPower();
        ctx.meter.importW = qMax(0.0,  p);
        ctx.meter.exportW = qMax(0.0, -p);
        ctx.meter.perPhaseA = {
            meter->currentPhaseA(),
            meter->currentPhaseB(),
            meter->currentPhaseC()
        };
    }
    // SurplusPv : interface inverter — déféré (remplissage prévu en 3d)
    // SurplusBattery : déféré 3f

    // --- loads[] : EV adapters ---
    for (auto it = m_adapters.constBegin(); it != m_adapters.constEnd(); ++it)
        ctx.loads.append(it.value()->toLoadContext());

    // --- loads[] : ECS relay adapters ---
    for (auto it = m_ecsAdapters.constBegin(); it != m_ecsAdapters.constEnd(); ++it)
        ctx.loads.append(it.value()->toLoadContext());

    return ctx;
}

void EnergyArbitrator::syncAdapters()
{
    // Crée les adapters manquants
    for (auto it = internalEvChargers().constBegin(); it != internalEvChargers().constEnd(); ++it) {
        const QString id = it.key().toString();
        if (!m_adapters.contains(id))
            m_adapters[id] = new EvAdapter(it.value(), this);
    }
    // Supprime les adapters obsolètes
    for (const QString &id : m_adapters.keys()) {
        if (!internalEvChargers().contains(ThingId(id)))
            m_adapters.take(id)->deleteLater();
    }
}

void EnergyArbitrator::applyActionsToAdapters(const Slot &slot)
{
    for (const LoadAction &action : slot.actions) {
        // EV (Setpoint) : dispatché par adjustEvChargers() amont jusqu'à 3g.
        if (action.kind != LoadAction::Stage)
            continue;

        EcsRelayAdapter *adapter = m_ecsAdapters.value(action.loadId);
        if (!adapter) {
            qCWarning(dcNymeaEnergy()) << "[Arbitre] action Stage sans adaptateur ECS:" << action.loadId;
            continue;
        }
        // L'adaptateur applique, écrête et verrouille (anti-rebond) — il ne décide pas.
        adapter->applyAction(action);
    }
}
