import {expect} from 'chai';

import type {Contact, Conversation} from '~/common/model';
import type {ModelStore} from '~/common/model/utils/model-store';
import {randomMessageId} from '~/common/network/protocol/utils';
import type {EmojiReaction} from '~/common/network/types';
import {tag} from '~/common/types';
import {assert} from '~/common/utils/assert';
import {
    addTestUserAsContact,
    makeTestServices,
    makeTestUser,
    TestTweetNaClBackend,
    type TestServices,
} from '~/test/mocha/common/backend-mocks';
import {createFileMessage, randomBlobId} from '~/test/mocha/common/db-backend-tests';

export function run(): void {
    describe('Message reaction model', function () {
        const me = makeTestUser('MEMEMEME');
        const anotherUser = makeTestUser('USER0001');

        const crypto = new TestTweetNaClBackend();

        let services: TestServices;
        let contact: ModelStore<Contact>;
        let conversation: ModelStore<Conversation>;

        const messageId = randomMessageId(crypto);

        const testCase = {
            init: {
                blobId: randomBlobId(),
                blobDownloadState: undefined,
                fileData: undefined,
            },
            expectedState: 'unsynced',
        };

        this.beforeEach(function () {
            services = makeTestServices(me.identity.string);
            contact = addTestUserAsContact(services.model, anotherUser);
            conversation = contact.get().controller.conversation();
        });

        // Set up log for failed tests
        this.afterEach(function () {
            if (this.currentTest?.state === 'failed') {
                console.log('--- Failed test logs start ---');
                services.logging.printLogs();
                console.log('--- Failed test logs end ---');
            }
        });

        it('Initialize an incoming message and add/withdraw reactions', function () {
            createFileMessage(services.model.db, {
                // Message ID
                id: messageId,
                // Make it an inbound message
                senderContactUid: contact.ctx,
                conversationUid: conversation.ctx,
                raw: new Uint8Array(123),
                processedAt: new Date(),
                // File data
                mediaType: 'application/pdf',
                fileName: 'document.pdf',
                fileSize: 1234,
                ...testCase.init,
            });
            const message = conversation.get().controller.getMessage(messageId);

            assert(message !== undefined, 'Message must not be undefined');
            expect(message.get().view.history.length).to.equal(
                0,
                'No message history should be present',
            );

            assert(message.type === 'file');

            const reactionDate = new Date();
            message
                .get()
                .controller.addReaction.direct(
                    tag<EmojiReaction>('🙈'),
                    reactionDate,
                    services.device.identity.string,
                );

            expect(message.get().view.reactions[0]).to.deep.equal({
                reactionAt: reactionDate,
                reaction: '🙈',
                senderIdentity: services.device.identity.string,
            });

            // Add to reactions of different shade from same identity
            message
                .get()
                .controller.addReaction.direct(
                    tag<EmojiReaction>('👋🏼'),
                    reactionDate,
                    services.device.identity.string,
                );

            // Add to reactions of different shade from same identity
            message
                .get()
                .controller.addReaction.direct(
                    tag<EmojiReaction>('👋'),
                    reactionDate,
                    services.device.identity.string,
                );

            expect(
                message.get().view.reactions.map((reactions) => reactions.reaction),
                'All three reactions should be present',
            ).to.have.members(['👋🏼', '👋', '🙈']);

            message
                .get()
                .controller.withdrawReaction.direct(
                    tag<EmojiReaction>('👋🏼'),
                    services.device.identity.string,
                );

            expect(
                message.get().view.reactions.map((reactions) => reactions.reaction),
                'The reaction 👋🏼 should have been withdrawn',
            ).to.have.members(['👋', '🙈']);

            // Now let sender react to its own message
            message
                .get()
                .controller.addReaction.direct(
                    tag<EmojiReaction>('👋🏼'),
                    reactionDate,
                    contact.get().view.identity,
                );

            expect(
                message.get().view.reactions.length,
                'Message should have three reactions',
            ).to.eq(3);
            const incomingReaction = message
                .get()
                .view.reactions.filter((r) => r.senderIdentity === contact.get().view.identity);

            expect(incomingReaction.length).to.eq(1);
            expect(incomingReaction[0]).to.deep.eq({
                reactionAt: reactionDate,
                reaction: '👋🏼',
                senderIdentity: contact.get().view.identity,
            });
        });
    });
}
