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

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* nymea-remoteproxy
* Tunnel proxy server for the nymea remote access
*
* Copyright (C) 2013 - 2024, nymea GmbH
* Copyright (C) 2024 - 2025, chargebyte austria GmbH
*
* This file is part of nymea-remoteproxy.
*
* nymea-remoteproxy is free software: you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* nymea-remoteproxy is 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with nymea-remoteproxy. If not, see <https://www.gnu.org/licenses/>.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include "proxyjsonrpcclient.h"
#include "proxyconnection.h"
#include "../common/slipdataprocessor.h"

#include <QJsonDocument>

Q_LOGGING_CATEGORY(dcRemoteProxyClientJsonRpc, "RemoteProxyClientJsonRpc")
Q_LOGGING_CATEGORY(dcRemoteProxyClientJsonRpcTraffic, "RemoteProxyClientJsonRpcTraffic")

namespace remoteproxyclient {

JsonRpcClient::JsonRpcClient(ProxyConnection *connection, QObject *parent) :
    QObject(parent),
    m_connection(connection)
{

}

JsonReply *JsonRpcClient::callHello()
{
    JsonReply *reply = new JsonReply(m_commandId, "RemoteProxy", "Hello", QVariantMap(), this);
    qCDebug(dcRemoteProxyClientJsonRpc()) << "Calling" << QString("%1.%2").arg(reply->nameSpace()).arg(reply->method());
    sendRequest(reply->requestMap());
    m_replies.insert(m_commandId, reply);
    return reply;
}

JsonReply *JsonRpcClient::callRegisterServer(const QUuid &serverUuid, const QString &serverName)
{
    QVariantMap params;
    params.insert("serverName", serverName);
    params.insert("serverUuid", serverUuid.toString());

    JsonReply *reply = new JsonReply(m_commandId, "TunnelProxy", "RegisterServer", params, this);
    qCDebug(dcRemoteProxyClientJsonRpc()) << "Calling" << QString("%1.%2").arg(reply->nameSpace()).arg(reply->method());
    sendRequest(reply->requestMap());
    m_replies.insert(m_commandId, reply);
    return reply;
}

JsonReply *JsonRpcClient::callRegisterClient(const QUuid &clientUuid, const QString &clientName, const QUuid &serverUuid)
{
    QVariantMap params;
    params.insert("clientUuid", clientUuid);
    params.insert("clientName", clientName);
    params.insert("serverUuid", serverUuid.toString());

    JsonReply *reply = new JsonReply(m_commandId, "TunnelProxy", "RegisterClient", params, this);
    qCDebug(dcRemoteProxyClientJsonRpc()) << "Calling" << QString("%1.%2").arg(reply->nameSpace()).arg(reply->method());
    sendRequest(reply->requestMap());
    m_replies.insert(m_commandId, reply);
    return reply;
}

JsonReply *JsonRpcClient::callDisconnectClient(quint16 socketAddress)
{
    QVariantMap params;
    params.insert("socketAddress", socketAddress);

    JsonReply *reply = new JsonReply(m_commandId, "TunnelProxy", "DisconnectClient", params, this);
    qCDebug(dcRemoteProxyClientJsonRpc()) << "Calling" << QString("%1.%2").arg(reply->nameSpace()).arg(reply->method());
    sendRequest(reply->requestMap(), true);
    m_replies.insert(m_commandId, reply);
    return reply;
}

JsonReply *JsonRpcClient::callPing(uint timestamp)
{
    QVariantMap params;
    params.insert("timestamp", timestamp);

    JsonReply *reply = new JsonReply(m_commandId, "TunnelProxy", "Ping", params, this);
    qCDebug(dcRemoteProxyClientJsonRpc()) << "Calling" << QString("%1.%2").arg(reply->nameSpace()).arg(reply->method());
    sendRequest(reply->requestMap(), true);
    m_replies.insert(m_commandId, reply);
    return reply;
}

void JsonRpcClient::sendRequest(const QVariantMap &request, bool slipEnabled)
{
    QByteArray data = QJsonDocument::fromVariant(request).toJson(QJsonDocument::Compact) + "\n";

    if (slipEnabled) {
        SlipDataProcessor::Frame frame;
        frame.socketAddress = 0x0000;
        frame.data = data;
        data = SlipDataProcessor::serializeData(SlipDataProcessor::buildFrame(frame));
    }

    qCDebug(dcRemoteProxyClientJsonRpcTraffic()) << "Sending" << qUtf8Printable(data);
    m_connection->sendData(data);
}

void JsonRpcClient::processDataPacket(const QByteArray &data)
{
    QJsonParseError error;
    QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
    if (error.error != QJsonParseError::NoError) {
        qCWarning(dcRemoteProxyClientJsonRpc()) << "Invalid JSON data recived" << error.errorString();
        return;
    }

    QVariantMap dataMap = jsonDoc.toVariant().toMap();

    qCDebug(dcRemoteProxyClientJsonRpcTraffic()) << "Data received" << qUtf8Printable(data);

    // check if this is a reply to a request
    int commandId = dataMap.value("id").toInt();
    JsonReply *reply = m_replies.take(commandId);
    if (reply) {
        qCDebug(dcRemoteProxyClientJsonRpcTraffic()) << QString("Got response for %1.%2: %3").arg(reply->nameSpace(), reply->method(), QString::fromUtf8(jsonDoc.toJson(QJsonDocument::Indented)));

        if (dataMap.value("status").toString() == "error") {
            qCWarning(dcRemoteProxyClientJsonRpc()) << "Api error happend" << dataMap.value("error").toString();
            // FIMXME: handle json layer errors
        }

        reply->setResponse(dataMap);
        emit reply->finished();
        return;
    }

    // check if this is a notification
    if (dataMap.contains("notification")) {
        QStringList notification = dataMap.value("notification").toString().split(".");
        QString nameSpace = notification.first();
        QString notificationName = notification.last();
        QVariantMap notificationParams = dataMap.value("params").toMap();

        qCDebug(dcRemoteProxyClientJsonRpc()) << "Notification received" << nameSpace << notificationName;

        if (nameSpace == "RemoteProxy" && notificationName == "TunnelEstablished") {
            QString clientName = notificationParams.value("name").toString();
            QString clientUuid = notificationParams.value("uuid").toString();
            emit tunnelEstablished(clientName, clientUuid);
        } else if (nameSpace == "TunnelProxy" && notificationName == "ClientConnected") {
            QString clientName = notificationParams.value("clientName").toString();
            QUuid clientUuid = notificationParams.value("clientUuid").toUuid();
            QString clientPeerAddress = notificationParams.value("clientPeerAddress").toString();
            quint16 socketAddress = static_cast<quint16>(notificationParams.value("socketAddress").toUInt());
            emit tunnelProxyClientConnected(clientName, clientUuid, clientPeerAddress, socketAddress);
        } else if (nameSpace == "TunnelProxy" && notificationName == "ClientDisconnected") {
            quint16 socketAddress = static_cast<quint16>(notificationParams.value("socketAddress").toUInt());
            emit tunnelProxyClientDisonnected(socketAddress);
        }
    }
}

void JsonRpcClient::processData(const QByteArray &data)
{
    qCDebug(dcRemoteProxyClientJsonRpcTraffic()) << "Received data:" << data;

    // Handle packet fragmentation
    m_dataBuffer.append(data);
    int splitIndex = m_dataBuffer.indexOf("}\n{");
    while (splitIndex > -1) {
        processDataPacket(m_dataBuffer.left(splitIndex + 1));
        m_dataBuffer = m_dataBuffer.right(m_dataBuffer.length() - splitIndex - 2);
        splitIndex = m_dataBuffer.indexOf("}\n{");
    }
    if (m_dataBuffer.endsWith("}\n") || m_dataBuffer.endsWith("}")) {
        processDataPacket(m_dataBuffer);
        m_dataBuffer.clear();
    }
}

}
