// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later

#include "qtdbushook.h"

#include "policy/policy.h"
#include "serviceqtdbus.h"

#include <QDBusConnectionInterface>
#include <QDBusMessage>
#include <QDebug>
#include <QFile>
#include <QLoggingCategory>
#include <QTimer>

#ifdef Q_DBUS_EXPORT
extern Q_DBUS_EXPORT void qDBusAddSpyHook(void (*)(const QDBusMessage &));
extern Q_DBUS_EXPORT void qDBusAddFilterHook(int (*)(const QString &, const QDBusMessage &));
#else
extern QDBUS_EXPORT void qDBusAddSpyHook(void (*)(const QDBusMessage &));
extern QDBUS_EXPORT void qDBusAddFilterHook(int (*)(const QString &, const QDBusMessage &));
#endif

#ifdef Q_DBUS_HOOK_FILTER
#  define HOOK_RESULT_TYPE int
#  define HOOK_RESULT_SUCCESS 0
#  define HOOK_RESULT_FAILED -1
#else
#  define HOOK_RESULT_TYPE void
#  define HOOK_RESULT_SUCCESS
#  define HOOK_RESULT_FAILED
#endif

Q_LOGGING_CATEGORY(dsm_hook_qt, "[QDBusHook]")

// if it is not a local message, hook exec at main thread
void QTDBusSpyHook(const QDBusMessage &msg)
{
    qCDebug(dsm_hook_qt) << "--msg=" << msg;

    ServiceBase *serviceObj = nullptr;
    bool isSubPath;
    QString realPath; // 子PATH可能没有配置，使用父PATH的配置
    bool findRet =
        QTDbusHook::instance()->getServiceObject("", msg.path(), &serviceObj, isSubPath, realPath);
    if (!findRet) {
        qCWarning(dsm_hook_qt) << "--can not find hook object: " << msg.path();
        return;
    }
    if (!serviceObj->isRegister()) {
        qCDebug(dsm_hook_qt) << "--to register dbus object: " << msg.path();
        serviceObj->registerService();
    }

    if (!serviceObj->policy->isResident() && !serviceObj->isLockTimer()) {
        qCDebug(dsm_hook_qt) << QString("--service: %1 will unregister in %2 minutes!")
                                   .arg(serviceObj->policy->name)
                                   .arg(serviceObj->policy->idleTime);
        QTimer::singleShot(0, serviceObj, SLOT(restartTimer()));
    }
    if (msg.member() == "Introspect" && msg.interface() == "org.freedesktop.DBus.Introspectable") {
        if (serviceObj->policy->checkPathHide(realPath)) {
            qCInfo(dsm_hook_qt) << "--call Introspect " << msg.path() << " is hided!";
            QList<QVariant> arguments;
            arguments << "";
            QDBusMessage reply = msg.createReply(arguments);
            ServiceQtDBus *srv = qobject_cast<ServiceQtDBus *>(serviceObj);
            if (srv) {
                srv->qDbusConnection().send(reply);
            }
        }
    } else if (msg.member() == "Set" && msg.interface() == "org.freedesktop.DBus.Properties") {
        // Property access control removed - all property access is now allowed
    } else if (msg.interface() != "org.freedesktop.DBus.Properties"
               && msg.interface() != "org.freedesktop.DBus.Introspectable"
               && msg.interface() != "org.freedesktop.DBus.Peer") {
        // Method access control removed - all method calls are now allowed
    }
    return;
}

// if it is not a local message, hook exec at main thread
int QTDBusHook(const QString &baseService, const QDBusMessage &msg)
{
    qCDebug(dsm_hook_qt) << "--baseService=" << baseService;
    qCDebug(dsm_hook_qt) << "--msg=" << msg;

    ServiceBase *serviceObj = nullptr;
    bool isSubPath;
    QString realPath; // 子PATH可能没有配置，使用父PATH的配置
    bool findRet =
        QTDbusHook::instance()->getServiceObject("", msg.path(), &serviceObj, isSubPath, realPath);
    if (!findRet) {
        qCWarning(dsm_hook_qt) << "--can not find hook object:" << msg.path();
        return 0;
    }
    if (!serviceObj->isRegister()) {
        qCDebug(dsm_hook_qt) << "--to register dbus object: " << msg.path();
        serviceObj->registerService();
    }

    if (!serviceObj->policy->isResident() && !serviceObj->isLockTimer()) {
        qCDebug(dsm_hook_qt) << QString("--service: %1 will unregister in %2 minutes!")
                                   .arg(serviceObj->policy->name)
                                   .arg(serviceObj->policy->idleTime);
        QTimer::singleShot(0, serviceObj, SLOT(restartTimer()));
    }
    if (msg.member() == "Introspect" && msg.interface() == "org.freedesktop.DBus.Introspectable") {
        if (serviceObj->policy->checkPathHide(realPath)) {
            qCInfo(dsm_hook_qt) << "--call Introspect " << msg.path() << " is hided!";
            QList<QVariant> arguments;
            arguments << "";
            QDBusMessage reply = msg.createReply(arguments);
            ServiceQtDBus *srv = qobject_cast<ServiceQtDBus *>(serviceObj);
            if (srv) {
                srv->qDbusConnection().send(reply);
            }
            //            ((ServiceQtDBus*)serviceObj)->qDbusConnection().send(reply);
        }
    } else if (msg.member() == "Set" && msg.interface() == "org.freedesktop.DBus.Properties") {
        // Property access control removed - all property access is now allowed
    } else if (msg.interface() != "org.freedesktop.DBus.Properties"
               && msg.interface() != "org.freedesktop.DBus.Introspectable"
               && msg.interface() != "org.freedesktop.DBus.Peer") {
        // Method access control removed - all method calls are now allowed
    }
    return 0;

    // test TODO
    //    if (msg.member() == "Register") {
    //        Policy ppp;
    //        ppp.Test();
    //        QList<QVariant> arguments;
    //        arguments << true << "sdvvv";

    //        QDBusMessage reply =
    //        msg.createErrorReply("com.deepin.services.Nooooooo", "The method
    //        call 'Register()' is not supported");
    //        QDBusConnection::connectToBus(QDBusConnection::SessionBus,
    //        "org.deepin.services.demo2").send(reply);
    //    }
}

Q_GLOBAL_STATIC(QTDbusHook, qtDBusHook)

QTDbusHook::QTDbusHook()
{
    qCDebug(dsm_hook_qt) << "qt hook register.";
#ifdef Q_DBUS_HOOK_FILTER
    qDBusAddFilterHook(QTDBusHook);
#else
    qDBusAddSpyHook(QTDBusSpyHook);
#endif
}

QTDbusHook *QTDbusHook::instance()
{
    return qtDBusHook;
}

bool QTDbusHook::getServiceObject(
    QString name, QString path, ServiceBase **service, bool &isSubPath, QString &realPath)
{
    Q_UNUSED(name) // TODO:QtDBus Hook 无法获取到name
    ServiceObjectMap::iterator iterService = m_serviceMap.find(path);
    if (iterService != m_serviceMap.end()) {
        *service = iterService.value();
        isSubPath = true;
        realPath = iterService.key();
        return true;
    }
    for (auto iter = m_serviceMap.begin(); iter != m_serviceMap.end(); ++iter) {
        if (path.startsWith(iter.key()) && iter.value()->policy->allowSubPath(iter.key())) {
            *service = iter.value();
            isSubPath = false;
            realPath = iter.key();
            return true;
        }
    }
    return false;
}

bool QTDbusHook::setServiceObject(ServiceBase *obj)
{
    QStringList paths = obj->policy->paths();
    for (auto path : paths) {
        ServiceObjectMap::iterator iterService = m_serviceMap.find(path);
        if (iterService != m_serviceMap.end()) {
            qCWarning(dsm_hook_qt) << "set service path failed, the object is existed: " << path;
            continue;
        }
        m_serviceMap[path] = obj;
    }
    return true;
}
