// SPDX-FileCopyrightText: 2024 Filipe Azevedo <pasnox@gmail.com>
//
// SPDX-License-Identifier: LGPL-2.1-or-later

#include "QXmppConstants_p.h"
#include "QXmppDiscoveryManager.h"
#include "QXmppMovedItem_p.h"
#include "QXmppMovedManager.h"
#include "QXmppPubSubManager.h"

#include "Iq.h"
#include "TestClient.h"

using namespace QXmpp::Private;

struct Tester {
    Tester()
    {
        client.addNewExtension<QXmppDiscoveryManager>();
        client.addNewExtension<QXmppPubSubManager>();
        manager = client.addNewExtension<QXmppMovedManager>();
    }

    Tester(const QString &jid)
        : Tester()
    {
        client.configuration().setJid(jid);
    }

    TestClient client;
    QXmppMovedManager *manager;
};

class tst_QXmppMovedManager : public QObject
{
    Q_OBJECT

private:
    Q_SLOT void testMovedItem();
    Q_SLOT void testMovedPresence();
    Q_SLOT void testDiscoveryFeatures();
    Q_SLOT void testSupportedByServer();
    Q_SLOT void testResetCachedData();
    Q_SLOT void testHandleDiscoInfo();
    Q_SLOT void testOnRegistered();
    Q_SLOT void testOnUnregistered();
    Q_SLOT void testPublishMoved();
    Q_SLOT void testVerifyMoved();
    Q_SLOT void testNotify();

    template<typename T>
    void testError(QXmppTask<T> &task, TestClient &client, const QString &id, const QString &from);
};

void tst_QXmppMovedManager::testMovedItem()
{
    const auto expected = u"<item id=\"current\"><moved xmlns=\"urn:xmpp:moved:1\"><new-jid>new@shakespeare.example</new-jid></moved></item>"_s;
    const QDomElement expectedElement = xmlToDom(expected);

    {
        QXmppMovedItem packet;
        packet.setNewJid(u"new@shakespeare.example"_s);

        QCOMPARE(packetToXml(packet), expected);
    }

    {
        QXmppMovedItem packet;
        packet.parse(expectedElement);

        QVERIFY(!packet.newJid().isEmpty());
    }
}

void tst_QXmppMovedManager::testMovedPresence()
{
    const auto expected =
        u"<presence to=\"contact@shakespeare.example\" type=\"subscribe\">"
        "<moved xmlns=\"urn:xmpp:moved:1\"><old-jid>old@shakespeare.example</old-jid></moved>"
        "</presence>"_s;
    const QDomElement expectedElement = xmlToDom(expected);

    {
        QXmppPresence packet;
        packet.setTo(u"contact@shakespeare.example"_s);
        packet.setType(QXmppPresence::Subscribe);
        packet.setOldJid(u"old@shakespeare.example"_s);

        QCOMPARE(packetToXml(packet), expected);
    }

    {
        QXmppPresence packet;
        packet.parse(expectedElement);

        QVERIFY(!packet.oldJid().isEmpty());
    }
}

void tst_QXmppMovedManager::testDiscoveryFeatures()
{
    QXmppMovedManager manager;

    QCOMPARE(manager.discoveryFeatures(), QStringList { ns_moved.toString() });
}

void tst_QXmppMovedManager::testSupportedByServer()
{
    QXmppMovedManager manager;
    QSignalSpy spy(&manager, &QXmppMovedManager::supportedByServerChanged);

    QVERIFY(!manager.supportedByServer());

    manager.setSupportedByServer(true);

    QVERIFY(manager.supportedByServer());
    QCOMPARE(spy.size(), 1);
}

void tst_QXmppMovedManager::testResetCachedData()
{
    QXmppMovedManager manager;

    manager.setSupportedByServer(true);
    manager.resetCachedData();

    QVERIFY(!manager.supportedByServer());
}

void tst_QXmppMovedManager::testHandleDiscoInfo()
{
    auto [client, manager] = Tester(u"hag66@shakespeare.example"_s);
    client.setStreamManagementState(QXmppClient::NewStream);

    ResultIq<QXmppDiscoInfo> iq {
        u"qx1"_s,
        u"shakespeare.example"_s,
        u"hag66@shakespeare.example"_s,
        {},
        QXmppDiscoInfo { {}, {}, QStringList { u"urn:xmpp:moved:1"_s } },
    };
    Q_EMIT client.connected();
    client.inject(writePacketToDom(iq));

    QVERIFY(manager->supportedByServer());

    Q_EMIT client.connected();
    iq.payload.setFeatures({});
    client.inject(writePacketToDom(iq));

    QVERIFY(!manager->supportedByServer());
}

void tst_QXmppMovedManager::testOnRegistered()
{
    TestClient client;
    QXmppMovedManager manager;

    client.addNewExtension<QXmppDiscoveryManager>();
    client.addNewExtension<QXmppPubSubManager>();
    client.configuration().setJid(u"hag66@shakespeare.example"_s);
    client.addExtension(&manager);

    manager.setSupportedByServer(true);

    client.setStreamManagementState(QXmppClient::NewStream);
    Q_EMIT client.connected();

    QVERIFY(!manager.supportedByServer());

    ResultIq<QXmppDiscoInfo> iq {
        u"qx1"_s,
        u"shakespeare.example"_s,
        u"hag66@shakespeare.example"_s,
        {},
        QXmppDiscoInfo { {}, {}, QStringList { u"urn:xmpp:moved:1"_s } },
    };
    Q_EMIT client.connected();
    client.inject(writePacketToDom(iq));

    QVERIFY(manager.supportedByServer());
}

void tst_QXmppMovedManager::testOnUnregistered()
{
    QXmppClient client;
    QXmppMovedManager manager;

    client.addNewExtension<QXmppDiscoveryManager>();
    client.addNewExtension<QXmppPubSubManager>();
    client.configuration().setJid(u"hag66@shakespeare.example"_s);
    client.addExtension(&manager);

    manager.setSupportedByServer(true);
    manager.onUnregistered(&client);

    QVERIFY(!manager.supportedByServer());

    manager.setSupportedByServer(true);
    Q_EMIT client.connected();

    QVERIFY(manager.supportedByServer());
}

void tst_QXmppMovedManager::testPublishMoved()
{
    auto tester = Tester(u"old@shakespeare.example"_s);
    auto &client = tester.client;
    auto manager = tester.manager;

    auto call = [manager]() {
        return manager->publishStatement(u"moved@shakespeare.example"_s);
    };

    auto task = call();

    client.expect(u"<iq id='qx1' to='old@shakespeare.example' type='set'>"
                  "<pubsub xmlns='http://jabber.org/protocol/pubsub'>"
                  "<publish node='urn:xmpp:moved:1'>"
                  "<item id='current'>"
                  "<moved xmlns='urn:xmpp:moved:1'>"
                  "<new-jid>moved@shakespeare.example</new-jid>"
                  "</moved>"
                  "</item>"
                  "</publish>"
                  "</pubsub>"
                  "</iq>"_s);
    client.inject(u"<iq id='qx1' from='old@shakespeare.example' type='result'>"
                  "<pubsub xmlns='http://jabber.org/protocol/pubsub'>"
                  "<publish node='uurn:xmpp:moved:1'>"
                  "<item id='current'/>"
                  "</publish>"
                  "</pubsub>"
                  "</iq>"_s);

    expectFutureVariant<QXmpp::Success>(task);

    testError(task = call(), client, u"qx1"_s, u"old@shakespeare.example"_s);
}

void tst_QXmppMovedManager::testVerifyMoved()
{
    auto tester = Tester(u"contact@shakespeare.example"_s);
    auto &client = tester.client;
    auto manager = tester.manager;

    auto call = [manager]() {
        return manager->verifyStatement(u"old@shakespeare.example"_s, u"moved@shakespeare.example"_s);
    };

    auto task = call();

    client.expect(u"<iq id='qx1' to='old@shakespeare.example' type='get'>"
                  "<pubsub xmlns='http://jabber.org/protocol/pubsub'>"
                  "<items node='urn:xmpp:moved:1'>"
                  "<item id='current'/>"
                  "</items>"
                  "</pubsub>"
                  "</iq>"_s);
    client.inject(u"<iq id='qx1' from='old@shakespeare.example' type='result'>"
                  "<pubsub xmlns='http://jabber.org/protocol/pubsub'>"
                  "<items node='urn:xmpp:moved:1'>"
                  "<item id='current'>"
                  "<moved xmlns='urn:xmpp:moved:1'>"
                  "<new-jid>moved@shakespeare.example</new-jid>"
                  "</moved>"
                  "</item>"
                  "</items>"
                  "</pubsub>"
                  "</iq>"_s);

    expectFutureVariant<QXmpp::Success>(task);

    testError(task = call(), client, u"qx1"_s, u"old@shakespeare.example"_s);
}

void tst_QXmppMovedManager::testNotify()
{
    auto tester = Tester(u"moved@shakespeare.example"_s);
    auto &client = tester.client;
    auto manager = tester.manager;

    auto call = [manager]() {
        return manager->notifyContact(u"contact@shakespeare.example"_s, u"old@shakespeare.example"_s, true, u"I moved."_s);
    };

    auto task = call();

    client.expect(u"<presence to='contact@shakespeare.example' type='subscribe'>"
                  "<status>I moved.</status>"
                  "<moved xmlns='urn:xmpp:moved:1'>"
                  "<old-jid>old@shakespeare.example</old-jid>"
                  "</moved>"
                  "</presence>"_s);
}

template<typename T>
void tst_QXmppMovedManager::testError(QXmppTask<T> &task, TestClient &client, const QString &id, const QString &from)
{
    client.ignore();
    client.inject(u"<iq id='%1' from='%2' type='error'>"
                  u"<error type='cancel'>"
                  u"<not-allowed xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>"
                  u"</error>"
                  u"</iq>"_s
                      .arg(id, from));

    expectFutureVariant<QXmppError>(task);
}

QTEST_MAIN(tst_QXmppMovedManager)
#include "tst_QXmppMovedManager.moc"
