/*global mock, converse */

const { Strophe, _ } = converse.env;
const $msg = converse.env.$msg;
const u = converse.env.utils;

describe("Notifications", function () {
    // Implement the protocol defined in https://xmpp.org/extensions/xep-0313.html#config

    describe("When show_desktop_notifications is set to true", function () {
        describe("And the desktop is not focused", function () {
            describe("an HTML5 Notification", function () {

                it("is shown when a new private message is received",
                        mock.initConverse(['rosterGroupsFetched'], {}, async (done, _converse) => {

                    await mock.waitForRoster(_converse, 'current');
                    spyOn(_converse, 'showMessageNotification').and.callThrough();
                    spyOn(_converse, 'areDesktopNotificationsEnabled').and.returnValue(true);
                    spyOn(_converse, 'isMessageToHiddenChat').and.returnValue(true);

                    const message = 'This message will show a desktop notification';
                    const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit',
                        msg = $msg({
                            from: sender_jid,
                            to: _converse.connection.jid,
                            type: 'chat',
                            id: u.getUniqueId()
                        }).c('body').t(message).up()
                        .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
                    await _converse.handleMessageStanza(msg); // This will emit 'message'
                    await u.waitUntil(() => _converse.api.chatviews.get(sender_jid));
                    expect(_converse.areDesktopNotificationsEnabled).toHaveBeenCalled();
                    expect(_converse.showMessageNotification).toHaveBeenCalled();
                    done();
                }));

                it("is shown when you are mentioned in a groupchat",
                        mock.initConverse(['rosterGroupsFetched'], {}, async (done, _converse) => {

                    await mock.waitForRoster(_converse, 'current');
                    await mock.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo');
                    const view = _converse.api.chatviews.get('lounge@montague.lit');
                    if (!view.el.querySelectorAll('.chat-area').length) {
                        view.renderChatArea();
                    }
                    let no_notification = false;
                    if (typeof window.Notification === 'undefined') {
                        no_notification = true;
                        window.Notification = function () {
                            return {
                                'close': function () {}
                            };
                        };
                    }
                    spyOn(_converse, 'showMessageNotification').and.callThrough();
                    spyOn(_converse, 'areDesktopNotificationsEnabled').and.returnValue(true);

                    // Test mention with setting false
                    const nick = mock.chatroom_names[0];
                    const makeMsg = text => $msg({
                        from: 'lounge@montague.lit/'+nick,
                        id: u.getUniqueId(),
                        to: 'romeo@montague.lit',
                        type: 'groupchat'
                    }).c('body').t(text).tree();
                    _converse.connection._dataRecv(mock.createRequest(makeMsg('romeo: this will NOT show a notification')));
                    await new Promise(resolve => view.model.messages.once('rendered', resolve));
                    await u.waitUntil(() => _converse.areDesktopNotificationsEnabled.calls.count() === 0);
                    expect(_converse.showMessageNotification).not.toHaveBeenCalled();

                    // Test reference
                    const message_with_ref = $msg({
                        from: 'lounge@montague.lit/'+nick,
                        id: u.getUniqueId(),
                        to: 'romeo@montague.lit',
                        type: 'groupchat'
                    }).c('body').t('romeo: this will show a notification').up()
                    .c('reference', {'xmlns':'urn:xmpp:reference:0', 'begin':'0', 'end':'5', 'type':'mention', 'uri':'xmpp:romeo@montague.lit'}).tree();
                    _converse.connection._dataRecv(mock.createRequest(message_with_ref));
                    await new Promise(resolve => view.model.messages.once('rendered', resolve));
                    await u.waitUntil(() => _converse.areDesktopNotificationsEnabled.calls.count() === 1);
                    expect(_converse.showMessageNotification).toHaveBeenCalled();

                    // Test mention with setting true
                    _converse.api.settings.set('notify_all_room_messages', true);
                    _converse.connection._dataRecv(mock.createRequest(makeMsg('romeo: this will show a notification')));
                    await new Promise(resolve => view.model.messages.once('rendered', resolve));
                    await u.waitUntil(() => _converse.areDesktopNotificationsEnabled.calls.count() === 2);
                    expect(_converse.showMessageNotification).toHaveBeenCalled();
                    if (no_notification) {
                        delete window.Notification;
                    }
                    done();
                }));

                it("is shown for headline messages",
                        mock.initConverse(['rosterGroupsFetched'], {}, async (done, _converse) => {

                    spyOn(_converse, 'showMessageNotification').and.callThrough();
                    spyOn(_converse, 'isMessageToHiddenChat').and.returnValue(true);
                    spyOn(_converse, 'areDesktopNotificationsEnabled').and.returnValue(true);
                    const stanza = $msg({
                            'type': 'headline',
                            'from': 'notify.example.com',
                            'to': 'romeo@montague.lit',
                            'xml:lang': 'en'
                        })
                        .c('subject').t('SIEVE').up()
                        .c('body').t('&lt;juliet@example.com&gt; You got mail.').up()
                        .c('x', {'xmlns': 'jabber:x:oob'})
                        .c('url').t('imap://romeo@example.com/INBOX;UIDVALIDITY=385759043/;UID=18');
                    _converse.connection._dataRecv(mock.createRequest(stanza));

                    await u.waitUntil(() => _converse.chatboxviews.keys().length);
                    const view = _converse.chatboxviews.get('notify.example.com');
                    await new Promise(resolve => view.model.messages.once('rendered', resolve));
                    expect(_converse.chatboxviews.keys().includes('notify.example.com')).toBeTruthy();
                    expect(_converse.showMessageNotification).toHaveBeenCalled();
                    done();
                }));

                it("is not shown for full JID headline messages if allow_non_roster_messaging is false", mock.initConverse((done, _converse) => {
                    _converse.allow_non_roster_messaging = false;
                    spyOn(_converse, 'showMessageNotification').and.callThrough();
                    spyOn(_converse, 'areDesktopNotificationsEnabled').and.returnValue(true);
                    const stanza = $msg({
                            'type': 'headline',
                            'from': 'someone@notify.example.com',
                            'to': 'romeo@montague.lit',
                            'xml:lang': 'en'
                        })
                        .c('subject').t('SIEVE').up()
                        .c('body').t('&lt;juliet@example.com&gt; You got mail.').up()
                        .c('x', {'xmlns': 'jabber:x:oob'})
                        .c('url').t('imap://romeo@example.com/INBOX;UIDVALIDITY=385759043/;UID=18');
                    _converse.connection._dataRecv(mock.createRequest(stanza));
                    expect(
                        _.includes(_converse.chatboxviews.keys(),
                            'someone@notify.example.com')
                        ).toBeFalsy();
                    expect(_converse.showMessageNotification).not.toHaveBeenCalled();
                    done();
                }));

                it("is shown when a user changes their chat state (if show_chat_state_notifications is true)",
                        mock.initConverse(['rosterGroupsFetched'], {show_chat_state_notifications: true},
                        async (done, _converse) => {

                    await mock.waitForRoster(_converse, 'current', 3);
                    spyOn(_converse, 'areDesktopNotificationsEnabled').and.returnValue(true);
                    spyOn(_converse, 'showChatStateNotification');
                    const jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@montague.lit';
                    _converse.roster.get(jid).presence.set('show', 'busy'); // This will emit 'contactStatusChanged'
                    await u.waitUntil(() => _converse.areDesktopNotificationsEnabled.calls.count() === 1);
                    expect(_converse.showChatStateNotification).toHaveBeenCalled();
                    done()
                }));
            });
        });

        describe("When a new contact request is received", function () {
            it("an HTML5 Notification is received", mock.initConverse((done, _converse) => {
                spyOn(_converse, 'areDesktopNotificationsEnabled').and.returnValue(true);
                spyOn(_converse, 'showContactRequestNotification');
                _converse.api.trigger('contactRequest', {'fullname': 'Peter Parker', 'jid': 'peter@parker.com'});
                expect(_converse.areDesktopNotificationsEnabled).toHaveBeenCalled();
                expect(_converse.showContactRequestNotification).toHaveBeenCalled();
                done();
            }));
        });
    });

    describe("When play_sounds is set to true", function () {
        describe("A notification sound", function () {

            it("is played when the current user is mentioned in a groupchat",
                    mock.initConverse(['rosterGroupsFetched'], {}, async (done, _converse) => {

                mock.createContacts(_converse, 'current');
                await mock.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo');
                _converse.play_sounds = true;
                spyOn(_converse, 'playSoundNotification');
                const view = _converse.chatboxviews.get('lounge@montague.lit');
                if (!view.el.querySelectorAll('.chat-area').length) {
                    view.renderChatArea();
                }
                let text = 'This message will play a sound because it mentions romeo';
                let message = $msg({
                    from: 'lounge@montague.lit/otheruser',
                    id: '1',
                    to: 'romeo@montague.lit',
                    type: 'groupchat'
                }).c('body').t(text);
                _converse.api.settings.set('notify_all_room_messages', true);
                await view.model.handleMessageStanza(message.nodeTree);
                await u.waitUntil(() => _converse.playSoundNotification.calls.count());
                expect(_converse.playSoundNotification).toHaveBeenCalled();

                text = "This message won't play a sound";
                message = $msg({
                    from: 'lounge@montague.lit/otheruser',
                    id: '2',
                    to: 'romeo@montague.lit',
                    type: 'groupchat'
                }).c('body').t(text);
                await view.model.handleMessageStanza(message.nodeTree);
                expect(_converse.playSoundNotification, 1);
                _converse.play_sounds = false;

                text = "This message won't play a sound because it is sent by romeo";
                message = $msg({
                    from: 'lounge@montague.lit/romeo',
                    id: '3',
                    to: 'romeo@montague.lit',
                    type: 'groupchat'
                }).c('body').t(text);
                await view.model.handleMessageStanza(message.nodeTree);
                expect(_converse.playSoundNotification, 1);
                _converse.play_sounds = false;
                done();
            }));
        });
    });


    describe("A Favicon Message Counter", function () {

        it("is incremented when the message is received and the window is not focused",
                mock.initConverse(
                    ['rosterGroupsFetched'], {'update_title': false},
                    async function (done, _converse) {

            await mock.waitForRoster(_converse, 'current');
            await mock.openControlBox(_converse);

            const favico = jasmine.createSpyObj('favico', ['badge']);
            spyOn(converse.env, 'Favico').and.returnValue(favico);

            const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
            const previous_state = _converse.windowState;
            const msg = $msg({
                    from: sender_jid,
                    to: _converse.connection.jid,
                    type: 'chat',
                    id: u.getUniqueId()
                }).c('body').t('This message will increment the message counter').up()
                  .c('active', {'xmlns': Strophe.NS.CHATSTATES}).tree();
            _converse.windowState = 'hidden';

            spyOn(_converse.api, "trigger").and.callThrough();

            await _converse.handleMessageStanza(msg);
            expect(_converse.api.trigger).toHaveBeenCalledWith('message', jasmine.any(Object));

            expect(favico.badge.calls.count()).toBe(0);

            _converse.api.settings.set('update_title', true);
            const msg2 = $msg({
                    from: sender_jid,
                    to: _converse.connection.jid,
                    type: 'chat',
                    id: u.getUniqueId()
                }).c('body').t('This message increment the message counter AND update the page title').up()
                  .c('active', {'xmlns': Strophe.NS.CHATSTATES}).tree();

            await _converse.handleMessageStanza(msg2);
            await u.waitUntil(() => favico.badge.calls.count() === 1);
            expect(favico.badge.calls.mostRecent().args.pop()).toBe(2);

            const view = _converse.chatboxviews.get(sender_jid);
            expect(view.model.get('num_unread')).toBe(2);

            // Check that it's cleared when the window is focused
            _converse.windowState = 'hidden';
            _converse.saveWindowState({'type': 'focus'});
            await u.waitUntil(() => favico.badge.calls.count() === 2);
            expect(favico.badge.calls.mostRecent().args.pop()).toBe(0);

            expect(view.model.get('num_unread')).toBe(0);
            _converse.windowSate = previous_state;
            done();
        }));

        it("is not incremented when the message is received and the window is focused",
            mock.initConverse(
                ['rosterGroupsFetched'], {},
                async function (done, _converse) {

            await mock.waitForRoster(_converse, 'current');
            await mock.openControlBox(_converse);

            const favico = jasmine.createSpyObj('favico', ['badge']);
            spyOn(converse.env, 'Favico').and.returnValue(favico);

            _converse.saveWindowState({'type': 'focus'});
            const message = 'This message will not increment the message counter';
            const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit',
                msg = $msg({
                    from: sender_jid,
                    to: _converse.connection.jid,
                    type: 'chat',
                    id: u.getUniqueId()
                }).c('body').t(message).up()
                  .c('active', {'xmlns': Strophe.NS.CHATSTATES}).tree();
            await _converse.handleMessageStanza(msg);

            setTimeout(() => {
                const view = _converse.chatboxviews.get(sender_jid);
                expect(view.model.get('num_unread')).toBe(0);
                expect(favico.badge.calls.count()).toBe(0);
                done();
            }, 500);
        }));

        it("is incremented from zero when chatbox was closed after viewing previously received messages and the window is not focused now",
            mock.initConverse(
                ['rosterGroupsFetched'], {},
                async function (done, _converse) {

            await mock.waitForRoster(_converse, 'current');

            const favico = jasmine.createSpyObj('favico', ['badge']);
            spyOn(converse.env, 'Favico').and.returnValue(favico);

            const message = 'This message will always increment the message counter from zero';
            const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
            const msgFactory = function () {
                    return $msg({
                        from: sender_jid,
                        to: _converse.connection.jid,
                        type: 'chat',
                        id: u.getUniqueId()
                    })
                    .c('body').t(message).up()
                    .c('active', {'xmlns': Strophe.NS.CHATSTATES})
                    .tree();
             };

            // leave converse-chat page
            _converse.windowState = 'hidden';
            await _converse.handleMessageStanza(msgFactory());
            let view = _converse.chatboxviews.get(sender_jid);
            await u.waitUntil(() => favico.badge.calls.count() === 1, 1000);
            expect(favico.badge.calls.mostRecent().args.pop()).toBe(1);

            // come back to converse-chat page
            _converse.saveWindowState({'type': 'focus'});
            await u.waitUntil(() => u.isVisible(view.el));
            await u.waitUntil(() => favico.badge.calls.count() === 2);
            expect(favico.badge.calls.mostRecent().args.pop()).toBe(0);

            // close chatbox and leave converse-chat page again
            view.close();
            _converse.windowState = 'hidden';

            // check that msg_counter is incremented from zero again
            await _converse.handleMessageStanza(msgFactory());
            view = _converse.chatboxviews.get(sender_jid);
            await u.waitUntil(() => u.isVisible(view.el));
            await u.waitUntil(() => favico.badge.calls.count() === 3);
            expect(favico.badge.calls.mostRecent().args.pop()).toBe(1);
            done();
        }));
    });

});
