#include "user_service.h"

#include <QIcon>
#include <QDebug>
#include <QDBusConnection>
#include <QDBusInterface>
#include <QDBusReply>
#include <QDBusArgument>
#include <QDBusObjectPath>
#include <QDBusMessage>
#include <QFileInfo>

UserService::UserService(QObject* parent)
    : QObject(parent) {}

static bool userRecordEquals(const UserRecord& a, const UserRecord& b) {
    return a.username == b.username &&
           a.realName == b.realName &&
           a.isAdmin == b.isAdmin &&
           a.uid == b.uid &&
           a.iconPath == b.iconPath;
}

QList<UserRecord> UserService::fetchUsers() {
    auto list = fetchViaDBus();
    if (!list.isEmpty()) return list;
    return fetchMock();
}

QList<UserRecord> UserService::fetchViaDBus() {
    QList<UserRecord> list;

    // org.freedesktop.Accounts list cached users
    QDBusInterface mgr("org.freedesktop.Accounts",
                       "/org/freedesktop/Accounts",
                       "org.freedesktop.Accounts",
                       QDBusConnection::systemBus());
    if (!mgr.isValid()) {
        qDebug() << "UserService: Accounts DBus manager invalid:" << QDBusConnection::systemBus().lastError();
        return list;
    }

    QDBusReply<QList<QDBusObjectPath>> reply = mgr.call("ListCachedUsers");
    if (!reply.isValid()) {
        qDebug() << "UserService: ListCachedUsers failed:" << reply.error();
        return list;
    }

    const auto paths = reply.value();

    QHash<QString, QString> newPathByName;
    QHash<QString, UserRecord> newUsersByName;
    for (const auto& op : paths) {
        // Read all properties of org.freedesktop.Accounts.User
        QDBusInterface props("org.freedesktop.Accounts",
                             op.path(),
                             "org.freedesktop.DBus.Properties",
                             QDBusConnection::systemBus());
        if (!props.isValid()) continue;
        QDBusReply<QVariant> r = props.call("Get", "org.freedesktop.Accounts.User", "UserName");
        QDBusReply<QVariant> r2 = props.call("Get", "org.freedesktop.Accounts.User", "RealName");
        QDBusReply<QVariant> r3 = props.call("Get", "org.freedesktop.Accounts.User", "IconFile");
        QDBusReply<QVariant> rUid = props.call("Get", "org.freedesktop.Accounts.User", "Uid");
        QDBusReply<QVariant> rType = props.call("Get", "org.freedesktop.Accounts.User", "AccountType");
        if (!r.isValid()) continue;
        UserRecord u;
        u.username = r.value().toString();
        u.realName = r2.isValid() ? r2.value().toString() : u.username;
            u.isAdmin = rType.isValid() ? (rType.value().toInt() > 0) : false;
        u.uid = rUid.isValid() ? rUid.value().toInt() : -1;
        u.iconPath = r3.isValid() ? r3.value().toString() : QString();

        if (u.iconPath.isEmpty() || !QFileInfo::exists(u.iconPath)) {
            // try theme fallback
            QIcon ic = QIcon::fromTheme("avatar-default");
            if (!ic.isNull()) {
                // store path hint for UI layer if needed; else leave empty and let UI fallback
            }
        }
        list.push_back(u);
        newPathByName[u.username] = op.path();
        newUsersByName[u.username] = u;
    }

    m_userPathByName = std::move(newPathByName);
    m_usersByName = std::move(newUsersByName);

    // Start monitoring if not started
    if (!m_monitoringStarted) {
        connectSignals();
        m_monitoringStarted = true;
    }
    return list;
}

QList<UserRecord> UserService::fetchMock() {
    QList<UserRecord> list;
    // Mirror the demo data from AccountsPlugin::pluginUi() with neutral names
        UserRecord a; a.username = "user1"; a.realName = QString::fromLatin1("User One"); a.isAdmin = true; list << a;
        UserRecord b; b.username = "user2"; b.realName = QString::fromLatin1("User Two"); b.isAdmin = false; list << b;
    return list;
}

void UserService::connectSignals() {
    // Watch for manager-level add/remove events
    QDBusConnection::systemBus().connect("org.freedesktop.Accounts",
                                         "/org/freedesktop/Accounts",
                                         "org.freedesktop.Accounts",
                                         "UserAdded",
                                         this,
                                         SLOT(handleUserAdded(QDBusObjectPath)));
    QDBusConnection::systemBus().connect("org.freedesktop.Accounts",
                                         "/org/freedesktop/Accounts",
                                         "org.freedesktop.Accounts",
                                         "UserDeleted",
                                         this,
                                         SLOT(handleUserRemoved(QDBusObjectPath)));

    // For existing users, listen to 'Changed' on user objects via queued lambda using QObject per path is complex; use PropertiesChanged wildcard
    // Subscribe to all PropertiesChanged for the service and filter by our map
    // Listen to org.freedesktop.Accounts.User::Changed on any user path; slot receives QDBusMessage so we can inspect path
    QDBusConnection::systemBus().connect("org.freedesktop.Accounts",
                                         QString(),
                                         "org.freedesktop.Accounts.User",
                                         "Changed",
                                         this,
                                         SLOT(onUserChangedMsg(QDBusMessage)));
}

// Helper: fetch by object path
UserRecord UserService::fetchViaPath(const QString& path) {
    UserRecord u;
    QDBusInterface props("org.freedesktop.Accounts",
                         path,
                         "org.freedesktop.DBus.Properties",
                         QDBusConnection::systemBus());
    if (!props.isValid()) return u;
    QDBusReply<QVariant> r = props.call("Get", "org.freedesktop.Accounts.User", "UserName");
    QDBusReply<QVariant> r2 = props.call("Get", "org.freedesktop.Accounts.User", "RealName");
    QDBusReply<QVariant> r3 = props.call("Get", "org.freedesktop.Accounts.User", "IconFile");
    QDBusReply<QVariant> r4 = props.call("Get", "org.freedesktop.Accounts.User", "Uid");
    QDBusReply<QVariant> r5 = props.call("Get", "org.freedesktop.Accounts.User", "AccountType");
    if (!r.isValid()) return u;
    u.username = r.value().toString();
    u.realName = r2.isValid() ? r2.value().toString() : u.username;
        u.isAdmin = r5.isValid() ? (r5.value().toInt() > 0) : false;
    u.uid = r4.isValid() ? r4.value().toInt() : -1;
    u.iconPath = r3.isValid() ? r3.value().toString() : QString();
    return u;
}

void UserService::handleUserAdded(const QDBusObjectPath& path) {
    UserRecord u = fetchViaPath(path.path());
    if (!u.username.isEmpty()) {
        // Idempotent: if UI already refreshed and has this user, treat as update.
        const bool existed = m_usersByName.contains(u.username);
        m_userPathByName[u.username] = path.path();
        m_usersByName[u.username] = u;
        if (existed) emit userUpdated(u);
        else emit userAdded(u);
    }
}

void UserService::handleUserRemoved(const QDBusObjectPath& path) {
    // Find username by path
    QString username;
    for (auto it = m_userPathByName.begin(); it != m_userPathByName.end(); ++it) {
        if (it.value() == path.path()) { username = it.key(); m_userPathByName.erase(it); break; }
    }
    if (!username.isEmpty()) {
        m_usersByName.remove(username);
        emit userRemoved(username);
    }
}

void UserService::handleUserChanged(const QDBusObjectPath& path) {
    // Fetch only necessary properties to reduce bus traffic
    QDBusInterface props("org.freedesktop.Accounts",
                         path.path(),
                         "org.freedesktop.DBus.Properties",
                         QDBusConnection::systemBus());
    if (!props.isValid()) return;
    QDBusReply<QVariant> rUser = props.call("Get", "org.freedesktop.Accounts.User", "UserName");
    QDBusReply<QVariant> rReal = props.call("Get", "org.freedesktop.Accounts.User", "RealName");
    QDBusReply<QVariant> rUid = props.call("Get", "org.freedesktop.Accounts.User", "Uid");
    QDBusReply<QVariant> rIcon = props.call("Get", "org.freedesktop.Accounts.User", "IconFile");
    QDBusReply<QVariant> rType = props.call("Get", "org.freedesktop.Accounts.User", "AccountType");
    if (!rUser.isValid()) return;

    UserRecord u;
    u.username = rUser.value().toString();
    u.realName = rReal.isValid() ? rReal.value().toString() : u.username;
    u.uid = rUid.isValid() ? rUid.value().toInt() : -1;
    u.iconPath = rIcon.isValid() ? rIcon.value().toString() : QString();
    u.isAdmin = rType.isValid() ? (rType.value().toInt() > 0) : false;

    if (!u.username.isEmpty()) {
        m_userPathByName[u.username] = path.path();
        m_usersByName[u.username] = u;
        emit userUpdated(u);
    }
}

void UserService::onUserChangedMsg(const QDBusMessage& msg) {
    handleUserChanged(QDBusObjectPath(msg.path()));
}

void UserService::startMonitoring() {
    if (m_monitoringStarted) return;
    connectSignals();
    m_monitoringStarted = true;
}

void UserService::refresh() {
    // Ensure we are connected so subsequent changes are incremental.
    startMonitoring();

    const auto oldUsers = m_usersByName;

    // This updates m_usersByName and m_userPathByName as a side-effect.
    const auto list = fetchViaDBus();
    if (list.isEmpty()) {
        // DBus not available; do not emit destructive diffs based on mock.
        return;
    }

    // Removed
    for (auto it = oldUsers.begin(); it != oldUsers.end(); ++it) {
        if (!m_usersByName.contains(it.key())) {
            emit userRemoved(it.key());
        }
    }

    // Added/Updated
    for (auto it = m_usersByName.begin(); it != m_usersByName.end(); ++it) {
        const QString& username = it.key();
        const UserRecord& now = it.value();
        if (!oldUsers.contains(username)) {
            emit userAdded(now);
        } else {
            const UserRecord& before = oldUsers.value(username);
            if (!userRecordEquals(before, now)) {
                emit userUpdated(now);
            }
        }
    }
}

UserRecord UserService::fetchSingleByName(const QString& username) {
    const QString path = m_userPathByName.value(username);
    if (path.isEmpty()) return UserRecord{};
    return fetchViaPath(path);
}
