#include <QtTest>
#include <QAbstractListModel>
#include <QAction>
#include <QFile>
#include <QTemporaryDir>
#define private public
#include "../recent-file-extension.h"
#undef private

using namespace UkuiMenu;

Q_DECLARE_METATYPE(UkuiMenu::RecentFile)
Q_DECLARE_METATYPE(QVector<UkuiMenu::RecentFile>)

namespace {

constexpr int UriRole = Qt::UserRole;
constexpr int NameRole = Qt::UserRole + 1;
constexpr int IconRole = Qt::UserRole + 2;
constexpr int PathRole = Qt::UserRole + 3;
constexpr int DateRole = Qt::UserRole + 4;

QAbstractListModel *recentFilesModel(RecentFileExtension *extension)
{
    return extension->findChild<QAbstractListModel *>();
}

QVector<RecentFile> buildRecentFiles()
{
    RecentFile latest;
    latest.accessTime = 1700000100;
    latest.uri = "file:///tmp/newer.txt";
    latest.name = "newer.txt";
    latest.icon = "text-plain";
    latest.infoId = "recent:///newer";

    RecentFile older;
    older.accessTime = 1700000000;
    older.uri = "file:///tmp/older.txt";
    older.name = "older.txt";
    older.icon = "text-x-generic";
    older.infoId = "recent:///older";

    return {latest, older};
}

void flushEvents()
{
    QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
    QCoreApplication::processEvents();
}

}

class recentFile_test : public QObject
{
    Q_OBJECT

private slots:
    void initTestCase();
    void cleanup();
    void cleanupTestCase();

    void test_RecentFileExtension();
    void test_ModelDataAndSignals();
    void test_ReceiveAndContextMenu();
    void test_HelperMethods();
    void test_OpenFileByProvider();
    void test_Plugin();

private:
    RecentFileExtension *m_extension = nullptr;
};

void recentFile_test::initTestCase()
{
    qRegisterMetaType<RecentFile>("RecentFile");
    qRegisterMetaType<QVector<RecentFile>>("QVector<RecentFile>");

    m_extension = new RecentFileExtension();
    QVERIFY(m_extension != nullptr);
    QVERIFY(m_extension->m_recentFilesProviderThread != nullptr);
    QVERIFY(m_extension->m_recentFilesProviderThread->isRunning());
}

void recentFile_test::cleanup()
{
    QVERIFY(m_extension != nullptr);

    if (m_extension->m_contextMenu) {
        m_extension->m_contextMenu->close();
        flushEvents();
    }

    QAbstractListModel *model = recentFilesModel(m_extension);
    QVERIFY(model != nullptr);

    QVector<RecentFile> recentFiles;
    QVERIFY(QMetaObject::invokeMethod(model,
                                      "updateRecentFiles",
                                      Qt::DirectConnection,
                                      Q_ARG(QVector<RecentFile>, recentFiles)));
}

void recentFile_test::cleanupTestCase()
{
    delete m_extension;
    m_extension = nullptr;
}

void recentFile_test::test_RecentFileExtension()
{
    QVERIFY(m_extension != nullptr);

    QCOMPARE(m_extension->metadata().value(WidgetMetadata::Id), "recent-file");
    QCOMPARE(m_extension->metadata().value(WidgetMetadata::Version), "1.0.0");
    QCOMPARE(m_extension->metadata().value(WidgetMetadata::Description), "recent-file");
    QCOMPARE(m_extension->metadata().value(WidgetMetadata::Main), "qrc:///extensions/RecentFileExtension.qml");
    QCOMPARE(m_extension->index(), 1);

    QVariantMap exportedData = m_extension->data();
    QVERIFY(exportedData.contains("recentFilesModel"));

    QVERIFY(m_extension->m_recentFilesModel != nullptr);
    QVERIFY(m_extension->m_recentFileProvider != nullptr);
    QVERIFY(recentFilesModel(m_extension) != nullptr);
}

void recentFile_test::test_ModelDataAndSignals()
{
    QVERIFY(m_extension != nullptr);

    QAbstractListModel *model = recentFilesModel(m_extension);
    QVERIFY(model != nullptr);

    QSignalSpy resetSpy(model, &QAbstractItemModel::modelReset);
    QVector<RecentFile> recentFiles = buildRecentFiles();
    QVERIFY(QMetaObject::invokeMethod(model,
                                      "updateRecentFiles",
                                      Qt::DirectConnection,
                                      Q_ARG(QVector<RecentFile>, recentFiles)));
    QCOMPARE(resetSpy.count(), 1);

    QCOMPARE(model->rowCount(), 2);

    QModelIndex firstIndex = model->index(0, 0);
    QVERIFY(firstIndex.isValid());
    QCOMPARE(model->data(firstIndex, UriRole).toString(), QString("file:///tmp/newer.txt"));
    QCOMPARE(model->data(firstIndex, NameRole).toString(), QString("newer.txt"));
    QCOMPARE(model->data(firstIndex, IconRole).toString(), QString("text-plain"));
    QCOMPARE(model->data(firstIndex, PathRole).toString(), QString("/tmp"));

    QSignalSpy dataChangedSpy(model, &QAbstractItemModel::dataChanged);
    QVERIFY(QMetaObject::invokeMethod(model,
                                      "setDateFormat",
                                      Qt::DirectConnection,
                                      Q_ARG(QString, QString("yyyy-MM-dd"))));
    QCOMPARE(dataChangedSpy.count(), 1);
    QCOMPARE(model->data(firstIndex, DateRole).toString(),
             QDateTime::fromSecsSinceEpoch(recentFiles.first().accessTime).toString(QStringLiteral("yyyy-MM-dd")));

    QList<QVariant> signalArguments = dataChangedSpy.takeFirst();
    QCOMPARE(signalArguments.at(2).value<QVector<int>>(), QVector<int>({DateRole}));

    QSignalSpy updateSpy(model, SIGNAL(updateRecentData()));
    QVERIFY(QMetaObject::invokeMethod(model, "updateData", Qt::DirectConnection));
    QCOMPARE(updateSpy.count(), 1);

    QVERIFY(!model->data(QModelIndex(), UriRole).isValid());
    QVERIFY(!model->data(model->index(10, 0), UriRole).isValid());
    QVERIFY(!model->data(firstIndex, Qt::DisplayRole).isValid());

    const QHash<int, QByteArray> roles = model->roleNames();
    QCOMPARE(roles.value(UriRole), QByteArray("uri"));
    QCOMPARE(roles.value(NameRole), QByteArray("name"));
    QCOMPARE(roles.value(IconRole), QByteArray("icon"));
    QCOMPARE(roles.value(PathRole), QByteArray("path"));
    QCOMPARE(roles.value(DateRole), QByteArray("date"));
}

void recentFile_test::test_ReceiveAndContextMenu()
{
    QVERIFY(m_extension != nullptr);

    QAbstractListModel *model = recentFilesModel(m_extension);
    QVERIFY(model != nullptr);
    QVector<RecentFile> recentFiles = buildRecentFiles();
    QVERIFY(QMetaObject::invokeMethod(model,
                                      "updateRecentFiles",
                                      Qt::DirectConnection,
                                      Q_ARG(QVector<RecentFile>, recentFiles)));

    m_extension->receive({{"action", "contextMenu"}, {"url", "invalid://not-exist"}, {"index", 0}});
    QVERIFY(m_extension->m_contextMenu != nullptr);
    QCOMPARE(m_extension->m_contextMenu->actions().count(), 5);

    QList<QAction *> actions = m_extension->m_contextMenu->actions();
    QVERIFY(actions.at(0) != nullptr);
    QVERIFY(actions.at(1) != nullptr);
    QVERIFY(actions.at(3) != nullptr);
    QVERIFY(actions.at(4) != nullptr);
    actions.at(0)->trigger();
    actions.at(1)->trigger();
    actions.at(3)->trigger();
    actions.at(4)->trigger();

    m_extension->receive({{"action", "contextMenu"}, {"url", "invalid://not-exist"}, {"index", 0}});
    flushEvents();
    QTRY_VERIFY(m_extension->m_contextMenu.isNull());

    m_extension->receive({{"action", "emptyAreaContextMenu"}});
    QVERIFY(m_extension->m_contextMenu != nullptr);
    QCOMPARE(m_extension->m_contextMenu->actions().count(), 1);
    QVERIFY(m_extension->m_contextMenu->actions().at(0) != nullptr);
    m_extension->m_contextMenu->actions().at(0)->trigger();

    m_extension->receive({{"action", "emptyAreaContextMenu"}});
    flushEvents();
    QTRY_VERIFY(m_extension->m_contextMenu.isNull());

    m_extension->receive({{"action", "closeMenu"}});
    flushEvents();
    QTRY_VERIFY(m_extension->m_contextMenu.isNull());

    m_extension->receive({{"action", "openFile"}, {"url", "invalid://not-exist"}});
    QVERIFY(m_extension->m_contextMenu.isNull());
    QTest::qWait(50);
}

void recentFile_test::test_HelperMethods()
{
    QVERIFY(m_extension != nullptr);

    QCOMPARE(m_extension->getDefaultAppForUrl("invalid://not-exist"), QString());
    QCOMPARE(m_extension->getAppIcon(QString()).isNull(), QIcon::fromTheme("unk-app-symbolic").isNull());

    QTemporaryDir temporaryDir;
    QVERIFY(temporaryDir.isValid());

    QFile desktopFile(temporaryDir.filePath("test.desktop"));
    QVERIFY(desktopFile.open(QIODevice::WriteOnly | QIODevice::Text));
    const QByteArray desktopContent = "[Desktop Entry]\n"
                                      "Type=Application\n"
                                      "Name=Recent File Test\n"
                                      "Exec=/bin/true\n"
                                      "Icon=/definitely/not/exist/icon.png\n";
    QCOMPARE(desktopFile.write(desktopContent), desktopContent.size());
    desktopFile.close();

    QCOMPARE(m_extension->getAppIcon(desktopFile.fileName()).isNull(),
             QIcon::fromTheme("unk-app-symbolic").isNull());

    QFile iconDesktopFile(temporaryDir.filePath("theme.desktop"));
    QVERIFY(iconDesktopFile.open(QIODevice::WriteOnly | QIODevice::Text));
    const QByteArray iconDesktopContent = "[Desktop Entry]\n"
                                          "Type=Application\n"
                                          "Name=Recent File Theme Test\n"
                                          "Exec=/bin/true\n"
                                          "Icon=text-plain\n";
    QCOMPARE(iconDesktopFile.write(iconDesktopContent), iconDesktopContent.size());
    iconDesktopFile.close();

    QVERIFY(!m_extension->getAppIcon(iconDesktopFile.fileName()).isNull());

    QFile existingFile(temporaryDir.filePath("document.txt"));
    QVERIFY(existingFile.open(QIODevice::WriteOnly | QIODevice::Text));
    QCOMPARE(existingFile.write("content"), 7);
    existingFile.close();

    const QString localFileUrl = QUrl::fromLocalFile(existingFile.fileName()).toString();
    QVERIFY(!m_extension->getDefaultAppForUrl(localFileUrl.toStdString()).isEmpty());
}

void recentFile_test::test_OpenFileByProvider()
{
    QVERIFY(m_extension != nullptr);
    QVERIFY(m_extension->m_recentFileProvider != nullptr);

    QTemporaryDir temporaryDir;
    QVERIFY(temporaryDir.isValid());

    QFile existingFile(temporaryDir.filePath("launch.txt"));
    QVERIFY(existingFile.open(QIODevice::WriteOnly | QIODevice::Text));
    QCOMPARE(existingFile.write("launch-content"), 14);
    existingFile.close();

    const QString localFileUrl = QUrl::fromLocalFile(existingFile.fileName()).toString();

    m_extension->openFileASync(localFileUrl);
    QTest::qWait(200);
}

void recentFile_test::test_Plugin()
{
    RecentFileExtensionPlugin plugin;

    QCOMPARE(plugin.id(), QString("recent-file"));
    QCOMPARE(plugin.createContextMenuExtension(), nullptr);

    WidgetExtension *widgetExtension = plugin.createWidgetExtension();
    QVERIFY(widgetExtension != nullptr);
    QCOMPARE(widgetExtension->metadata().value(WidgetMetadata::Id), QString("recent-file"));
}

QTEST_MAIN(recentFile_test)

#include "recent-file-test.moc"
