// SPDX-FileCopyrightText: 2024 g10 Code GmbH
// SPDX-FileContributor: Carl Schwan <carl.schwan@gnupg.com>
// SPDX-License-Identifier: GPL-3.0-or-later

#include "userslistmodel.h"

#include <QColor>
#include <QDir>
#include <QFileInfo>
#include <QFont>
#include <QLocale>

#include <KLocalizedString>

#include <QGpgME/Protocol>
#include <libkleo/formatting.h>

#include "gpgmehelpers.h"

Q_DECLARE_METATYPE(UserInfo)

UsersListModel::UsersListModel(const QList<QByteArray> &recipients, QObject *parent)
    : QAbstractListModel(parent)
    , m_recipients(recipients)
{
    generateUserList();
}

UsersListModel::~UsersListModel() = default;

QVariant UsersListModel::data(const QModelIndex &index, int role) const
{
    Q_ASSERT(checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid));

    const auto &user = m_users[index.row()];

    switch (role) {
    case Qt::DisplayRole: {
        QString userText = user.name + QLatin1Char('\n') + user.key_id;
        if (user.created.toSecsSinceEpoch() > 0) {
            userText += QLatin1Char(' ') + i18nc("time of key creation", "created %1", QLocale().toString(user.created, QLocale::ShortFormat));
        }
        if (user.expiry.toSecsSinceEpoch() > 0) {
            userText += QLatin1Char(' ') + i18nc("Time of key expiry", "expires %1", QLocale().toString(user.expiry, QLocale::ShortFormat));
        }
        return userText;
    }
    case NameRole:
        return user.name;
    case Qt::CheckStateRole:
        return user.enabled ? Qt::Checked : Qt::Unchecked;
    case Qt::UserRole:
        return QVariant::fromValue(user);
    case Qt::FontRole:
        if (user.have_secret) {
            QFont font;
            font.setFamily(font.defaultFamily());
            font.setBold(true);
            return font;
        }
        return {};
    case Qt::ForegroundRole:
        if (user.have_secret) {
            return QColor(Qt::blue);
        } else if (!user.isValid()) {
            return QColor(Qt::white);
        } else if (user.isExpired()) {
            return QColor(164, 0, 0);
        } else if (!user.fullyValid()) {
            return QColor(Qt::white);
        }
        return {};
    case Qt::BackgroundRole:
        if (user.have_secret) {
            return {}; // default
        } else if (!user.isValid()) {
            return QColor(164, 0, 0);
        } else if (user.isExpired()) {
            return {};
        } else if (!user.fullyValid()) {
            return QColor(164, 80, 0);
        }
        return {};
    default:
        return {};
    }
}

bool UsersListModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    Q_ASSERT(checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid));

    if (role != Qt::CheckStateRole) {
        return false;
    }

    auto user = m_users[index.row()];
    user.enabled = value == Qt::Checked;
    m_users[index.row()] = user;
    Q_EMIT dataChanged(index, index, {Qt::CheckStateRole});
    return true;
}

Qt::ItemFlags UsersListModel::flags(const QModelIndex &index) const
{
    Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
    if (index.isValid()) {
        return defaultFlags | Qt::ItemIsUserCheckable;
    }
    return defaultFlags;
}

int UsersListModel::rowCount(const QModelIndex &parent) const
{
    return parent.isValid() ? 0 : (int)m_users.size();
}

void UsersListModel::generateUserList()
{
    // Fetch all keys
    auto job = QGpgME::openpgp()->keyListJob();
    job->addMode(GpgME::KeyListMode::WithSecret);

    connect(job, &QGpgME::KeyListJob::result, this, &UsersListModel::slotUserListFetched);
    job->start({}, false);
}

static UserInfo toUserInfo(const GpgME::Key &key)
{
    UserInfo user;
    user.created = QDateTime::fromSecsSinceEpoch(key.subkey(0).creationTime());
    user.key_id = fromGpgmeCharStar(key.keyID());
    user.name = createCombinedNameString(key.userID(0));
    user.validity = key.userID(0).validityAsString();
    user.expiry = QDateTime::fromSecsSinceEpoch(key.subkey(0).expirationTime());
    user.have_secret = key.hasSecret();
    return user;
}

void UsersListModel::slotUserListFetched(const GpgME::KeyListResult &result,
                                         const std::vector<GpgME::Key> &keys,
                                         const QString &auditLogAsHtml,
                                         const GpgME::Error &auditLogError)
{
    Q_UNUSED(auditLogAsHtml);
    Q_UNUSED(&auditLogError);

    beginResetModel();
    m_users.clear();

    if (!isSuccess(result.error())) {
        Q_EMIT errorOccurred(Kleo::Formatting::errorAsString(result.error()));
        return;
    }

    std::transform(keys.cbegin(), keys.cend(), std::back_inserter(m_users), toUserInfo);

    if (!m_recipients.isEmpty()) {
        // Fetch all keys which are marked as recipients in the selected directory
        auto job = QGpgME::openpgp()->keyListJob();
        job->addMode(GpgME::KeyListMode::WithSecret);

        QStringList recipientsList;
        std::transform(m_recipients.cbegin(), m_recipients.cend(), std::back_inserter(recipientsList), [](auto byteArray) {
            return QString::fromUtf8(byteArray);
        });

        connect(job,
                &QGpgME::KeyListJob::result,
                this,
                [this, recipientsList](const GpgME::KeyListResult &result,
                                       const std::vector<GpgME::Key> &keys,
                                       const QString &auditLogAsHtml,
                                       const GpgME::Error &auditLogError) {
                    Q_UNUSED(auditLogAsHtml);
                    Q_UNUSED(&auditLogError);
                    slotRecipientsFetched(result, keys, recipientsList);
                });
        job->start(recipientsList, false);
    } else {
        // No more keys to fetch
        Q_EMIT finishedKeysFetching();
    }

    endResetModel();
}

void UsersListModel::slotRecipientsFetched(const GpgME::KeyListResult &result, const std::vector<GpgME::Key> &keys, const QStringList &recipients)
{
    std::vector<UserInfo> selectedUsers;

    if (!isSuccess(result.error())) {
        Q_EMIT errorOccurred(Kleo::Formatting::errorAsString(result.error()));
        return;
    }

    std::transform(keys.cbegin(), keys.cend(), std::back_inserter(selectedUsers), toUserInfo);

    QStringList missingRecipients = recipients;

    for (const UserInfo &selelectedUser : std::as_const(selectedUsers)) {
        int row = 0;
        missingRecipients.removeAll(selelectedUser.key_id);
        for (auto &user : m_users) {
            if (selelectedUser.key_id == user.key_id) {
                user.enabled = true;
                Q_EMIT dataChanged(index(row, 0), index(row, 0), {Qt::CheckStateRole});
            }
        }
    }

    for (const QString &missingRecipient : missingRecipients) {
        UserInfo i;
        i.enabled = true;
        i.key_id = missingRecipient;
        i.name = QStringLiteral(" ?? ") + i18n("Key not found in keyring");
        beginInsertRows({}, m_users.size(), m_users.size());
        m_users.push_back(i);
        endInsertRows();
    }

    Q_EMIT finishedKeysFetching();
}

std::vector<UserInfo> UsersListModel::users() const
{
    return m_users;
}

FilterUsersListModel::FilterUsersListModel(QObject *parent)
    : QSortFilterProxyModel(parent)
{
    setFilterRole(UsersListModel::NameRole);
    setFilterCaseSensitivity(Qt::CaseInsensitive);
}

FilterUsersListModel::~FilterUsersListModel() = default;

bool FilterUsersListModel::showOnlyValidUser() const
{
    return m_showOnlyValidUser;
}

void FilterUsersListModel::setShowOnlyValidUser(bool state)
{
    m_showOnlyValidUser = state;
    invalidateFilter();
}

bool FilterUsersListModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{
    // filter by name
    auto ok = QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent);
    if (!ok) {
        return false;
    }

    if (!m_showOnlyValidUser) {
        return true;
    }

    const auto index = sourceModel()->index(sourceRow, 0, sourceParent);
    const auto user = index.data(Qt::UserRole).value<UserInfo>();

    return user.isValid() && !user.isExpired();
}
