/*global mock, converse */
const { $iq, Strophe, u, stx }  = converse.env;

describe("A Groupchat", function () {

    beforeAll(() => jasmine.addMatchers({ toEqualStanza: jasmine.toEqualStanza }));

    describe("upon being entered", function () {

        it("will fetch the member list if muc_fetch_members is true",
                mock.initConverse([], {'muc_fetch_members': true}, async function (_converse) {

            const { api } = _converse;
            let sent_IQs = _converse.api.connection.get().IQ_stanzas;
            const muc_jid = 'lounge@montague.lit';
            await mock.openAndEnterMUC(_converse, muc_jid, 'romeo');
            let view = _converse.chatboxviews.get(muc_jid);
            expect(sent_IQs.filter(iq => iq.querySelector('query item[affiliation]')).length).toBe(3);


            // Check in reverse order that we requested all three lists
            const owner_iq = sent_IQs.pop();
            expect(Strophe.serialize(owner_iq)).toBe(
                `<iq id="${owner_iq.getAttribute('id')}" to="${muc_jid}" type="get" xmlns="jabber:client">`+
                    `<query xmlns="http://jabber.org/protocol/muc#admin"><item affiliation="owner"/></query>`+
                `</iq>`);

            const admin_iq = sent_IQs.pop();
            expect(Strophe.serialize(admin_iq)).toBe(
                `<iq id="${admin_iq.getAttribute('id')}" to="${muc_jid}" type="get" xmlns="jabber:client">`+
                    `<query xmlns="http://jabber.org/protocol/muc#admin"><item affiliation="admin"/></query>`+
                `</iq>`);

            const member_iq = sent_IQs.pop();
            expect(Strophe.serialize(member_iq)).toBe(
                `<iq id="${member_iq.getAttribute('id')}" to="${muc_jid}" type="get" xmlns="jabber:client">`+
                    `<query xmlns="http://jabber.org/protocol/muc#admin"><item affiliation="member"/></query>`+
                `</iq>`);
            view.close();

            _converse.api.connection.get().IQ_stanzas = [];
            sent_IQs = _converse.api.connection.get().IQ_stanzas;
            api.settings.set('muc_fetch_members', false);
            await mock.openAndEnterMUC(_converse, 'orchard@montague.lit', 'romeo');
            view = _converse.chatboxviews.get('orchard@montague.lit');
            expect(sent_IQs.filter(iq => iq.querySelector('query item[affiliation]')).length).toBe(0);
            await view.close();

            _converse.api.connection.get().IQ_stanzas = [];
            sent_IQs = _converse.api.connection.get().IQ_stanzas;
            api.settings.set('muc_fetch_members', ['admin']);
            await mock.openAndEnterMUC(_converse, 'courtyard@montague.lit', 'romeo');
            view = _converse.chatboxviews.get('courtyard@montague.lit');
            expect(sent_IQs.filter(iq => iq.querySelector('query item[affiliation]')).length).toBe(1);
            expect(sent_IQs.filter(iq => iq.querySelector('query item[affiliation="admin"]')).length).toBe(1);
            view.close();

            _converse.api.connection.get().IQ_stanzas = [];
            sent_IQs = _converse.api.connection.get().IQ_stanzas;
            api.settings.set('muc_fetch_members', ['owner']);
            await mock.openAndEnterMUC(_converse, 'garden@montague.lit', 'romeo');
            view = _converse.chatboxviews.get('garden@montague.lit');
            expect(sent_IQs.filter(iq => iq.querySelector('query item[affiliation]')).length).toBe(1);
            expect(sent_IQs.filter(iq => iq.querySelector('query item[affiliation="owner"]')).length).toBe(1);
            view.close();
        }));

        it("will not fetch the member list if the user is not affiliated",
                mock.initConverse([], {'muc_fetch_members': true}, async function (_converse) {

            const muc_jid = 'lounge@montague.lit';
            const sent_IQs = _converse.api.connection.get().IQ_stanzas;
            spyOn(_converse.exports.MUCOccupants.prototype, 'fetchMembers').and.callThrough();
            // Join MUC without an affiliation
            const model = await mock.openAndEnterMUC(_converse, muc_jid, 'romeo', [], [], true, {}, 'none', 'participant');
            await u.waitUntil(() => model.occupants.fetchMembers.calls.count());
            expect(sent_IQs.filter(iq => iq.querySelector('query item[affiliation]')).length).toBe(0);
        }));

        describe("when fetching the member lists", function () {

            it("gracefully handles being forbidden from fetching the lists for certain affiliations",
                    mock.initConverse([], {'muc_fetch_members': true}, async function (_converse) {

                const sent_IQs = _converse.api.connection.get().IQ_stanzas;
                const muc_jid = 'lounge@montague.lit';
                const features = [
                    'http://jabber.org/protocol/muc',
                    'jabber:iq:register',
                    'muc_hidden',
                    'muc_membersonly',
                    'muc_passwordprotected',
                    Strophe.NS.MAM,
                    Strophe.NS.SID
                ];
                const nick = 'romeo';
                _converse.api.rooms.open(muc_jid);
                await mock.waitForMUCDiscoInfo(_converse, muc_jid, features);
                await mock.waitForReservedNick(_converse, muc_jid, nick);
                mock.receiveOwnMUCPresence(_converse, muc_jid, nick);

                const view = await u.waitUntil(() => _converse.chatboxviews.get(muc_jid));
                await u.waitUntil(() => (view.model.session.get('connection_status') === converse.ROOMSTATUS.ENTERED));

                // Check in reverse order that we requested all three lists
                const owner_iq = sent_IQs.pop();
                expect(Strophe.serialize(owner_iq)).toBe(
                    `<iq id="${owner_iq.getAttribute('id')}" to="${muc_jid}" type="get" xmlns="jabber:client">`+
                        `<query xmlns="http://jabber.org/protocol/muc#admin"><item affiliation="owner"/></query>`+
                    `</iq>`);
                const admin_iq = sent_IQs.pop();
                expect(Strophe.serialize(admin_iq)).toBe(
                    `<iq id="${admin_iq.getAttribute('id')}" to="${muc_jid}" type="get" xmlns="jabber:client">`+
                        `<query xmlns="http://jabber.org/protocol/muc#admin"><item affiliation="admin"/></query>`+
                    `</iq>`);
                const member_iq = sent_IQs.pop();
                expect(Strophe.serialize(member_iq)).toBe(
                    `<iq id="${member_iq.getAttribute('id')}" to="${muc_jid}" type="get" xmlns="jabber:client">`+
                        `<query xmlns="http://jabber.org/protocol/muc#admin"><item affiliation="member"/></query>`+
                    `</iq>`);

                // It might be that the user is not allowed to fetch certain lists.
                let err_stanza = u.toStanza(
                    `<iq xmlns="jabber:client" type="error" to="${_converse.jid}" from="${muc_jid}" id="${admin_iq.getAttribute('id')}">
                        <error type="auth"><forbidden xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/></error>
                    </iq>`);
                _converse.api.connection.get()._dataRecv(mock.createRequest(err_stanza));

                err_stanza = u.toStanza(
                    `<iq xmlns="jabber:client" type="error" to="${_converse.jid}" from="${muc_jid}" id="${owner_iq.getAttribute('id')}">
                        <error type="auth"><forbidden xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/></error>
                    </iq>`);
                _converse.api.connection.get()._dataRecv(mock.createRequest(err_stanza));

                // Now the service sends the member lists to the user
                const member_list_stanza = stx`
                    <iq from="${muc_jid}"
                        id="${member_iq.getAttribute('id')}"
                        to="romeo@montague.lit/orchard"
                        type="result"
                        xmlns="jabber:client">
                        <query xmlns="${Strophe.NS.MUC_ADMIN}">
                            <item affiliation="member" jid="hag66@shakespeare.lit" nick="thirdwitch" role="participant"/>
                        </query>
                    </iq>`;
                _converse.api.connection.get()._dataRecv(mock.createRequest(member_list_stanza));

                await u.waitUntil(() => view.model.occupants.length > 1);
                expect(view.model.occupants.length).toBe(2);
                // The existing owner occupant should not have their
                // affiliation removed due to the owner list
                // not being returned (forbidden err).
                expect(view.model.occupants.findWhere({'jid': _converse.bare_jid}).get('affiliation')).toBe('owner');
                expect(view.model.occupants.findWhere({'jid': 'hag66@shakespeare.lit'}).get('affiliation')).toBe('member');
            }));
        });
    });
});

describe("Someone being invited to a groupchat", function () {

    beforeAll(() => jasmine.addMatchers({ toEqualStanza: jasmine.toEqualStanza }));

    it("will first be added to the member list if the groupchat is members only",
            mock.initConverse(['chatBoxesFetched'], {}, async function (_converse) {

        await mock.waitForRoster(_converse, 'current', 0);
        spyOn(_converse.exports.MUCOccupants.prototype, 'fetchMembers').and.callThrough();
        const sent_IQs = _converse.api.connection.get().IQ_stanzas;
        const muc_jid = 'coven@chat.shakespeare.lit';
        const nick = 'romeo';
        const room_creation_promise = _converse.api.rooms.open(muc_jid, {nick});

        // Check that the groupchat queried for the features.
        let stanza = await u.waitUntil(() => sent_IQs.filter(iq => iq.querySelector(`iq[to="${muc_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]`)).pop());
        expect(Strophe.serialize(stanza)).toBe(
            `<iq from="romeo@montague.lit/orchard" id="${stanza.getAttribute("id")}" to="${muc_jid}" type="get" xmlns="jabber:client">`+
                `<query xmlns="http://jabber.org/protocol/disco#info"/>`+
            `</iq>`);

        const features_stanza = stx`
            <iq from="coven@chat.shakespeare.lit"
                id="${stanza.getAttribute('id')}"
                to="romeo@montague.lit/desktop"
                type="result"
                xmlns="jabber:client">
                <query xmlns="http://jabber.org/protocol/disco#info">
                    <identity category="conference" name="A Dark Cave" type="text"/>
                    <feature var="http://jabber.org/protocol/muc"/>
                    <feature var="muc_hidden"/>
                    <feature var="muc_temporary"/>
                    <feature var="muc_membersonly"/>
                </query>
            </iq>`;
        _converse.api.connection.get()._dataRecv(mock.createRequest(features_stanza));
        const sent_stanzas = _converse.api.connection.get().sent_stanzas;
        await u.waitUntil(() => sent_stanzas.filter(s => s.matches(`presence[to="${muc_jid}/${nick}"]`)).pop());

        // State that the chat is members-only via the features IQ
        const view = await u.waitUntil(() => _converse.chatboxviews.get(muc_jid));
        expect(view.model.features.get('membersonly')).toBeTruthy();

        await room_creation_promise;
        await mock.createContacts(_converse, 'current');

        let sent_stanza, sent_id;
        spyOn(_converse.api.connection.get(), 'send').and.callFake(function (stanza) {
            if (stanza.nodeName === 'message') {
                sent_id = stanza.getAttribute('id');
                sent_stanza = stanza;
            }
        });
        const invitee_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
        const reason = "Please join this groupchat";
        view.model.directInvite(invitee_jid, reason);

        // Check in reverse order that we requested all three lists
        const owner_iq = sent_IQs.pop();
        expect(Strophe.serialize(owner_iq)).toBe(
            `<iq id="${owner_iq.getAttribute('id')}" to="coven@chat.shakespeare.lit" type="get" xmlns="jabber:client">`+
                `<query xmlns="http://jabber.org/protocol/muc#admin"><item affiliation="owner"/></query>`+
            `</iq>`);

        const admin_iq = sent_IQs.pop();
        expect(Strophe.serialize(admin_iq)).toBe(
            `<iq id="${admin_iq.getAttribute('id')}" to="coven@chat.shakespeare.lit" type="get" xmlns="jabber:client">`+
                `<query xmlns="http://jabber.org/protocol/muc#admin"><item affiliation="admin"/></query>`+
            `</iq>`);

        const member_iq = sent_IQs.pop();
        expect(Strophe.serialize(member_iq)).toBe(
            `<iq id="${member_iq.getAttribute('id')}" to="coven@chat.shakespeare.lit" type="get" xmlns="jabber:client">`+
                `<query xmlns="http://jabber.org/protocol/muc#admin"><item affiliation="member"/></query>`+
            `</iq>`);

        // Now the service sends the member lists to the user
        const member_list_stanza = stx`
            <iq from="coven@chat.shakespeare.lit"
                id="${member_iq.getAttribute('id')}"
                to="romeo@montague.lit/orchard"
                type="result"
                xmlns="jabber:client">
                <query xmlns="${Strophe.NS.MUC_ADMIN}">
                    <item affiliation="member" jid="hag66@shakespeare.lit" nick="thirdwitch" role="participant"/>
                </query>
            </iq>`;
        _converse.api.connection.get()._dataRecv(mock.createRequest(member_list_stanza));

        const admin_list_stanza = stx`
            <iq from="coven@chat.shakespeare.lit"
                id="${admin_iq.getAttribute('id')}"
                to="romeo@montague.lit/orchard"
                type="result"
                xmlns="jabber:client">
                <query xmlns="${Strophe.NS.MUC_ADMIN}">
                    <item affiliation="admin" jid="wiccarocks@shakespeare.lit" nick="secondwitch"/>
                </query>
            </iq>`;
        _converse.api.connection.get()._dataRecv(mock.createRequest(admin_list_stanza));

        const owner_list_stanza = stx`
            <iq from="coven@chat.shakespeare.lit"
                id="${owner_iq.getAttribute('id')}"
                to="romeo@montague.lit/orchard"
                type="result"
                xmlns="jabber:client">
                <query xmlns="${Strophe.NS.MUC_ADMIN}">
                    <item affiliation="owner" jid="crone1@shakespeare.lit"/>
                </query>
            </iq>`;
        _converse.api.connection.get()._dataRecv(mock.createRequest(owner_list_stanza));

        // Converse puts the user on the member list
        stanza = await u.waitUntil(() => sent_IQs.filter(iq => iq.querySelector(`iq[to="${muc_jid}"] query[xmlns="http://jabber.org/protocol/muc#admin"]`)).pop());
        expect(stanza.outerHTML,
            `<iq id="${stanza.getAttribute('id')}" to="coven@chat.shakespeare.lit" type="set" xmlns="jabber:client">`+
                `<query xmlns="http://jabber.org/protocol/muc#admin">`+
                    `<item affiliation="member" jid="${invitee_jid}">`+
                        `<reason>Please join this groupchat</reason>`+
                    `</item>`+
                `</query>`+
            `</iq>`);

        const result = stx`
            <iq from="coven@chat.shakespeare.lit"
                id="${stanza.getAttribute('id')}"
                to="romeo@montague.lit/orchard"
                type="result"
                xmlns="jabber:client"/>`;
        _converse.api.connection.get()._dataRecv(mock.createRequest(result));

        await u.waitUntil(() => view.model.occupants.fetchMembers.calls.count());

        // Finally check that the user gets invited.
        expect(sent_stanza).toEqualStanza(stx`
            <message id="${sent_id}" to="${invitee_jid}" xmlns="jabber:client">
                <x jid="coven@chat.shakespeare.lit" reason="Please join this groupchat" xmlns="jabber:x:conference"/>
            </message>
        `);
    }));
});
