#include <QtTest>
#include <QSignalSpy>

#include "../../../src/notification/notification-model.h"
#include "stubs/applications-settings.h"
#include "stubs/notification-global-settings.h"
#include "stubs/notification-client.h"
#include "stubs/popup-notification.h"
#include "stubs/screen-monitor.h"

using namespace UkuiNotification;

class notificationModel_test : public QObject
{
    Q_OBJECT

private slots:
    void init();
    void test_instance_singleton();
    void test_roleNames_containsExpectedEntries();
    void test_onNotificationReceived_addsNotification();
    void test_onNotificationReceived_updatesExistingNotification();
    void test_onNotificationReceived_honorsGlobalAndAppSettings();
    void test_notificationData_roles_coverCoreFields();
    void test_setNotificationData_marksStored();
    void test_execAction_invokesClientAndRemovesNotification();
    void test_expirationAndStoreTransitions();
    void test_storePopupNotification_marksTransientAsStored();
    void test_clearAll_removesAllRows();
    void test_processedBody_usesLinkColor();
    void test_closeNotification_removesRow();

private:
    PopupNotification createNotification(uint id, const QString &body = QString("body"), int popupTimeout = 0);
    NotificationModel *model();
    void clearModel();
    bool sendNotification(const PopupNotification &notification);
};

PopupNotification notificationModel_test::createNotification(uint id, const QString &body, int popupTimeout)
{
    PopupNotification notification;
    notification.setId(id);
    notification.setDisplay(QString("display-%1").arg(id));
    notification.setApplicationName(QString("app-%1").arg(id));
    notification.setApplicationIconName(QString("icon-%1").arg(id));
    notification.setSummary(QString("summary-%1").arg(id));
    notification.setBody(body);
    notification.setCategory(QString("category-%1").arg(id));
    notification.setIcon(QString("pixmap-%1").arg(id));
    notification.setCreatedTime(QDateTime(QDate(2024, 1, 1), QTime(10, 0, 0).addSecs(id)));
    notification.setHasDefaultAction(true);
    notification.setEnableActionIcons(true);
    notification.setActions(ActionList{{"default", "Open"}, {"reply", "Reply"}});
    notification.setActionState(QStringList{"ready", "idle"});
    notification.setSuppressSound(false);
    notification.setResident(false);
    notification.setTransient(true);
    notification.setUrgency(PopupNotification::CriticalUrgency);
    notification.setPopupTimeout(popupTimeout);
    notification.setTimeout(0);
    return notification;
}

NotificationModel *notificationModel_test::model()
{
    return NotificationModel::instance();
}

void notificationModel_test::clearModel()
{
    if (model()->rowCount(QModelIndex()) > 0) {
        model()->clearAll();
    }
    NotificationClient::reset();
    ApplicationsSettings::settings()->setAllowNotify(true);
    ApplicationsSettings::settings()->setAllowSound(false);
    ApplicationsSettings::settings()->setShowNotificationOnLockScreen(true);
    ApplicationsSettings::settings()->setShowContentOnLockScreen(true);
    Sidebar::ScreenMonitor::getInstance()->setScreenLockState(false);
    if (NotificationGlobalSettings::lastInstance()) {
        NotificationGlobalSettings::lastInstance()->setReceiveNotificationsFromApps(true);
        NotificationGlobalSettings::lastInstance()->setDND(false);
        NotificationGlobalSettings::lastInstance()->setNotifyAlarmWhileDND(false);
    }
}

bool notificationModel_test::sendNotification(const PopupNotification &notification)
{
    return QMetaObject::invokeMethod(model(),
                                     "onNotificationReceived",
                                     Qt::DirectConnection,
                                     Q_ARG(UkuiNotification::PopupNotification, notification));
}

void notificationModel_test::init()
{
    clearModel();
}

void notificationModel_test::test_instance_singleton()
{
    QCOMPARE(model(), NotificationModel::instance());
}

void notificationModel_test::test_roleNames_containsExpectedEntries()
{
    QHash<int, QByteArray> roles = model()->roleNames();

    QCOMPARE(roles.value(NotificationItem::Id), QByteArray("id"));
    QCOMPARE(roles.value(NotificationItem::Summary), QByteArray("summary"));
    QCOMPARE(roles.value(NotificationItem::ProcessedBody), QByteArray("processedBody"));
}

void notificationModel_test::test_onNotificationReceived_addsNotification()
{
    PopupNotification notification = createNotification(1, "plain", 0);

    QVERIFY(sendNotification(notification));

    QCOMPARE(model()->rowCount(QModelIndex()), 1);
    QCOMPARE(model()->data(model()->index(0, 0), NotificationItem::Id).toUInt(), 1u);
    QCOMPARE(model()->data(model()->index(0, 0), NotificationItem::AppName).toString(), QString("app-1"));
    QCOMPARE(model()->data(model()->index(0, 0), NotificationItem::HasDefaultAction).toBool(), true);
    QCOMPARE(model()->data(model()->index(0, 0), NotificationItem::Actions).toList().size(), 2);
}

void notificationModel_test::test_onNotificationReceived_updatesExistingNotification()
{
    PopupNotification notification = createNotification(11, "first", 0);
    QVERIFY(sendNotification(notification));

    notification.setSummary("summary-updated");
    notification.setBody("body-updated");
    QSignalSpy spy(model(), &QAbstractItemModel::dataChanged);

    QVERIFY(sendNotification(notification));

    QCOMPARE(model()->rowCount(QModelIndex()), 1);
    QVERIFY(spy.count() >= 1);
    QCOMPARE(model()->data(model()->index(0, 0), NotificationItem::Summary).toString(), QString("summary-updated"));
    QCOMPARE(model()->data(model()->index(0, 0), NotificationItem::Body).toString(), QString("body-updated"));
}

void notificationModel_test::test_onNotificationReceived_honorsGlobalAndAppSettings()
{
    PopupNotification notification = createNotification(12, "plain", 0);

    NotificationGlobalSettings::lastInstance()->setReceiveNotificationsFromApps(false);
    QVERIFY(sendNotification(notification));
    QCOMPARE(model()->rowCount(QModelIndex()), 0);

    NotificationGlobalSettings::lastInstance()->setReceiveNotificationsFromApps(true);
    ApplicationsSettings::settings()->setAllowNotify(false);
    QVERIFY(sendNotification(notification));
    QCOMPARE(model()->rowCount(QModelIndex()), 0);
}

void notificationModel_test::test_notificationData_roles_coverCoreFields()
{
    PopupNotification notification = createNotification(13, "plain", 0);
    notification.setIcon(QString());
    notification.setApplicationIconName(QString());
    notification.setImage(QImage(2, 2, QImage::Format_ARGB32));
    notification.setSoundFile("/tmp/a.ogg");
    notification.setResident(true);
    notification.setTransient(false);
    notification.setNoFold(true);
    notification.setTimeout(150);

    QVERIFY(sendNotification(notification));

    QModelIndex idx = model()->index(0, 0);
    QCOMPARE(model()->data(idx, NotificationItem::Display).toString(), QString("display-13"));
    QCOMPARE(model()->data(idx, NotificationItem::AppIconName).toString(), QString("application-x-desktop"));
    QCOMPARE(model()->data(idx, NotificationItem::Category).toString(), QString("category-13"));
    QVERIFY(!model()->data(idx, NotificationItem::Image).value<QImage>().isNull());
    QCOMPARE(model()->data(idx, NotificationItem::CreateTime).toDateTime(), notification.createdTime());
    QCOMPARE(model()->data(idx, NotificationItem::EnableActionIcons).toBool(), true);
    QCOMPARE(model()->data(idx, NotificationItem::SoundFile).toString(), QString("/tmp/a.ogg"));
    QCOMPARE(model()->data(idx, NotificationItem::SuppressSound).toBool(), false);
    QCOMPARE(model()->data(idx, NotificationItem::Resident).toBool(), true);
    QCOMPARE(model()->data(idx, NotificationItem::Transient).toBool(), false);
    QCOMPARE(model()->data(idx, NotificationItem::Urgency).toInt(), PopupNotification::CriticalUrgency);
    QCOMPARE(model()->data(idx, NotificationItem::Timout).toInt(), 150);
    QCOMPARE(model()->data(idx, NotificationItem::NoFold).toBool(), true);
    QCOMPARE(model()->data(idx, NotificationItem::PopupTimeout).toInt(), 0);
    QCOMPARE(model()->data(idx, NotificationItem::ShowOnLockScreen).toBool(), true);
    QCOMPARE(model()->data(idx, NotificationItem::ShowContentOnLockScreen).toBool(), true);
}

void notificationModel_test::test_setNotificationData_marksStored()
{
    PopupNotification notification = createNotification(2, "plain", 1000);
    QVERIFY(sendNotification(notification));

    QVERIFY(model()->setNotificationData(2, true, NotificationItem::IsStored));
    QCOMPARE(model()->data(model()->index(0, 0), NotificationItem::IsStored).toBool(), true);
    QCOMPARE(model()->data(model()->index(0, 0), NotificationItem::ToBeStored).toBool(), false);
}

void notificationModel_test::test_execAction_invokesClientAndRemovesNotification()
{
    PopupNotification notification = createNotification(14, "plain", 0);
    notification.setResident(false);
    QVERIFY(sendNotification(notification));

    model()->execAction(14, "reply");
    QTest::qWait(80);

    QCOMPARE(NotificationClient::invokedIds(), QList<uint>({14u}));
    QCOMPARE(NotificationClient::invokedActions(), QStringList({"reply"}));
    QCOMPARE(NotificationClient::closedIds(), QList<uint>({14u}));
    QCOMPARE(model()->rowCount(QModelIndex()), 0);
}

void notificationModel_test::test_expirationAndStoreTransitions()
{
    PopupNotification transientNotification = createNotification(15, "plain", 20);
    transientNotification.setTimeout(40);
    ApplicationsSettings::settings()->setShowNotificationOnLockScreen(false);
    Sidebar::ScreenMonitor::getInstance()->setScreenLockState(true);

    QVERIFY(sendNotification(transientNotification));
    QTest::qWait(30);

    QModelIndex idx = model()->index(0, 0);
    QCOMPARE(model()->data(idx, NotificationItem::IsStored).toBool(), true);
    QCOMPARE(model()->getUnreadMessageCount(), 1);

    QTest::qWait(40);
    QCOMPARE(model()->data(idx, NotificationItem::IsExpired).toBool(), true);

    PopupNotification dndNotification = createNotification(16, "plain", 0);
    NotificationGlobalSettings::lastInstance()->setDND(true);
    QVERIFY(sendNotification(dndNotification));
    QCOMPARE(model()->data(model()->index(1, 0), NotificationItem::IsStored).toBool(), true);
}

void notificationModel_test::test_storePopupNotification_marksTransientAsStored()
{
    PopupNotification notification = createNotification(17, "plain", 100);
    QVERIFY(sendNotification(notification));

    model()->storePopupNotification(true);

    QCOMPARE(model()->data(model()->index(0, 0), NotificationItem::IsStored).toBool(), true);
    QCOMPARE(model()->getUnreadMessageCount(), 0);
}

void notificationModel_test::test_clearAll_removesAllRows()
{
    QVERIFY(sendNotification(createNotification(18, "one", 0)));
    QVERIFY(sendNotification(createNotification(19, "two", 0)));

    model()->clearAll();

    QCOMPARE(model()->rowCount(QModelIndex()), 0);
    QCOMPARE(NotificationClient::closedIds().size(), 2);
}

void notificationModel_test::test_processedBody_usesLinkColor()
{
    PopupNotification notification = createNotification(3, "<a href='https://example.com'>link</a>", 0);
    QVERIFY(sendNotification(notification));

    model()->setLinkActiveColor("#ff0000");

    const QString processed = model()->data(model()->index(0, 0), NotificationItem::ProcessedBody).toString();
    QVERIFY(processed.contains("#ff0000"));
    QVERIFY(processed.contains("link"));
}

void notificationModel_test::test_closeNotification_removesRow()
{
    PopupNotification notification = createNotification(4, "plain", 0);
    QVERIFY(sendNotification(notification));

    model()->closeNotification(4);
    QTest::qWait(80);

    QCOMPARE(model()->rowCount(QModelIndex()), 0);
    QCOMPARE(NotificationClient::closedIds(), QList<uint>({4u}));
}

QTEST_MAIN(notificationModel_test)

#include "notification-model-test.moc"
