// SPDX-License-Identifier: GPL-3.0-or-later

#include "v2ctcpdiscovery.h"
#include "extern-plugininfo.h"

#include <QNetworkReply>
#include <QNetworkRequest>
#include <QJsonDocument>
#include <QJsonObject>
#include <QTimer>
#include <QTime>
#include <QUrl>

V2cTcpDiscovery::V2cTcpDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery,
                                 QObject *parent)
    : QObject(parent),
      m_networkDeviceDiscovery(networkDeviceDiscovery)
{}

void V2cTcpDiscovery::startDiscovery()
{
    qCInfo(dcV2C()) << "Discovery: searching for V2C Trydan chargers on the network...";
    m_startTime         = QDateTime::currentDateTime();
    m_pendingProbes     = 0;
    m_scanFinished      = false;
    m_results.clear();
    m_networkDeviceInfos.clear();

    NetworkDeviceDiscoveryReply *reply = m_networkDeviceDiscovery->discover();

    connect(reply, &NetworkDeviceDiscoveryReply::hostAddressDiscovered,
            this, &V2cTcpDiscovery::probeAddress);

    connect(reply, &NetworkDeviceDiscoveryReply::finished,
            reply, &NetworkDeviceDiscoveryReply::deleteLater);

    connect(reply, &NetworkDeviceDiscoveryReply::finished, this, [this, reply]() {
        m_networkDeviceInfos = reply->networkDeviceInfos();
        m_scanFinished = true;

        // Give outstanding HTTP probes 3 extra seconds to complete.
        QTimer::singleShot(3000, this, &V2cTcpDiscovery::finishDiscovery);
    });
}

QList<V2cTcpDiscovery::Result> V2cTcpDiscovery::results() const
{
    return m_results;
}

void V2cTcpDiscovery::probeAddress(const QHostAddress &address)
{
    // The V2C Trydan HTTP API: GET /RealTimeData returns JSON with a "Paused"
    // field (and ChargeState, ChargePower, etc.).  The presence of "Paused"
    // is a reliable Trydan fingerprint; it does not appear on generic devices.
    // cf. evcc charger/trydan.go realTimeData struct and AGENTS.md § HTTP.
    QUrl url;
    url.setScheme(QStringLiteral("http"));
    url.setHost(address.toString());
    url.setPath(QStringLiteral("/RealTimeData"));

    QNetworkRequest request(url);
    request.setTransferTimeout(3000);

    m_pendingProbes++;
    QNetworkReply *reply = m_nam.get(request);

    connect(reply, &QNetworkReply::finished, this, [this, reply, address]() {
        reply->deleteLater();
        m_pendingProbes--;

        if (reply->error() == QNetworkReply::NoError) {
            const QByteArray data = reply->readAll();
            const QJsonObject json = QJsonDocument::fromJson(data).object();

            // "Paused" is the HTTP-API name for PauseState — note it differs from
            // the Modbus register name (cf. AGENTS.md § HTTP).
            if (json.contains(QStringLiteral("Paused"))) {
                Result result;
                result.ipAddress         = address.toString();
                result.networkDeviceInfo = m_networkDeviceInfos.get(address);
                if (result.networkDeviceInfo.address().isNull()) {
                    NetworkDeviceInfo info;
                    info.setAddress(address);
                    result.networkDeviceInfo = info;
                }
                qCDebug(dcV2C()) << "Discovery: found V2C Trydan at" << address.toString();
                m_results.append(result);
            }
        }

        // Finish early if the scan is already done and this was the last probe.
        if (m_scanFinished && m_pendingProbes == 0)
            finishDiscovery();
    });
}

void V2cTcpDiscovery::finishDiscovery()
{
    // Guard against double-emission (can be called by the timer and the last probe).
    if (m_finished)
        return;
    m_finished = true;

    const qint64 ms = QDateTime::currentMSecsSinceEpoch() - m_startTime.toMSecsSinceEpoch();
    qCInfo(dcV2C()) << "Discovery: finished in"
                    << QTime::fromMSecsSinceStartOfDay(static_cast<int>(ms)).toString("mm:ss.zzz")
                    << "with" << m_results.count() << "V2C Trydan(s) found.";
    emit discoveryFinished();
}
