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

describe("A Chat Message", function () {

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

    it("can be sent as a correction by using the up arrow",
            mock.initConverse(['chatBoxesFetched'], {}, async function (_converse) {

        const { api } = _converse;
        await mock.waitForRoster(_converse, 'current', 1);
        await mock.openControlBox(_converse);
        const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
        await mock.openChatBoxFor(_converse, contact_jid)
        const view = _converse.chatboxviews.get(contact_jid);
        const textarea = view.querySelector('textarea.chat-textarea');
        expect(textarea.value).toBe('');
        const message_form = view.querySelector('converse-message-form');
        message_form.onKeyDown({
            target: textarea,
            key: "ArrowUp",
        });
        expect(textarea.value).toBe('');

        textarea.value = 'But soft, what light through yonder airlock breaks?';
        message_form.onKeyDown({
            target: textarea,
            preventDefault: function preventDefault () {},
            key: "Enter",
        });
        await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length);
        expect(view.querySelectorAll('.chat-msg').length).toBe(1);
        expect(view.querySelector('.chat-msg__text').textContent)
            .toBe('But soft, what light through yonder airlock breaks?');

        const first_msg = view.model.messages.findWhere({'message': 'But soft, what light through yonder airlock breaks?'});
        expect(textarea.value).toBe('');
        message_form.onKeyDown({
            target: textarea,
            key: "ArrowUp",
        });
        await u.waitUntil(() => textarea.value === 'But soft, what light through yonder airlock breaks?');
        expect(view.model.messages.at(0).get('correcting')).toBe(true);
        expect(view.querySelectorAll('.chat-msg').length).toBe(1);
        await u.waitUntil(() => u.hasClass('correcting', view.querySelector('.chat-msg')), 500);

        spyOn(api.connection.get(), 'send');
        let new_text = 'But soft, what light through yonder window breaks?';
        textarea.value = new_text;
        message_form.onKeyDown({
            target: textarea,
            preventDefault: function preventDefault () {},
            key: "Enter",
        });
        await u.waitUntil(() => view.querySelector('.chat-msg__text').textContent.replace(/<!-.*?->/g, '') === new_text);

        expect(api.connection.get().send).toHaveBeenCalled();
        const msg = api.connection.get().send.calls.all()[0].args[0];
        expect(msg).toEqualStanza(
            stx`<message from="romeo@montague.lit/orchard" id="${msg.getAttribute("id")}"
                        to="mercutio@montague.lit" type="chat"
                        xmlns="jabber:client">
                    <body>But soft, what light through yonder window breaks?</body>
                    <active xmlns="http://jabber.org/protocol/chatstates"/>
                    <request xmlns="urn:xmpp:receipts"/>
                    <replace id="${first_msg.get("msgid")}" xmlns="urn:xmpp:message-correct:0"/>
                    <origin-id id="${msg.querySelector('origin-id').getAttribute("id")}" xmlns="urn:xmpp:sid:0"/>
            </message>`);
        expect(view.model.messages.models.length).toBe(1);
        const corrected_message = view.model.messages.at(0);
        expect(corrected_message.get('msgid')).toBe(first_msg.get('msgid'));
        expect(corrected_message.get('correcting')).toBe(false);

        const older_versions = corrected_message.get('older_versions');
        const keys = Object.keys(older_versions);
        expect(keys.length).toBe(1);
        expect(older_versions[keys[0]]).toBe('But soft, what light through yonder airlock breaks?');

        expect(view.querySelectorAll('.chat-msg').length).toBe(1);
        await u.waitUntil(() => (u.hasClass('correcting', view.querySelector('.chat-msg')) === false), 500);

        // Test that pressing the down arrow cancels message correction
        await u.waitUntil(() => textarea.value === '')
        message_form.onKeyDown({
            target: textarea,
            key: "ArrowUp",
        });
        await u.waitUntil(() => textarea.value === 'But soft, what light through yonder window breaks?');
        expect(view.model.messages.at(0).get('correcting')).toBe(true);
        expect(view.querySelectorAll('.chat-msg').length).toBe(1);
        await u.waitUntil(() => u.hasClass('correcting', view.querySelector('.chat-msg')), 500);
        expect(textarea.value).toBe('But soft, what light through yonder window breaks?');
        message_form.onKeyDown({
            target: textarea,
            key: "ArrowDown",
        });
        await u.waitUntil(() => textarea.value === '');
        expect(view.model.messages.at(0).get('correcting')).toBe(false);
        expect(view.querySelectorAll('.chat-msg').length).toBe(1);
        await u.waitUntil(() => (u.hasClass('correcting', view.querySelector('.chat-msg')) === false), 500);

        new_text = 'It is the east, and Juliet is the one.';
        textarea.value = new_text;
        message_form.onKeyDown({
            target: textarea,
            preventDefault: function preventDefault () {},
            key: "Enter",
        });
        await u.waitUntil(() => Array.from(view.querySelectorAll('.chat-msg__text'))
            .filter(m => m.textContent.replace(/<!-.*?->/g, '') === new_text).length);
        expect(view.querySelectorAll('.chat-msg').length).toBe(2);

        textarea.value =  'Arise, fair sun, and kill the envious moon';
        message_form.onKeyDown({
            target: textarea,
            preventDefault: function preventDefault () {},
            key: "Enter",
        });
        await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length === 3);

        message_form.onKeyDown({
            target: textarea,
            key: "ArrowUp",
        });
        await u.waitUntil(() => textarea.value === 'Arise, fair sun, and kill the envious moon');
        await u.waitUntil(() => view.model.messages.at(2).get('correcting') === true);
        expect(view.model.messages.at(0).get('correcting')).toBeFalsy();
        expect(view.model.messages.at(1).get('correcting')).toBeFalsy();
        await u.waitUntil(() => u.hasClass('correcting', sizzle('.chat-msg:last', view).pop()), 750);

        textarea.selectionEnd = 0; // Happens by pressing up,
                                // but for some reason not in tests, so we set it manually.
        message_form.onKeyDown({
            target: textarea,
            key: "ArrowUp",
        });
        await u.waitUntil(() => textarea.value === 'It is the east, and Juliet is the one.');
        expect(view.model.messages.at(0).get('correcting')).toBeFalsy();
        expect(view.model.messages.at(1).get('correcting')).toBe(true);
        expect(view.model.messages.at(2).get('correcting')).toBeFalsy();
        await u.waitUntil(() => u.hasClass('correcting', sizzle('.chat-msg', view)[1]), 500);

        textarea.value = 'It is the east, and Juliet is the sun.';
        message_form.onKeyDown({
            target: textarea,
            preventDefault: function preventDefault () {},
            key: "Enter",
        });
        await u.waitUntil(() => textarea.value === '');
        await u.waitUntil(() => Array.from(view.querySelectorAll('.chat-msg__text')).filter(
            m => m.textContent === 'It is the east, and Juliet is the sun.').length);

        const messages = view.querySelectorAll('.chat-msg');
        expect(messages.length).toBe(3);
        expect(messages[0].querySelector('.chat-msg__text').textContent)
            .toBe('But soft, what light through yonder window breaks?');
        expect(messages[1].querySelector('.chat-msg__text').textContent)
            .toBe('It is the east, and Juliet is the sun.');
        expect(messages[2].querySelector('.chat-msg__text').textContent)
            .toBe('Arise, fair sun, and kill the envious moon');

        expect(view.model.messages.at(0).get('correcting')).toBeFalsy();
        expect(view.model.messages.at(1).get('correcting')).toBeFalsy();
        expect(view.model.messages.at(2).get('correcting')).toBeFalsy();
    }));


    it("can be sent as a correction by clicking the pencil icon",
            mock.initConverse(['chatBoxesFetched'], {}, async function (_converse) {

        const { api } = _converse;
        await mock.waitForRoster(_converse, 'current', 1);
        await mock.openControlBox(_converse);
        const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
        await mock.openChatBoxFor(_converse, contact_jid);
        const view = _converse.chatboxviews.get(contact_jid);
        const textarea = view.querySelector('textarea.chat-textarea');

        textarea.value = 'But soft, what light through yonder airlock breaks?';
        const message_form = view.querySelector('converse-message-form');
        message_form.onKeyDown({
            target: textarea,
            preventDefault: function preventDefault () {},
            key: "Enter",
        });
        await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length);

        expect(view.querySelectorAll('.chat-msg').length).toBe(1);
        expect(view.querySelector('.chat-msg__text').textContent)
            .toBe('But soft, what light through yonder airlock breaks?');
        await u.waitUntil(() => textarea.value === '');

        const first_msg = view.model.messages.findWhere({'message': 'But soft, what light through yonder airlock breaks?'});
        await u.waitUntil(() => view.querySelectorAll('.chat-msg .chat-msg__action').length >= 1);
        let action = view.querySelector('.chat-msg .chat-msg__action');
        expect(action.textContent.trim()).toBe('Edit');

        action.style.opacity = 1;
        action.click();

        await u.waitUntil(() => textarea.value === 'But soft, what light through yonder airlock breaks?');
        expect(view.model.messages.at(0).get('correcting')).toBe(true);
        expect(view.querySelectorAll('.chat-msg').length).toBe(1);
        await u.waitUntil(() => u.hasClass('correcting', view.querySelector('.chat-msg')));

        spyOn(api.connection.get(), 'send');
        const text = 'But soft, what light through yonder window breaks?';
        textarea.value = text;
        message_form.onKeyDown({
            target: textarea,
            preventDefault: function preventDefault () {},
            key: "Enter",
        });
        await u.waitUntil(() => view.querySelector('.chat-msg__text').textContent.replace(/<!-.*?->/g, '') === text);
        expect(api.connection.get().send).toHaveBeenCalled();

        const msg = api.connection.get().send.calls.all()[0].args[0];
        expect(msg).toEqualStanza(stx`
              <message from="romeo@montague.lit/orchard" id="${msg.getAttribute("id")}"
                to="mercutio@montague.lit" type="chat"
                xmlns="jabber:client">
                    <body>But soft, what light through yonder window breaks?</body>
                    <active xmlns="http://jabber.org/protocol/chatstates"/>
                    <request xmlns="urn:xmpp:receipts"/>
                    <replace id="${first_msg.get("msgid")}" xmlns="urn:xmpp:message-correct:0"/>
                    <origin-id id="${msg.querySelector('origin-id').getAttribute("id")}" xmlns="urn:xmpp:sid:0"/>
            </message>`);
        expect(view.model.messages.models.length).toBe(1);
        const corrected_message = view.model.messages.at(0);
        expect(corrected_message.get('msgid')).toBe(first_msg.get('msgid'));
        expect(corrected_message.get('correcting')).toBe(false);

        const older_versions = corrected_message.get('older_versions');
        const keys = Object.keys(older_versions);
        expect(keys.length).toBe(1);
        expect(older_versions[keys[0]]).toBe('But soft, what light through yonder airlock breaks?');

        await u.waitUntil(() => u.hasClass('correcting', view.querySelector('.chat-msg')) === false);
        expect(view.querySelectorAll('.chat-msg').length).toBe(1);

        // Test that clicking the pencil icon a second time cancels editing.
        action = view.querySelector('.chat-msg .chat-msg__action');
        action.style.opacity = 1;
        action.click();

        await u.waitUntil(() => textarea.value === 'But soft, what light through yonder window breaks?');
        expect(view.model.messages.at(0).get('correcting')).toBe(true);
        expect(view.querySelectorAll('.chat-msg').length).toBe(1);
        await u.waitUntil(() => u.hasClass('correcting', view.querySelector('.chat-msg')) === true);

        action = view.querySelector('.chat-msg .chat-msg__action');
        action.style.opacity = 1;
        action.click();
        expect(view.model.messages.at(0).get('correcting')).toBe(false);
        expect(view.querySelectorAll('.chat-msg').length).toBe(1);
        await u.waitUntil(() => textarea.value === '');
        await u.waitUntil(() => (u.hasClass('correcting', view.querySelector('.chat-msg')) === false), 500);

        // Test that messages from other users don't have the pencil icon
        _converse.handleMessageStanza(
            stx`<message from="${contact_jid}"
                    to="${api.connection.get().jid}"
                    type="chat"
                    id="${u.getUniqueId()}"
                    xmlns="jabber:client">
                <body>Hello</body>
                <active xmlns="http://jabber.org/protocol/chatstates"/>
            </message>`
        );
        await new Promise(resolve => view.model.messages.once('rendered', resolve));
        expect(view.querySelector('.chat-msg .chat-msg__action .chat-msg__action-edit')).toBeNull()

        // Test confirmation dialog
        spyOn(_converse.api, 'confirm').and.callFake(() => Promise.resolve(true));
        textarea.value = 'But soft, what light through yonder airlock breaks?';
        action = view.querySelector('.chat-msg .chat-msg__action');
        action.style.opacity = 1;
        action.click();

        await u.waitUntil(() => _converse.api.confirm.calls.count());
        expect(_converse.api.confirm).toHaveBeenCalledWith(
            'Confirm',
            'You have an unsent message which will be lost if you continue. Are you sure?'
        );
        expect(view.model.messages.at(0).get('correcting')).toBe(true);
        await u.waitUntil(() => textarea.value === 'But soft, what light through yonder window breaks?');

        textarea.value = 'But soft, what light through yonder airlock breaks?'
        action.click();

        await u.waitUntil(() => _converse.api.confirm.calls.count() === 2);
        expect(view.model.messages.at(0).get('correcting')).toBe(false);
        expect(_converse.api.confirm.calls.argsFor(0)).toEqual(
            ['Confirm', 'You have an unsent message which will be lost if you continue. Are you sure?']);
        expect(_converse.api.confirm.calls.argsFor(1)).toEqual(
            ['Confirm', 'You have an unsent message which will be lost if you continue. Are you sure?']);
    }));


    describe("when received from someone else", function () {

        it("can be replaced with a correction",
                mock.initConverse(['chatBoxesFetched'], {}, async function (_converse) {

            const { api } = _converse;
            await mock.waitForRoster(_converse, 'current', 1);
            await mock.openControlBox(_converse);
            const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
            const msg_id = u.getUniqueId();
            const view = await mock.openChatBoxFor(_converse, sender_jid);
            _converse.handleMessageStanza(stx`
                <message from="${sender_jid}"
                        xmlns="jabber:client"
                        to="${api.connection.get().jid}"
                        type="chat"
                        id="${msg_id}">
                    <body>But soft, what light through yonder airlock breaks?</body>
                </message>`);
            await new Promise(resolve => view.model.messages.once('rendered', resolve));
            expect(view.querySelectorAll('.chat-msg').length).toBe(1);
            expect(view.querySelector('.chat-msg__text').textContent)
                .toBe('But soft, what light through yonder airlock breaks?');

            _converse.handleMessageStanza(stx`
                <message from="${sender_jid}"
                        xmlns="jabber:client"
                        to="${api.connection.get().jid}"
                        type="chat"
                        id="${u.getUniqueId()}">
                    <body>But soft, what light through yonder chimney breaks?</body>
                    <replace id="${msg_id}" xmlns="urn:xmpp:message-correct:0"/>
                </message>`);
            await new Promise(resolve => view.model.messages.once('rendered', resolve));

            expect(view.querySelector('.chat-msg__text').textContent)
                .toBe('But soft, what light through yonder chimney breaks?');
            expect(view.querySelectorAll('.chat-msg').length).toBe(1);
            expect(view.querySelectorAll('.chat-msg__content .fa-edit').length).toBe(1);
            expect(view.model.messages.models.length).toBe(1);

            _converse.handleMessageStanza(stx`
                <message from="${sender_jid}"
                        xmlns="jabber:client"
                        to="${api.connection.get().jid}"
                        type="chat"
                        id="${u.getUniqueId()}">
                    <body>But soft, what light through yonder window breaks?</body>
                    <replace id="${msg_id}" xmlns="urn:xmpp:message-correct:0"/>
                </message>`);
            await new Promise(resolve => view.model.messages.once('rendered', resolve));

            expect(view.querySelector('.chat-msg__text').textContent)
                .toBe('But soft, what light through yonder window breaks?');
            expect(view.querySelectorAll('.chat-msg').length).toBe(1);
            expect(view.querySelectorAll('.chat-msg__content .fa-edit').length).toBe(1);
            view.querySelector('.chat-msg__content .fa-edit').click();

            const modal = _converse.api.modal.get('converse-message-versions-modal');
            await u.waitUntil(() => u.isVisible(modal), 1000);
            const older_msgs = modal.querySelectorAll('.older-msg');
            expect(older_msgs.length).toBe(2);
            expect(older_msgs[0].textContent.includes('But soft, what light through yonder airlock breaks?')).toBe(true);
            expect(view.model.messages.models.length).toBe(1);
        }));
    });
});
