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

#include <integrations/thing.h>

#include <QTcpSocket>
#include <QJsonDocument>
#include <QRegularExpression>

#include "extern-plugininfo.h"

EnergyMockController::EnergyMockController(Thing *thing, QObject *parent):
    QTcpServer(parent),
    m_thing(thing)
{

}

EnergyMockController::~EnergyMockController()
{
    close();
}

void EnergyMockController::incomingConnection(qintptr socket)
{
    QTcpSocket* s = new QTcpSocket(this);
    connect(s, &QTcpSocket::readyRead, this, &EnergyMockController::onReadyRead);
    connect(s, &QTcpSocket::disconnected, s, [s](){
        s->deleteLater();
    });
    s->setSocketDescriptor(socket);
}

void EnergyMockController::logActionExecuted(const ActionType &actionType, const Action &action)
{
    QVariantMap actionMap;
    actionMap.insert("timestamp", static_cast<uint>(QDateTime::currentDateTime().toMSecsSinceEpoch() / 1000));
    actionMap.insert("actionTypeId", action.actionTypeId());
    actionMap.insert("name", actionType.name());
    actionMap.insert("displayName", actionType.displayName());
    QVariantList params;
    foreach (const Param &param, action.params()) {
        QVariantMap paramsMap;
        paramsMap.insert("paramTypeId", param.paramTypeId());
        paramsMap.insert("value", param.value());
        paramsMap.insert("name", actionType.paramTypes().findById(param.paramTypeId()).name());
        params.append(paramsMap);
    }
    actionMap.insert("params", params);
    m_executedActions.append(actionMap);
    //qCDebug(dcEnergyMocks()) << "Action logged" << actionMap;
}

void EnergyMockController::onReadyRead()
{
    QTcpSocket *client = static_cast<QTcpSocket*>(sender());
    QByteArray data = client->readAll();

    // split the data into header and payload
    int headerEndIndex = data.indexOf("\r\n\r\n");
    if (headerEndIndex < 0) {
        qCWarning(dcEnergyMocks()) << "Could not parse payload" << data;
        return;
    }

    QByteArray rawHeader = data.left(headerEndIndex);
    QByteArray payload = data.right(data.length() - headerEndIndex).simplified();

    // parse status line
    QStringList headerLines = QString(rawHeader).split(QRegularExpression("\r\n"));
    QString statusLine = headerLines.takeFirst();
    QStringList statusLineTokens = statusLine.split(QRegularExpression("[ \r\n][ \r\n]*"));

    // verify http version
    QString httpVersion = statusLineTokens.at(2).toUtf8().simplified();
    if (!httpVersion.contains("HTTP")) {
        qCWarning(dcEnergyMocks()) << "Unknown HTTP version:" << httpVersion;
        return;
    }

    QString methodString = statusLineTokens.at(0).simplified();
    QUrl url = QUrl("http://example.com" + statusLineTokens.at(1).simplified());
    QUrlQuery query(url);

    if (url.path() == "/setstates") {
        qCDebug(dcEnergyMocks()) << "Set states called" << methodString << url.path() << query.toString();

        emit updateStateRequestReceived(query);

        QTextStream os(client);
        os.setAutoDetectUnicode(true);
        os << generateHeader();
        client->close();
        return;
    } else if (url.path() == "/actionhistory") {
        QTextStream os(client);
        os.setAutoDetectUnicode(true);
        os << generateJsonResponse(QJsonDocument::fromVariant(m_executedActions));
        client->close();
        return;
    } else if (url.path() == "/clearactionhistory") {
        m_executedActions.clear();
        QTextStream os(client);
        os.setAutoDetectUnicode(true);
        os << generateHeader();
        client->close();
        return;
    }


    // Default website
    QTextStream os(client);
    os.setAutoDetectUnicode(true);
    os << generateWebPage();
    client->close();

}

QByteArray EnergyMockController::generateHeader()
{
    QByteArray contentHeader(
                "HTTP/1.0 200 Ok\r\n"
       "Content-Type: text/html; charset=\"utf-8\"\r\n"
       "\r\n"
    );
    return contentHeader;
}

QByteArray EnergyMockController::generateJsonResponse(const QJsonDocument &document)
{
    QByteArray body = document.toJson(QJsonDocument::Compact);
    QByteArray contentHeader("HTTP/1.0 200 Ok\r\n");
    contentHeader.append("Content-Type: application/json\r\n");
    contentHeader.append(QString("Content-Length: %1").arg(body.length()).toUtf8() + "\r\n");
    contentHeader.append("\r\n");
    QByteArray response = contentHeader + body;
    //qCDebug(dcEnergyMocks()) << "Action histroy response:" << response;
    return response;
}

QByteArray EnergyMockController::generateWebPage()
{
    QString body = QString(
        "<html>"
        "<body>"
        "<h1>Mock device Controller</h1>\n"
        "<hr>"
            "<h2>Device Information</h2>"
            "Name: %1<br>"
            "ID: %2<br>"
            "DeviceClass ID: %3<br>").arg(m_thing->name()).arg(m_thing->id().toString()).arg(m_thing->thingClassId().toString());
    body.append("</body>");
    body.append("</html>\n");

    return generateHeader() + body.toUtf8();
}
