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

describe("The OMEMO module", function() {

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

    it("shows an error when it can't download a received encrypted file",
        mock.initConverse(['chatBoxesFetched'], {}, async function (_converse) {

        await mock.waitForRoster(_converse, 'current', 1);
        const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
        await mock.initializedOMEMO(_converse);
        await mock.openChatBoxFor(_converse, contact_jid);
        await u.waitUntil(() => mock.deviceListFetched(_converse, contact_jid, ['555']));

        await u.waitUntil(() => _converse.state.omemo_store);
        const devicelist = _converse.state.devicelists.get({'jid': contact_jid});
        await u.waitUntil(() => devicelist.devices.length === 1);

        const view = _converse.chatboxviews.get(contact_jid);
        view.model.set('omemo_active', true);

        const textarea = view.querySelector('.chat-textarea');
        textarea.value = 'This message will be encrypted';
        const message_form = view.querySelector('converse-message-form');
        message_form.onKeyDown({
            target: textarea,
            preventDefault: function preventDefault () {},
            key: "Enter",
        });

        await u.waitUntil(() => mock.bundleFetched(_converse, {
            jid: contact_jid,
            device_id: '555',
            identity_key: '3333',
            signed_prekey_id: "4223",
            signed_prekey_public: "1111",
            signed_prekey_sig: "2222",
            prekeys: ['1001', '1002', '1003'],
        }));
        await u.waitUntil(() =>
            mock.bundleFetched(_converse, {
                jid: _converse.bare_jid,
                device_id: "482886413b977930064a5888b92134fe",
                identity_key: '300000',
                signed_prekey_id: "4224",
                signed_prekey_public: "100000",
                signed_prekey_sig: "200000",
                prekeys: ["1991", "1992", "1993"],
            })
        );
        const sent_stanzas = _converse.api.connection.get().sent_stanzas;
        await u.waitUntil(() => sent_stanzas.filter((s) => sizzle('body', s).length).pop(), 1000);

        // Test reception of an encrypted file
        let obj = await u.omemo.encryptMessage('aesgcm://upload.example.org/b9e3eaaa-2eae-4900-ae41/k9mKam2JT.jpg#6b5ba0f96eae')

        // XXX: Normally the key will be encrypted via libsignal.
        // However, we're mocking libsignal in the tests, so we include it as plaintext in the message.
        let stanza = stx`<message from="${contact_jid}"
                        to="${_converse.api.connection.get().jid}"
                        type="chat"
                        id="${_converse.api.connection.get().getUniqueId()}"
                        xmlns="jabber:client">
                    <body>This is a fallback message</body>
                    <encrypted xmlns="${Strophe.NS.OMEMO}">
                        <header sid="555">
                            <key rid="${_converse.state.omemo_store.get('device_id')}">${u.arrayBufferToBase64(obj.key_and_tag)}</key>
                            <iv>${obj.iv}</iv>
                        </header>
                        <payload>${obj.payload}</payload>
                    </encrypted>
                </message>`;
        _converse.api.connection.get()._dataRecv(mock.createRequest(stanza));
        await new Promise(resolve => view.model.messages.once('rendered', resolve));

        expect(view.model.messages.length).toBe(2);
        const error = await u.waitUntil(() => view.querySelector('.error'), 5000);
        expect(error.textContent).toBe('Error: could not decrypt a received encrypted file, because it could not be downloaded');
    }));

    it("implements XEP-0454 to encrypt uploaded files",
        mock.initConverse(['chatBoxesFetched'], {}, async function (_converse) {

        const base_url = 'https://example.org/';
        await mock.waitUntilDiscoConfirmed(
            _converse, _converse.domain,
            [{'category': 'server', 'type':'IM'}],
            ['http://jabber.org/protocol/disco#items'], [], 'info');

        const send_backup = XMLHttpRequest.prototype.send;
        const IQ_stanzas = _converse.api.connection.get().IQ_stanzas;

        await mock.waitUntilDiscoConfirmed(_converse, _converse.domain, [], [], ['upload.montague.tld'], 'items');
        await mock.waitUntilDiscoConfirmed(_converse, 'upload.montague.tld', [], [Strophe.NS.HTTPUPLOAD], []);
        await mock.waitForRoster(_converse, 'current', 3);
        const contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@montague.lit';

        await u.waitUntil(() => mock.initializedOMEMO(_converse));
        await mock.openChatBoxFor(_converse, contact_jid);

        let iq_stanza = await u.waitUntil(() => mock.deviceListFetched(_converse, contact_jid));
        let stanza = stx`
            <iq from="${contact_jid}"
                id="${iq_stanza.getAttribute('id')}"
                to="${_converse.api.connection.get().jid}"
                type="result"
                xmlns="jabber:client">
            <pubsub xmlns="http://jabber.org/protocol/pubsub">
                <items node="eu.siacs.conversations.axolotl.devicelist">
                    <item xmlns="http://jabber.org/protocol/pubsub"> // TODO: must have an id attribute
                        <list xmlns="eu.siacs.conversations.axolotl">
                            <device id="555"/>
                        </list>
                    </item>
                </items>
            </pubsub>
            </iq>`;
        _converse.api.connection.get()._dataRecv(mock.createRequest(stanza));
        await u.waitUntil(() => _converse.state.omemo_store);
        const devicelist = _converse.state.devicelists.get({'jid': contact_jid});
        await u.waitUntil(() => devicelist.devices.length === 1);

        const view = _converse.chatboxviews.get(contact_jid);
        const file = new File(['secret'], 'secret.txt', { type: 'text/plain' })
        view.model.set('omemo_active', true);
        await view.model.sendFiles([file]);

        await u.waitUntil(() => IQ_stanzas.filter(iq => iq.querySelector('iq[to="upload.montague.tld"] request')).length);
        const iq = IQ_stanzas.pop();
        const url = base_url+"/secret.txt";
        stanza = stx`
            <iq from="upload.montague.tld"
                id="${iq.getAttribute("id")}"
                to="romeo@montague.lit/orchard"
                type="result"
                xmlns="jabber:client">
            <slot xmlns="urn:xmpp:http:upload:0">
                <put url="https://upload.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/secret.txt">
                    <header name="Authorization">Basic Base64String==</header>
                    <header name="Cookie">foo=bar; user=romeo</header>
                </put>
                <get url="${url}" />
            </slot>
            </iq>`;

        spyOn(XMLHttpRequest.prototype, 'send').and.callFake(async function () {
            const message = view.model.messages.at(0);
            message.set('progress', 1);
            await u.waitUntil(() => view.querySelector('.chat-content progress')?.getAttribute('value') === '1')
            message.save({
                upload: _converse.SUCCESS,
                oob_url: message.get('get'),
                body: message.get('get')
            });
            await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length);
        });
        let sent_stanza;
        _converse.api.connection.get()._dataRecv(mock.createRequest(stanza));

        iq_stanza = await u.waitUntil(() => mock.bundleIQRequestSent(_converse, contact_jid, '555'));
        stanza = stx`
            <iq from="${contact_jid}"
                id="${iq_stanza.getAttribute('id')}"
                to="${_converse.bare_jid}"
                type="result"
                xmlns="jabber:client">
            <pubsub xmlns="http://jabber.org/protocol/pubsub">
                <items node="eu.siacs.conversations.axolotl.bundles:555">
                    <item>
                        <bundle xmlns="eu.siacs.conversations.axolotl">
                            <signedPreKeyPublic signedPreKeyId="4223">${btoa('1111')}</signedPreKeyPublic>
                            <signedPreKeySignature>${btoa('2222')}</signedPreKeySignature>
                            <identityKey>${btoa('3333')}</identityKey>
                            <prekeys>
                                <preKeyPublic preKeyId="1">${btoa('1001')}</preKeyPublic>
                                <preKeyPublic preKeyId="2">${btoa('1002')}</preKeyPublic>
                                <preKeyPublic preKeyId="3">${btoa('1003')}</preKeyPublic>
                            </prekeys>
                        </bundle>
                    </item>
                </items>
            </pubsub>
            </iq>`;
        _converse.api.connection.get()._dataRecv(mock.createRequest(stanza));
        iq_stanza = await u.waitUntil(() => mock.bundleIQRequestSent(_converse, _converse.bare_jid, '482886413b977930064a5888b92134fe'));
        stanza = stx`
            <iq from="${_converse.bare_jid}"
                id="${iq_stanza.getAttribute('id')}"
                to="${_converse.bare_jid}"
                type="result"
                xmlns="jabber:client">
            <pubsub xmlns="http://jabber.org/protocol/pubsub">
                <items node="eu.siacs.conversations.axolotl.bundles:482886413b977930064a5888b92134fe">
                    <item>
                        <bundle xmlns="eu.siacs.conversations.axolotl">
                            <signedPreKeyPublic signedPreKeyId="4223">${btoa('100000')}</signedPreKeyPublic>
                            <signedPreKeySignature>${btoa('200000')}</signedPreKeySignature>
                            <identityKey>${btoa('300000')}</identityKey>
                            <prekeys>
                                <preKeyPublic preKeyId="1">${btoa('1991')}</preKeyPublic>
                                <preKeyPublic preKeyId="2">${btoa('1992')}</preKeyPublic>
                                <preKeyPublic preKeyId="3">${btoa('1993')}</preKeyPublic>
                            </prekeys>
                        </bundle>
                    </item>
                </items>
            </pubsub>
            </iq>`;

        spyOn(_converse.api.connection.get(), 'send').and.callFake(stanza => (sent_stanza = stanza));
        _converse.api.connection.get()._dataRecv(mock.createRequest(stanza));

        await u.waitUntil(() => sent_stanza);

        const fallback = 'This is an OMEMO encrypted message which your client doesn’t seem to support. Find more information on https://conversations.im/omemo';
        expect(sent_stanza).toEqualStanza(stx`
            <message from="romeo@montague.lit/orchard"
                id="${sent_stanza.getAttribute("id")}"
                to="lady.montague@montague.lit"
                type="chat"
                xmlns="jabber:client">
                    <body>${fallback}</body>
                    <active xmlns="http://jabber.org/protocol/chatstates"/>
                    <request xmlns="urn:xmpp:receipts"/>
                    <origin-id id="${sent_stanza.getAttribute('id')}" xmlns="urn:xmpp:sid:0"/>
                    <encrypted xmlns="eu.siacs.conversations.axolotl">
                    <header sid="123456789">
                        <key rid="482886413b977930064a5888b92134fe">YzFwaDNSNzNYNw==</key>
                        <key rid="555">YzFwaDNSNzNYNw==</key>
                        <iv>${sent_stanza.querySelector('header iv').textContent}</iv>
                    </header>
                <payload>${sent_stanza.querySelector('payload').textContent}</payload>
                </encrypted>
                <store xmlns="urn:xmpp:hints"/>
                <encryption namespace="eu.siacs.conversations.axolotl" xmlns="urn:xmpp:eme:0"/>
            </message>`);

        const link_el = await u.waitUntil(() => view.querySelector('.chat-msg__text'));
        expect(link_el.textContent.trim()).toBe(url);

        const message = view.model.messages.at(0);
        expect(message.get('is_encrypted')).toBe(true);

        XMLHttpRequest.prototype.send = send_backup;
    }));
});
