/*global mock, converse */

const { Strophe, sizzle, u, stx } = converse.env;

describe("A MUC", function () {

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

    it("allows you to change your nickname via a modal",
        mock.initConverse(
            [],
            { view_mode: 'fullscreen', auto_register_muc_nickname: true },
            async function (_converse) {

        const nick = 'romeo';
        const muc_jid = 'lounge@montague.lit';
        const model = await mock.openAndEnterMUC(_converse, muc_jid, nick);

        expect(model.get('nick')).toBe(nick);
        expect(model.occupants.length).toBe(1);
        expect(model.occupants.at(0).get('nick')).toBe(nick);

        const view = _converse.chatboxviews.get(muc_jid);
        const dropdown_item = await u.waitUntil(() => view.querySelector(".open-nickname-modal"));
        dropdown_item.click();

        const modal = _converse.api.modal.get('converse-muc-nickname-modal');
        await u.waitUntil(() => u.isVisible(modal));

        const input = modal.querySelector('input[name="nick"]');
        expect(input.value).toBe(nick);

        const newnick = 'loverboy';
        input.value = newnick;
        modal.querySelector('input[type="submit"]')?.click();

        await u.waitUntil(() => !u.isVisible(modal));
        const { sent_stanzas } = _converse.api.connection.get();
        const sent_stanza = sent_stanzas.pop()
        expect(sent_stanza).toEqualStanza(
            stx`<presence from="${_converse.jid}"
                id="${sent_stanza.getAttribute('id')}"
                to="${muc_jid}/${newnick}"
                xmlns="jabber:client"/>`);

        // clear sent stanzas
        const IQ_stanzas = _converse.api.connection.get().IQ_stanzas;
        while (IQ_stanzas.length) IQ_stanzas.pop();

        // Two presence stanzas are received from the MUC service
        _converse.api.connection.get()._dataRecv(mock.createRequest(
            stx`
            <presence
                xmlns="jabber:client"
                from='${muc_jid}/${nick}'
                id='DC352437-C019-40EC-B590-AF29E879AF98'
                to='${_converse.jid}'
                type='unavailable'>
            <x xmlns='http://jabber.org/protocol/muc#user'>
                <item affiliation='member'
                    jid='${_converse.jid}'
                    nick='${newnick}'
                    role='participant'/>
                <status code='303'/>
                <status code='110'/>
            </x>
            </presence>`
        ));

        await u.waitUntil(() => model.get('nick') === newnick);

        // Check that the new nickname gets registered with the MUC
        _converse.api.connection.get()._dataRecv(mock.createRequest(
            stx`
            <presence
                xmlns="jabber:client"
                from='${muc_jid}/${newnick}'
                id='5B4F27A4-25ED-43F7-A699-382C6B4AFC67'
                to='${_converse.jid}'>
            <x xmlns='http://jabber.org/protocol/muc#user'>
                <item affiliation='member'
                    jid='${_converse.jid}'
                    role='participant'/>
                <status code='110'/>
            </x>
            </presence>`
        ));

        await u.waitUntil(() => model.occupants.at(0).get('nick') === newnick);
        expect(model.occupants.length).toBe(1);

        let stanza = await u.waitUntil(() => IQ_stanzas.find(
            iq => sizzle(`iq[type="get"] query[xmlns="${Strophe.NS.MUC_REGISTER}"]`, iq).length));

        expect(stanza).toEqualStanza(
            stx`<iq to="lounge@montague.lit"
                    type="get"
                    xmlns="jabber:client"
                    id="${stanza.getAttribute('id')}"><query xmlns="jabber:iq:register"/></iq>`);

        _converse.api.connection.get()._dataRecv(mock.createRequest(
            stx`<iq from="${muc_jid}"
                id="${stanza.getAttribute('id')}"
                to="${_converse.session.get('jid')}"
                xmlns="jabber:client"
                type="result">
            <query xmlns='jabber:iq:register'>
                <x xmlns='jabber:x:data' type='form'>
                <field
                    type='hidden'
                    var='FORM_TYPE'>
                    <value>http://jabber.org/protocol/muc#register</value>
                </field>
                <field
                    label='Desired Nickname'
                    type='text-single'
                    var='muc#register_roomnick'>
                    <required/>
                </field>
                </x>
            </query>
            </iq>`));

        stanza = await u.waitUntil(() => IQ_stanzas.find(
            iq => sizzle(`iq[type="set"] query[xmlns="${Strophe.NS.MUC_REGISTER}"]`, iq).length));

        expect(stanza).toEqualStanza(
            stx`<iq xmlns="jabber:client" to="${muc_jid}" type="set" id="${stanza.getAttribute('id')}">
                <query xmlns="jabber:iq:register">
                <x xmlns="jabber:x:data" type="submit">
                    <field var="FORM_TYPE"><value>http://jabber.org/protocol/muc#register</value></field>
                    <field var="muc#register_roomnick"><value>loverboy</value></field>
                </x>
                </query>
            </iq>`);
    }));

    it("informs users if their nicknames have been changed.",
            mock.initConverse([], {}, async function (_converse) {

        /* The service then sends two presence stanzas to the full JID
         * of each occupant (including the occupant who is changing his
         * or her room nickname), one of type "unavailable" for the old
         * nickname and one indicating availability for the new
         * nickname.
         *
         * See: https://xmpp.org/extensions/xep-0045.html#changenick
         *
         *  <presence
         *      from='coven@montague.lit/thirdwitch'
         *      id='DC352437-C019-40EC-B590-AF29E879AF98'
         *      to='hag66@shakespeare.lit/pda'
         *      type='unavailable'>
         *  <x xmlns='http://jabber.org/protocol/muc#user'>
         *      <item affiliation='member'
         *          jid='hag66@shakespeare.lit/pda'
         *          nick='oldhag'
         *          role='participant'/>
         *      <status code='303'/>
         *      <status code='110'/>
         *  </x>
         *  </presence>
         *
         *  <presence
         *      from='coven@montague.lit/oldhag'
         *      id='5B4F27A4-25ED-43F7-A699-382C6B4AFC67'
         *      to='hag66@shakespeare.lit/pda'>
         *  <x xmlns='http://jabber.org/protocol/muc#user'>
         *      <item affiliation='member'
         *          jid='hag66@shakespeare.lit/pda'
         *          role='participant'/>
         *      <status code='110'/>
         *  </x>
         *  </presence>
         */
        const { __ } = _converse;
        await mock.openAndEnterMUC(_converse, 'lounge@montague.lit', 'oldnick');

        const view = _converse.chatboxviews.get('lounge@montague.lit');
        await u.waitUntil(() => view.querySelectorAll('li .occupant-nick').length, 500);
        let occupants = view.querySelector('.occupant-list');
        expect(occupants.querySelectorAll('.occupant-nick').length).toBe(1);
        expect(occupants.querySelector('.occupant-nick').textContent.trim()).toBe("oldnick");

        const csntext = await u.waitUntil(() => view.querySelector('.chat-content__notifications').textContent);
        expect(csntext.trim()).toEqual("oldnick has entered the groupchat");

        let presence = stx`
            <presence
                    xmlns="jabber:client"
                    from='lounge@montague.lit/oldnick'
                    id='DC352437-C019-40EC-B590-AF29E879AF98'
                    to='romeo@montague.lit/pda'
                    type='unavailable'>
                <x xmlns='http://jabber.org/protocol/muc#user'>
                    <item affiliation='owner'
                        jid='romeo@montague.lit/pda'
                        nick='newnick'
                        role='moderator'/>
                    <status code='303'/>
                    <status code='110'/>
                </x>
            </presence>`;

        _converse.api.connection.get()._dataRecv(mock.createRequest(presence));
        await u.waitUntil(() => view.querySelectorAll('.chat-info').length);

        expect(sizzle('div.chat-info:last').pop().textContent.trim()).toBe(
            __(_converse.labels.muc.STATUS_CODE_MESSAGES["303"], "newnick")
        );
        expect(view.model.session.get('connection_status')).toBe(converse.ROOMSTATUS.ENTERED);

        occupants = view.querySelector('.occupant-list');
        expect(occupants.querySelectorAll('.occupant-nick').length).toBe(1);

        presence = stx`
            <presence
                    from='lounge@montague.lit/newnick'
                    id='5B4F27A4-25ED-43F7-A699-382C6B4AFC67'
                    to='romeo@montague.lit/pda'
                    xmlns="jabber:client">
                <x xmlns='http://jabber.org/protocol/muc#user'>
                    <item affiliation='owner'
                        jid='romeo@montague.lit/pda'
                        role='moderator'/>
                    <status code='110'/>
                </x>
            </presence>`;

        _converse.api.connection.get()._dataRecv(mock.createRequest(presence));
        expect(view.model.session.get('connection_status')).toBe(converse.ROOMSTATUS.ENTERED);
        expect(view.querySelectorAll('div.chat-info').length).toBe(1);
        expect(sizzle('div.chat-info', view)[0].textContent.trim()).toBe(
            __(_converse.labels.muc.STATUS_CODE_MESSAGES["303"], "newnick")
        );
        occupants = view.querySelector('.occupant-list');
        await u.waitUntil(() => sizzle('.occupant-nick:first', occupants).pop().textContent.trim() === "newnick");
        expect(view.model.occupants.length).toBe(1);
        expect(view.model.get('nick')).toBe("newnick");
    }));

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

        it("will use the user's reserved nickname, if it exists",
                mock.initConverse(['chatBoxesFetched'], {}, async function (_converse) {

            const IQ_stanzas = _converse.api.connection.get().IQ_stanzas;
            const muc_jid = 'lounge@montague.lit';
            _converse.api.rooms.open(muc_jid);
            await mock.waitForNewMUCDiscoInfo(_converse, muc_jid);

            const iq = await u.waitUntil(() => IQ_stanzas.filter(
                    s => sizzle(`iq[to="${muc_jid}"] query[node="x-roomuser-item"]`, s).length
                ).pop());

            expect(iq).toEqualStanza(stx`
                <iq id="${iq.getAttribute('id')}" to="${muc_jid}" type="get" xmlns="jabber:client">
                    <query node="x-roomuser-item" xmlns="http://jabber.org/protocol/disco#info"/>
                </iq>`);

            const stanza = stx`
                <iq type="result"
                    id="${iq.getAttribute("id")}"
                    from="${muc_jid}"
                    to="${_converse.api.connection.get().jid}"
                    xmlns="jabber:client">
                    <query xmlns="http://jabber.org/protocol/disco#info" node="x-roomuser-item">
                        <identity category="conference" name="thirdwitch" type="text"/>
                    </query>
                </iq>`;
            _converse.api.connection.get()._dataRecv(mock.createRequest(stanza));

            // The user has just entered the groupchat (because join was called)
            // and receives their own presence from the server.
            // See example 24:
            // https://xmpp.org/extensions/xep-0045.html#enter-pres
            const presence = stx`
                <presence
                    to="romeo@montague.lit/orchard"
                    from="lounge@montague.lit/thirdwitch"
                    id="DC352437-C019-40EC-B590-AF29E879AF97"
                    xmlns="jabber:client">
                    <x xmlns="http://jabber.org/protocol/muc#user">
                        <item affiliation="member"
                            jid="romeo@montague.lit/orchard"
                            role="participant"/>
                        <status code="110"/>
                        <status code="210"/>
                    </x>
                </presence>`;
            _converse.api.connection.get()._dataRecv(mock.createRequest(presence));

            // clear sent stanzas
            while (IQ_stanzas.length) IQ_stanzas.pop();

            // Now that the user has entered the groupchat, the features are requested again.
            await mock.waitForMUCDiscoInfo(_converse, muc_jid);

            const view = await u.waitUntil(() => _converse.chatboxviews.get(muc_jid));
            await u.waitUntil(() => (view.model.session.get('connection_status') === converse.ROOMSTATUS.ENTERED));
            await mock.returnMemberLists(_converse, muc_jid, [], ['member', 'admin', 'owner']);
            await u.waitUntil(() => view.querySelectorAll('.chat-content .chat-info').length);
            const info_text = sizzle('.chat-content .chat-info:first', view).pop().textContent.trim();
            expect(info_text).toBe('Your nickname has been automatically set to thirdwitch');
        }));

        it("will use the nickname set in the global settings if the user doesn't have a VCard nickname",
            mock.initConverse(['chatBoxesFetched'], {
                blacklisted_plugins: ['converse-vcard'],
                nickname: 'Benedict-Cucumberpatch'
            },
        async function (_converse) {
            const { api } = _converse;
            const muc_jid = 'roomy@muc.montague.lit';
            api.rooms.open(muc_jid);
            await mock.waitForMUCDiscoInfo(_converse, muc_jid);
            await mock.waitForReservedNick(_converse, muc_jid, '');
            const view = await u.waitUntil(() => _converse.chatboxviews.get(muc_jid));
            expect(view.model.get('nick')).toBe('Benedict-Cucumberpatch');
        }));

        it("will render a nickname form if a nickname conflict happens and muc_nickname_from_jid=false",
                mock.initConverse([], { vcard: { nickname: '' }}, async function (_converse) {

            const { api } = _converse;
            const muc_jid = 'conflicted@muc.montague.lit';
            api.rooms.open(muc_jid, { nick: 'romeo' });
            await mock.waitForMUCDiscoInfo(_converse, muc_jid);

            _converse.api.connection.get()._dataRecv(mock.createRequest(stx`
                <presence
                        from="${muc_jid}/romeo"
                        id="${u.getUniqueId()}"
                        to="romeo@montague.lit/pda"
                        type="error"
                        xmlns="jabber:client">
                    <x xmlns="http://jabber.org/protocol/muc"><history maxstanzas="0"/></x>
                    <error by="${muc_jid}" type="cancel">
                        <conflict xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/>
                    </error>
                </presence>`));

            const view = await u.waitUntil(() => _converse.chatboxviews.get(muc_jid));
            const el = await u.waitUntil(() => view.querySelector('.muc-nickname-form .validation-message'));
            expect(el.textContent.trim()).toBe('The nickname you chose is reserved or currently in use, please choose a different one.');
        }));

        it("will automatically choose a new nickname if a nickname conflict happens and muc_nickname_from_jid=true",
                mock.initConverse(['chatBoxesFetched'], {vcard: { nickname: '' }}, async function (_converse) {

            const { api } = _converse;
            const muc_jid = 'conflicting@muc.montague.lit'

            api.settings.set('muc_nickname_from_jid', true);
            api.rooms.open(muc_jid, { nick: 'romeo' });
            await mock.waitForMUCDiscoInfo(_converse, muc_jid);

            const connection = api.connection.get();
            const sent_stanzas = connection.sent_stanzas;
            await u.waitUntil(() => sent_stanzas.filter(s => s.nodeName === 'presence' && s.getAttribute('to').startsWith(muc_jid)).pop());

            const { IQ_stanzas } = api.connection.get();

            while (IQ_stanzas.length) IQ_stanzas.pop();
            while (sent_stanzas.length) sent_stanzas.pop();

            // Simulate repeatedly that there's already someone in the groupchat
            // with that nickname
            let presence = stx`
                <presence xmlns="jabber:client"
                        from='${muc_jid}/romeo'
                        id='${u.getUniqueId()}'
                        to='${api.connection.get().jid}'
                        type='error'>
                    <x xmlns='http://jabber.org/protocol/muc'/>
                    <error by='${muc_jid}' type='cancel'>
                        <conflict xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
                    </error>
                </presence>`;
            api.connection.get()._dataRecv(mock.createRequest(presence));

            await mock.waitForMUCDiscoInfo(_converse, muc_jid);

            let sent_stanza = await u.waitUntil(() => sent_stanzas.filter(s => s.nodeName === 'presence' && s.getAttribute('to').startsWith(muc_jid)).pop());
            expect(sent_stanza).toEqualStanza(stx`
                <presence id="${sent_stanza.getAttribute('id')}"
                        from="${connection.jid}"
                        to="${muc_jid}/romeo-2"
                        xmlns="jabber:client">
                    <x xmlns="http://jabber.org/protocol/muc"><history maxstanzas="0"/></x>
                    <c xmlns="http://jabber.org/protocol/caps" hash="sha-1" node="https://conversejs.org"
                        ver="qgxN8hmrdSa2/4/7PUoM9bPFN2s="/>
                </presence>`);

            while (IQ_stanzas.length) IQ_stanzas.pop();
            while (sent_stanzas.length) sent_stanzas.pop();

            presence = stx`
                <presence
                        xmlns="jabber:client"
                        from="${muc_jid}/romeo-2"
                        id="${u.getUniqueId()}"
                        to="romeo@montague.lit/pda"
                        type="error">
                    <x xmlns="http://jabber.org/protocol/muc"><history maxstanzas="0"/></x>
                    <error by="${muc_jid}" type="cancel">
                        <conflict xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/>
                    </error>
                </presence>`;
            _converse.api.connection.get()._dataRecv(mock.createRequest(presence));

            await mock.waitForMUCDiscoInfo(_converse, muc_jid);

            sent_stanza = await u.waitUntil(() => sent_stanzas.filter(s => s.nodeName === 'presence' && s.getAttribute('to').startsWith(muc_jid)).pop());
            expect(sent_stanza).toEqualStanza(stx`
                <presence id="${sent_stanza.getAttribute('id')}"
                        from="${connection.jid}"
                        to="${muc_jid}/romeo-3"
                        xmlns="jabber:client">
                    <x xmlns="http://jabber.org/protocol/muc"><history maxstanzas="0"/></x>
                    <c xmlns="http://jabber.org/protocol/caps" hash="sha-1" node="https://conversejs.org"
                        ver="qgxN8hmrdSa2/4/7PUoM9bPFN2s="/>
                </presence>`);

            while (IQ_stanzas.length) IQ_stanzas.pop();
            while (sent_stanzas.length) sent_stanzas.pop();

            presence = stx`
                <presence
                        xmlns="jabber:client"
                        from="${muc_jid}/romeo-3"
                        id="${u.getUniqueId()}"
                        to="romeo@montague.lit/pda"
                        type="error">
                    <x xmlns="http://jabber.org/protocol/muc"/>
                    <error by="${muc_jid}" type="cancel">
                        <conflict xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/>
                    </error>
                </presence>`;
            _converse.api.connection.get()._dataRecv(mock.createRequest(presence));

            await mock.waitForMUCDiscoInfo(_converse, muc_jid);

            sent_stanza = await u.waitUntil(() => sent_stanzas.filter(s => s.nodeName === 'presence' && s.getAttribute('to').startsWith(muc_jid)).pop());
            expect(sent_stanza).toEqualStanza(stx`
                <presence id="${sent_stanza.getAttribute('id')}"
                        from="${connection.jid}"
                        to="${muc_jid}/romeo-4"
                        xmlns="jabber:client">
                    <x xmlns="http://jabber.org/protocol/muc"><history maxstanzas="0"/></x>
                    <c xmlns="http://jabber.org/protocol/caps" hash="sha-1" node="https://conversejs.org"
                        ver="qgxN8hmrdSa2/4/7PUoM9bPFN2s="/>
                </presence>`);
        }));

        it("will show an error message if the user's nickname doesn't conform to groupchat policy",
                mock.initConverse([], {}, async function (_converse) {

            const { api } = _converse;
            const muc_jid = 'conformist@muc.montague.lit'
            api.rooms.open(muc_jid, { nick: 'romeo' });
            await mock.waitForMUCDiscoInfo(_converse, muc_jid);

            const presence = stx`
                <presence
                    xmlns="jabber:client"
                    from='${muc_jid}/romeo'
                    id='${u.getUniqueId()}'
                    to='romeo@montague.lit/pda'
                    type='error'>
                    <x xmlns='http://jabber.org/protocol/muc'/>
                    <error by='lounge@montague.lit' type='cancel'>
                        <not-acceptable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
                    </error>
                </presence>`;
            api.connection.get()._dataRecv(mock.createRequest(presence));

            const view = await u.waitUntil(() => _converse.chatboxviews.get(muc_jid));
            const el = await u.waitUntil(() => view.querySelector('.chatroom-body converse-muc-disconnected .disconnect-msg:last-child'));
            expect(el.textContent.trim()).toBe("Your nickname doesn't conform to this groupchat's policies.");
        }));

        it("doesn't show the nickname field if locked_muc_nickname is true",
            mock.initConverse(['chatBoxesFetched'], {
                locked_muc_nickname: true,
                muc_nickname_from_jid: true,
                vcard: { nickname: '' },
            }, async function (_converse) {

            const muc_jid = 'lounge@montague.lit';
            await mock.openControlBox(_converse);
            await mock.waitForRoster(_converse, 'current', 0);
            const roomspanel = _converse.chatboxviews.get('controlbox').querySelector('converse-rooms-list');
            roomspanel.querySelector('.show-add-muc-modal').click();

            const modal = _converse.api.modal.get('converse-add-muc-modal');
            await u.waitUntil(() => u.isVisible(modal), 1000)
            const name_input = modal.querySelector('input[name="chatroom"]');
            name_input.value = muc_jid;
            expect(modal.querySelector('label[for="nickname"]')).toBe(null);
            expect(modal.querySelector('input[name="nickname"]')).toBe(null);
            modal.querySelector('form input[type="submit"]').click();

            await mock.waitForMUCDiscoInfo(_converse, muc_jid);
            await u.waitUntil(() => _converse.chatboxes.length > 1);
            const chatroom = _converse.chatboxes.get(muc_jid);
            expect(chatroom.get('nick')).toBe('romeo');
        }));

        it("uses the JID node if muc_nickname_from_jid is set to true",
            mock.initConverse(
                ['chatBoxesFetched'],
                {
                    'muc_nickname_from_jid': true,
                    blacklisted_plugins: ['converse-vcard']
                }, async function (_converse) {

            await mock.openControlBox(_converse);
            await mock.waitForRoster(_converse, 'current', 0);
            const roomspanel = _converse.chatboxviews.get('controlbox').querySelector('converse-rooms-list');
            roomspanel.querySelector('.show-add-muc-modal').click();

            const modal = _converse.api.modal.get('converse-add-muc-modal');
            await u.waitUntil(() => u.isVisible(modal), 1000)
            const label_nick = modal.querySelector('label[for="nickname"]');
            expect(label_nick.textContent.trim()).toBe('Nickname:');
            const nick_input = modal.querySelector('input[name="nickname"]');
            expect(nick_input.value).toBe('romeo');
        }));
    });
});
