// 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 "tunnelproxyserver.h"
#include "loggingcategories.h"

#include "jsonrpc/tunnelproxyhandler.h"
#include "tunnelproxyserverconnection.h"
#include "tunnelproxyclientconnection.h"

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

namespace remoteproxy {

TunnelProxyServer::TunnelProxyServer(QObject *parent) :
    QObject(parent)
{
    m_jsonRpcServer = new JsonRpcServer(this);
    m_jsonRpcServer->registerHandler(m_jsonRpcServer);
    m_jsonRpcServer->registerHandler(new TunnelProxyHandler(this));
}

TunnelProxyServer::~TunnelProxyServer()
{

}

bool TunnelProxyServer::running() const
{
    return m_running;
}

void TunnelProxyServer::setRunning(bool running)
{
    if (m_running == running)
        return;

    qCDebug(dcTunnelProxyServer()) << "The proxy tunnel manager is" << (running ? "now up and running." : "not running any more.");
    m_running = running;
    emit runningChanged(m_running);
}

void TunnelProxyServer::registerTransportInterface(TransportInterface *interface)
{
    qCDebug(dcTunnelProxyServer()) << "Register transport interface" << interface->serverName();
    //interface->setParent(this);
    if (m_transportInterfaces.contains(interface)) {
        qCWarning(dcTunnelProxyServer()) << "Transport interface already registerd.";
        return;

    }

    connect(interface, &TransportInterface::clientConnected, this, &TunnelProxyServer::onClientConnected);
    connect(interface, &TransportInterface::clientDisconnected, this, &TunnelProxyServer::onClientDisconnected);
    connect(interface, &TransportInterface::dataAvailable, this, &TunnelProxyServer::onClientDataAvailable);

    m_transportInterfaces.append(interface);
}

TunnelProxyServer::TunnelProxyError TunnelProxyServer::registerServer(const QUuid &clientId, const QUuid &serverUuid, const QString &serverName)
{
    qCDebug(dcTunnelProxyServer()) << "Register new server" << m_proxyClients.value(clientId) << serverName << serverUuid.toString();

    // Make sure we have a proxy client for this id
    TunnelProxyClient *tunnelProxyClient = m_proxyClients.value(clientId);
    if (!tunnelProxyClient) {
        qCWarning(dcTunnelProxyServer()) << "There is no client with client uuid" << clientId.toString();
        return TunnelProxyServer::TunnelProxyErrorInternalServerError;
    }

    // Make sure this client has not been registered as client or re-registration has been called...
    if (tunnelProxyClient->type() != TunnelProxyClient::TypeNone) {
        qCWarning(dcTunnelProxyServer()) << "Client tried to register as server but has already been registerd as" << tunnelProxyClient->type();
        tunnelProxyClient->killConnectionAfterResponse("Already registered");
        return TunnelProxyServer::TunnelProxyErrorAlreadyRegistered;
    }

    // Make sure there is no server trying to make multiple server tunnel connections. We allow only one
    if (m_tunnelProxyServerConnections.contains(serverUuid)) {
        qCWarning(dcTunnelProxyServer()) << "Client tried to register as server" << tunnelProxyClient << "but there is already a server registered with this server uuid:" << serverUuid.toString();
        tunnelProxyClient->killConnectionAfterResponse("Already registered");
        return TunnelProxyServer::TunnelProxyErrorAlreadyRegistered;
    }

    // Also make sure this uuid has not been alreay used for any client connections...
    if (m_tunnelProxyClientConnections.contains(serverUuid)) {
        qCWarning(dcTunnelProxyServer()) << "Client tried to register as server" << tunnelProxyClient << "but there is already a client connection using this server uuid:" << serverUuid.toString();
        tunnelProxyClient->killConnectionAfterResponse("UUID cross registeration");
        return TunnelProxyServer::TunnelProxyErrorAlreadyRegistered;
    }

    tunnelProxyClient->setType(TunnelProxyClient::TypeServer);
    tunnelProxyClient->setUuid(serverUuid);
    tunnelProxyClient->setName(serverName);

    // This client has been registered successfully.
    // Make sure it does not get disconnected any more because of inactivity.
    tunnelProxyClient->activateClient();

    // Enable SLIP from now on
    tunnelProxyClient->enableSlipAfterResponse();

    TunnelProxyServerConnection *serverConnection = new TunnelProxyServerConnection(tunnelProxyClient, serverUuid, serverName, tunnelProxyClient);
    m_tunnelProxyServerConnections.insert(serverUuid, serverConnection);
    qCDebug(dcTunnelProxyServer()) << "New server connection registered successfully" << serverConnection;

    return TunnelProxyServer::TunnelProxyErrorNoError;
}

TunnelProxyServer::TunnelProxyError TunnelProxyServer::registerClient(const QUuid &clientId, const QUuid &clientUuid, const QString &clientName, const QUuid &serverUuid)
{
    TunnelProxyClient *tunnelProxyClient = m_proxyClients.value(clientId);
    if (!tunnelProxyClient) {
        qCWarning(dcTunnelProxyServer()) << "There is no client with client uuid" << clientId.toString();
        return TunnelProxyServer::TunnelProxyErrorInternalServerError;
    }

    // Make sure this client has not been registered as client or re-registration has been called...
    if (tunnelProxyClient->type() != TunnelProxyClient::TypeNone) {
        qCWarning(dcTunnelProxyServer()) << "Client tried to register as client but has already been registerd as" << tunnelProxyClient->type();
        tunnelProxyClient->killConnectionAfterResponse("Already registered");
        return TunnelProxyServer::TunnelProxyErrorAlreadyRegistered;
    }

    if (m_tunnelProxyClientConnections.contains(clientUuid)) {
        qCWarning(dcTunnelProxyServer()) << "There is a client already registered with client uuid" << clientUuid.toString();
        tunnelProxyClient->killConnectionAfterResponse("Already registered");
        return TunnelProxyServer::TunnelProxyErrorAlreadyRegistered;
    }

    // Also make sure this uuid has not been alreay used for any client connections...
    if (m_tunnelProxyServerConnections.contains(clientUuid)) {
        qCWarning(dcTunnelProxyServer()) << "Client tried to register as client" << tunnelProxyClient << "but there is already a server connection using this client uuid:" << clientUuid.toString();
        tunnelProxyClient->killConnectionAfterResponse("UUID cross registeration");
        return TunnelProxyServer::TunnelProxyErrorAlreadyRegistered;
    }


    // Get the desired server connection
    TunnelProxyServerConnection *serverConnection = m_tunnelProxyServerConnections.value(serverUuid);
    if (!serverConnection) {
        qCWarning(dcTunnelProxyServer()) << "There is no server registered with server uuid" << serverUuid.toString();
        tunnelProxyClient->killConnectionAfterResponse("Unknown server");
        return TunnelProxyServer::TunnelProxyErrorServerNotFound;
    }


    // Not registered yet, we have a connected server for the requested server uuid
    tunnelProxyClient->setType(TunnelProxyClient::TypeClient);
    tunnelProxyClient->setUuid(clientUuid);
    tunnelProxyClient->setName(clientName);

    // This client has been registered successfully.
    // Make sure it does not get disconnected any more because due to inactivity.
    tunnelProxyClient->activateClient();

    TunnelProxyClientConnection *clientConnection = new TunnelProxyClientConnection(tunnelProxyClient, clientUuid, clientName, this);
    clientConnection->setServerConnection(serverConnection);
    m_tunnelProxyClientConnections.insert(clientUuid, clientConnection);

    serverConnection->registerClientConnection(clientConnection);
    qCDebug(dcTunnelProxyServer()) << "New client connection registered successfully" << clientConnection << "-->" << serverConnection;;

    // Tell the server a new client want's to connect
    QVariantMap params;
    params.insert("clientName", tunnelProxyClient->name());
    params.insert("clientUuid", tunnelProxyClient->uuid().toString());
    params.insert("clientPeerAddress", tunnelProxyClient->peerAddress().toString());
    params.insert("socketAddress", clientConnection->socketAddress());
    m_jsonRpcServer->sendNotification("TunnelProxy", "ClientConnected", params, serverConnection->transportClient());

    // Note: check if a confirmation from the server would be needed, for rejection or limit or something. For now they are directly connected.

    return TunnelProxyServer::TunnelProxyErrorNoError;
}

TunnelProxyServer::TunnelProxyError TunnelProxyServer::disconnectClient(const QUuid &clientId, quint16 socketAddress)
{
    TunnelProxyClient *tunnelProxyClient = m_proxyClients.value(clientId);
    if (!tunnelProxyClient) {
        qCWarning(dcTunnelProxyServer()) << "There is no client with client uuid" << clientId.toString();
        return TunnelProxyServer::TunnelProxyErrorInternalServerError;
    }

    if (tunnelProxyClient->type() != TunnelProxyClient::TypeServer) {
        qCWarning(dcTunnelProxyServer()) << "Client tried to disconnect a client but has not been registerd as server" << tunnelProxyClient->type();
        tunnelProxyClient->killConnectionAfterResponse("Already registered");
        return TunnelProxyServer::TunnelProxyErrorAlreadyRegistered;
    }

    TunnelProxyServerConnection *serverConnection = m_tunnelProxyServerConnections.value(tunnelProxyClient->uuid());
    if (!serverConnection) {
        qCWarning(dcTunnelProxyServer()) << "Could not find server connection for" << tunnelProxyClient;
        tunnelProxyClient->killConnectionAfterResponse("Internal server error");
        return TunnelProxyServer::TunnelProxyErrorInternalServerError;
    }

    TunnelProxyClientConnection *clientConnection = serverConnection->getClientConnection(socketAddress);
    if (!clientConnection) {
        qCWarning(dcTunnelProxyServer()) << "Could not find client connection for socket address" << socketAddress << "on" << serverConnection;
        return TunnelProxyServer::TunnelProxyErrorUnknownSocketAddress;
    }

    clientConnection->transportClient()->killConnection("Server requested disconnect.");
    return TunnelProxyServer::TunnelProxyErrorNoError;
}

QVariantMap TunnelProxyServer::currentStatistics(bool printAll)
{
    QVariantMap statisticsMap;
    statisticsMap.insert("totalClientCount", m_proxyClients.count());
    statisticsMap.insert("serverConnectionsCount", m_tunnelProxyServerConnections.count());
    statisticsMap.insert("clientConnectionsCount", m_tunnelProxyClientConnections.count());
    QVariantMap transports;
    foreach (TransportInterface *transportInterface, m_transportInterfaces) {
        transports.insert(transportInterface->serverName(), transportInterface->connectionsCount());
    }
    statisticsMap.insert("transports", transports);
    statisticsMap.insert("troughput", m_troughput);

    QVariantList tunnelConnections;
    foreach (TunnelProxyServerConnection *serverConnection, m_tunnelProxyServerConnections) {

        // Show only active clients
        if (!printAll && serverConnection->clientConnections().isEmpty())
            continue;

        QVariantMap serverMap;
        serverMap.insert("id", serverConnection->transportClient()->clientId().toString());
        serverMap.insert("address", serverConnection->transportClient()->peerAddress().toString());
        serverMap.insert("timestamp", serverConnection->transportClient()->creationTime());
        serverMap.insert("name", serverConnection->transportClient()->name());
        serverMap.insert("serverUuid", serverConnection->transportClient()->uuid());
        serverMap.insert("rxDataCount", serverConnection->transportClient()->rxDataCount());
        serverMap.insert("txDataCount", serverConnection->transportClient()->txDataCount());

        QVariantList clientList;
        foreach (TunnelProxyClientConnection *clientConnection, serverConnection->clientConnections()) {
            QVariantMap clientMap;
            clientMap.insert("id", clientConnection->transportClient()->clientId().toString());
            clientMap.insert("address", clientConnection->transportClient()->peerAddress().toString());
            clientMap.insert("timestamp", clientConnection->transportClient()->creationTime());
            clientMap.insert("name", clientConnection->transportClient()->name());
            clientMap.insert("clientUuid", clientConnection->transportClient()->uuid());
            clientMap.insert("rxDataCount", clientConnection->transportClient()->rxDataCount());
            clientMap.insert("txDataCount", clientConnection->transportClient()->txDataCount());
            clientList.append(clientMap);
        }
        serverMap.insert("clientConnections", clientList);
        tunnelConnections.append(serverMap);
    }

    statisticsMap.insert("tunnelConnections", tunnelConnections);

    return statisticsMap;
}

void TunnelProxyServer::startServer()
{
    qCDebug(dcTunnelProxyServer()) << "Starting tunnel proxy...";
    foreach (TransportInterface *interface, m_transportInterfaces) {
        interface->startServer();
    }
    setRunning(true);
}

void TunnelProxyServer::stopServer()
{
    qCDebug(dcTunnelProxyServer()) << "Stopping tunnel proxy...";
    foreach (TransportInterface *interface, m_transportInterfaces) {
        interface->stopServer();
    }
    setRunning(false);
}

void TunnelProxyServer::tick()
{
    m_troughput = m_troughputCounter;
    m_troughputCounter = 0;
}

void TunnelProxyServer::onClientConnected(const QUuid &clientId, const QHostAddress &address)
{    
    TransportInterface *interface = static_cast<TransportInterface *>(sender());
    qCDebug(dcTunnelProxyServer()) << "New client connected" << interface->serverName() << address.toString() << clientId.toString();

    if (m_proxyClients.contains(clientId)) {
        qCWarning(dcTunnelProxyServer()) << "Internal error. A client with clientId" << clientId.toString() << "has already connected before. Terminate new connection.";
        interface->killClientConnection(clientId, "Internal server error");
        return;
    }

    TunnelProxyClient *tunnelProxyClient = new TunnelProxyClient(interface, clientId, address, this);
    m_proxyClients.insert(clientId, tunnelProxyClient);
    m_jsonRpcServer->registerClient(tunnelProxyClient);
}

void TunnelProxyServer::onClientDisconnected(const QUuid &clientId)
{
    TransportInterface *interface = static_cast<TransportInterface *>(sender());
    TunnelProxyClient *tunnelProxyClient = m_proxyClients.take(clientId);
    if (!tunnelProxyClient) {
        qCWarning(dcTunnelProxyServer()) << "Unknown client disconnected from proxy server." << clientId.toString();
        return;
    }

    if (tunnelProxyClient->type() == TunnelProxyClient::TypeServer) {
        TunnelProxyServerConnection *serverConnection = m_tunnelProxyServerConnections.take(tunnelProxyClient->uuid());
        if (!serverConnection) {
            qCWarning(dcTunnelProxyServer()) << "Could not find server connection for disconnected tunnel proxy client claiming to be a server.";
        } else {
            qCDebug(dcTunnelProxyServer()) << "Server connection disconnected" << interface->serverName() << clientId.toString();
            foreach (TunnelProxyClientConnection *clientConnection, serverConnection->clientConnections()) {
                serverConnection->unregisterClientConnection(clientConnection);
                clientConnection->transportClient()->killConnection("Server disconnected");
            }

            serverConnection->deleteLater();
        }
    }

    if (tunnelProxyClient->type() == TunnelProxyClient::TypeClient) {
        TunnelProxyClientConnection *clientConnection = m_tunnelProxyClientConnections.take(tunnelProxyClient->uuid());
        if (!clientConnection) {
            qCWarning(dcTunnelProxyServer()) << "Could not find client connection for disconnected tunnel proxy client claiming to be a client.";
        } else {
            TunnelProxyServerConnection *serverConnection = clientConnection->serverConnection();
            if (serverConnection) {
                QVariantMap params;
                params.insert("socketAddress", clientConnection->socketAddress());
                serverConnection->unregisterClientConnection(clientConnection);
                m_jsonRpcServer->sendNotification("TunnelProxy", "ClientDisconnected", params, serverConnection->transportClient());
            }

            clientConnection->deleteLater();
        }
    }

    // Unregister from json rpc server
    m_jsonRpcServer->unregisterClient(tunnelProxyClient);

    // Delete the proxy client
    tunnelProxyClient->deleteLater();
}

void TunnelProxyServer::onClientDataAvailable(const QUuid &clientId, const QByteArray &data)
{
    TunnelProxyClient *tunnelProxyClient = m_proxyClients.value(clientId);
    if (!tunnelProxyClient) {
        qCWarning(dcTunnelProxyServer()) << "Data received but could not find client for uuid" << clientId;
        return;
    }

    qCDebug(dcTunnelProxyServerTraffic()) << "Client data available" << tunnelProxyClient << qUtf8Printable(data);
    tunnelProxyClient->addRxDataCount(data.count());

    if (tunnelProxyClient->type() == TunnelProxyClient::TypeClient) {
        // Send the data to the server using slip encoded frame
        TunnelProxyClientConnection *clientConnection = m_tunnelProxyClientConnections.value(tunnelProxyClient->uuid());
        if (!clientConnection) {
            qCWarning(dcTunnelProxyServer()) << "Could not find a client connection for client uuid" << tunnelProxyClient->uuid().toString();
            // FIXME: check what do with this client
            return;
        }

        if (!clientConnection->serverConnection()) {
            qCWarning(dcTunnelProxyServer()) << "Valid client wants to send data to the server, but there is no server registered for this client.";
            Q_ASSERT_X(clientConnection->serverConnection(), "TunnelProxyServer", "The client has not been registered to a server connection");
            return;
        }

        SlipDataProcessor::Frame frame;
        frame.socketAddress = clientConnection->socketAddress();
        frame.data = data;
        qCDebug(dcTunnelProxyServerTraffic()) << "--> Tunnel data to server socket address" << clientConnection->socketAddress() << "to" << clientConnection->serverConnection() << "\n" << data;
        QByteArray rawData = SlipDataProcessor::serializeData(SlipDataProcessor::buildFrame(frame));
        clientConnection->serverConnection()->transportClient()->sendData(rawData);
        m_troughputCounter += data.count();

    } else if (tunnelProxyClient->type() == TunnelProxyClient::TypeServer) {
        // Data coming from a connected server connection
        if (tunnelProxyClient->slipEnabled()) {
            // Unpack SLIP data, get address, pipe to client or give it to the json rpc server if address 0x0000
            // Handle packet fragmentation
            QList<QByteArray> frames = tunnelProxyClient->processData(data);
            foreach (const QByteArray &frameData, frames) {
                SlipDataProcessor::Frame frame = SlipDataProcessor::parseFrame(frameData);

                if (frame.socketAddress == 0x0000) {
                    qCDebug(dcTunnelProxyServerTraffic()) << "Received frame for the JSON server" << tunnelProxyClient;
                    m_jsonRpcServer->processDataPacket(tunnelProxyClient, frame.data);
                } else {
                    // This data seems to be for a client with the given address
                    TunnelProxyServerConnection *serverConnection = m_tunnelProxyServerConnections.value(tunnelProxyClient->uuid());
                    if (!serverConnection) {
                        qCWarning(dcTunnelProxyServer()) << "Could not find server connection for" << tunnelProxyClient;
                        continue;
                    }

                    // Get the client and send the data to the client
                    TunnelProxyClientConnection *clientConnection = serverConnection->getClientConnection(frame.socketAddress);
                    if (!clientConnection) {
                        qCWarning(dcTunnelProxyServer()) << "The server connection wants to send data to a client connection which has not been registered to the server.";
                        continue;
                    }

                    qCDebug(dcTunnelProxyServerTraffic()) << "--> Tunnel data from server socket" << frame.socketAddress << "to" << clientConnection <<  "\n" << frame.data;
                    clientConnection->transportClient()->sendData(frame.data);
                    m_troughputCounter += frame.data.count();
                }
            }
        } else {
            m_jsonRpcServer->processData(tunnelProxyClient, data);
        }

    } else {
        // Not registered yet or doing other stuff...let the JSON RPC server handle this data
        m_jsonRpcServer->processData(tunnelProxyClient, data);
    }
}

}
