import { describe, expect, test } from "@odoo/hoot";
import { leave } from "@odoo/hoot-dom";

import { withUser } from "@web/../tests/_framework/mock_server/mock_server";
import { Command, serverState } from "@web/../tests/web_test_helpers";
import { rpc } from "@web/core/network/rpc";
import {
    assertSteps,
    click,
    contains,
    defineMailModels,
    hover,
    insertText,
    onRpcBefore,
    start,
    startServer,
    step,
    triggerHotkey,
} from "../mail_test_helpers";

describe.current.tags("desktop");
defineMailModels();

test("Folded chat windows are displayed as chat bubbles", async () => {
    const pyEnv = await startServer();
    pyEnv["discuss.channel"].create([
        {
            name: "Channel A",
            channel_member_ids: [
                Command.create({ fold_state: "folded", partner_id: serverState.partnerId }),
            ],
        },
        {
            name: "Channel B",
            channel_member_ids: [
                Command.create({ fold_state: "folded", partner_id: serverState.partnerId }),
            ],
        },
    ]);
    await start();
    await contains(".o-mail-ChatBubble", { count: 2 });
    await click(".o-mail-ChatBubble", { count: 2 });
    await contains(".o-mail-ChatBubble", { count: 1 });
    await contains(".o-mail-ChatWindow", { count: 1 });
});

test.tags("focus required")("'New message' chat window can only be open", async () => {
    const pyEnv = await startServer();
    const partnerId = pyEnv["res.partner"].create({ name: "John" });
    pyEnv["res.users"].create({ partner_id: partnerId });
    await start();
    await click(".o_menu_systray i[aria-label='Messages']");
    await click(".o-mail-MessagingMenu button", { text: "New Message" });
    await contains(".o-mail-ChatWindow", { count: 1 });
    await contains("[title='Fold']", { count: 0 });
    await insertText(".o-discuss-ChannelSelector input", "John");
    await click(".o-discuss-ChannelSelector-suggestion");
    triggerHotkey("Enter");
    await contains(".o-mail-ChatWindow");
    await contains(".o-mail-Thread-empty", { text: "The conversation is empty." });
    await click(".o-mail-ChatWindow-command[title='Fold']");
    await contains(".o-mail-ChatBubble", { count: 1 }); // can fold chat
});

test("No duplicated chat bubbles", async () => {
    const pyEnv = await startServer();
    const partnerId = pyEnv["res.partner"].create({ name: "John" });
    pyEnv["res.users"].create({ partner_id: partnerId });
    await start();
    // Make bubble of "John" chat
    await click(".o_menu_systray i[aria-label='Messages']");
    await click(".o-mail-MessagingMenu button", { text: "New Message" });
    await insertText(".o-discuss-ChannelSelector input", "John");
    await click(".o-discuss-ChannelSelector-suggestion");
    triggerHotkey("Enter");
    await contains(".o-mail-ChatWindow", { text: "John" });
    await contains(".o-mail-ChatWindow", { text: "The conversation is empty." }); // wait fully loaded
    await click("button[title='Fold']");
    await contains(".o-mail-ChatBubble[name='John']");
    // Make bubble of "John" chat again
    await click(".o_menu_systray i[aria-label='Messages']");
    await click(".o-mail-MessagingMenu button", { text: "New Message" });
    await insertText(".o-discuss-ChannelSelector input", "John");
    await click(".o-discuss-ChannelSelector-suggestion");
    triggerHotkey("Enter");
    await contains(".o-mail-ChatBubble[name='John']", { count: 0 });
    await contains(".o-mail-ChatWindow", { text: "John" });
    await click(".o-mail-ChatWindow-command[title='Fold']");
    // Make again from click messaging menu item
    await click(".o_menu_systray i[aria-label='Messages']");
    await click(".o-mail-NotificationItem");
    await contains(".o-mail-ChatBubble[name='John']", { count: 0 });
    await contains(".o-mail-ChatWindow", { text: "John" });
});

test("Up to 7 chat bubbles", async () => {
    const pyEnv = await startServer();
    for (let i = 1; i <= 8; i++) {
        pyEnv["discuss.channel"].create({
            name: String(i),
            channel_member_ids: [
                Command.create({ fold_state: "folded", partner_id: serverState.partnerId }),
            ],
        });
    }
    await start();
    for (let i = 8; i > 1; i--) {
        await contains(`.o-mail-ChatBubble[name='${String(i)}']`);
    }
    await contains(".o-mail-ChatBubble[name='1']", { count: 0 });
    await contains(".o-mail-ChatHub-hiddenBtn", { text: "+1" });
    await hover(".o-mail-ChatHub-hiddenBtn");
    await contains(".o-mail-ChatHub-hiddenItem[name='1']");
    await contains(".o-mail-ChatWindow", { count: 0 });
    await click(".o-mail-ChatHub-hiddenItem");
    await contains(".o-mail-ChatWindow", { count: 1 });
    await contains(".o-mail-ChatHub-hiddenBtn", { count: 0 });
});

test("Ordering of chat bubbles is consistent and seems logical.", async () => {
    const pyEnv = await startServer();
    const partnerId = pyEnv["res.partner"].create({ name: "Demo" });
    const userId = pyEnv["res.users"].create({ partner_id: partnerId });
    const channelId = pyEnv["discuss.channel"].create({
        channel_member_ids: [
            Command.create({ fold_state: "folded", partner_id: serverState.partnerId }),
            Command.create({ fold_state: "folded", partner_id: partnerId }),
        ],
        channel_type: "chat",
    });
    for (let i = 1; i <= 7; i++) {
        pyEnv["discuss.channel"].create({
            name: String(i),
            channel_member_ids: [
                Command.create({ fold_state: "folded", partner_id: serverState.partnerId }),
            ],
        });
    }
    await start();
    // FIXME: expect arbitrary order 7, 6, 5, 4, 3, 2, 1
    await contains(":nth-child(1 of .o-mail-ChatBubble)[name='7']");
    await contains(":nth-child(2 of .o-mail-ChatBubble)[name='6']");
    await contains(":nth-child(3 of .o-mail-ChatBubble)[name='5']");
    await contains(":nth-child(4 of .o-mail-ChatBubble)[name='4']");
    await contains(":nth-child(5 of .o-mail-ChatBubble)[name='3']");
    await contains(":nth-child(6 of .o-mail-ChatBubble)[name='2']");
    await contains(":nth-child(7 of .o-mail-ChatBubble)[name='1']");
    await contains(".o-mail-ChatBubble[name='Demo']", { count: 0 });
    await contains(".o-mail-ChatWindow", { count: 0 });
    await click(".o-mail-ChatBubble[name='3']");
    await contains(".o-mail-ChatWindow", { text: "3" });
    await contains(":nth-child(7 of .o-mail-ChatBubble)[name='Demo']");
    await click(".o-mail-ChatWindow-command[title='Fold']");
    await contains(".o-mail-ChatBubble[name='Demo']", { count: 0 });
    await click(".o-mail-ChatBubble[name='4']");
    await contains(":nth-child(1 of .o-mail-ChatBubble)[name='3']");
    await contains(":nth-child(2 of .o-mail-ChatBubble)[name='7']");
    await contains(":nth-child(3 of .o-mail-ChatBubble)[name='6']");
    await contains(":nth-child(7 of .o-mail-ChatBubble)[name='Demo']");
    await click(".o-mail-ChatWindow-command[title='Fold']");
    await contains(".o-mail-ChatWindow", { count: 0 });
    // no reorder on receiving new message
    withUser(userId, () =>
        rpc("/mail/message/post", {
            post_data: { body: "test", message_type: "comment" },
            thread_id: channelId,
            thread_model: "discuss.channel",
        })
    );
    await hover(".o-mail-ChatHub-hiddenBtn");
    await contains(".o-mail-ChatHub-hiddenItem[name='Demo']");
});

test("Hover on chat bubble shows chat name + last message preview", async () => {
    const pyEnv = await startServer();
    const marcPartnerId = pyEnv["res.partner"].create({ name: "Marc" });
    const marcChannelId = pyEnv["discuss.channel"].create({
        channel_member_ids: [
            Command.create({ fold_state: "folded", partner_id: serverState.partnerId }),
            Command.create({ fold_state: "folded", partner_id: marcPartnerId }),
        ],
        channel_type: "chat",
    });
    pyEnv["mail.message"].create({
        body: "Hello!",
        model: "discuss.channel",
        author_id: marcPartnerId,
        res_id: marcChannelId,
    });
    const demoPartnerId = pyEnv["res.partner"].create({ name: "Demo" });
    const demoChannelId = pyEnv["discuss.channel"].create({
        channel_member_ids: [
            Command.create({ fold_state: "folded", partner_id: serverState.partnerId }),
            Command.create({ fold_state: "folded", partner_id: demoPartnerId }),
        ],
        channel_type: "chat",
    });
    await start();
    await hover(".o-mail-ChatBubble[name='Marc']");
    await contains(".o-mail-ChatBubble-preview", { text: "MarcHello!" });
    await leave();
    await contains(".o-mail-ChatBubble-preview", { count: 0 });
    await hover(".o-mail-ChatBubble[name='Demo']");
    await contains(".o-mail-ChatBubble-preview", { text: "Demo" });
    await leave();
    rpc("/mail/message/post", {
        post_data: { body: "Hi", message_type: "comment" },
        thread_id: demoChannelId,
        thread_model: "discuss.channel",
    });
    await hover(".o-mail-ChatBubble[name='Demo']");
    await contains(".o-mail-ChatBubble-preview", { text: "DemoYou: Hi" });
});

test("Chat bubble preview works on author as email address", async () => {
    const pyEnv = await startServer();
    const partnerId = pyEnv["res.partner"].create({ name: "TestPartner" });
    const messageId = pyEnv["mail.message"].create({
        author_id: null,
        body: "Some email message",
        email_from: "md@oilcompany.fr",
        model: "res.partner",
        needaction: true,
        res_id: partnerId,
    });
    pyEnv["mail.notification"].create({
        mail_message_id: messageId,
        notification_status: "sent",
        notification_type: "inbox",
        res_partner_id: serverState.partnerId,
    });
    await start();
    await click(".o_menu_systray i[aria-label='Messages']");
    await click(".o-mail-NotificationItem");
    await click(".o-mail-ChatWindow [title='Fold']");
    await hover(".o-mail-ChatBubble");
    await contains(".o-mail-ChatBubble-preview", { text: "md@oilcompany.fr: Some email message" });
});

test("chat bubbles are synced between tabs", async () => {
    const pyEnv = await startServer();
    const marcPartnerId = pyEnv["res.partner"].create({ name: "Marc" });
    pyEnv["discuss.channel"].create({
        channel_member_ids: [
            Command.create({ fold_state: "folded", partner_id: serverState.partnerId }),
            Command.create({ fold_state: "folded", partner_id: marcPartnerId }),
        ],
        channel_type: "chat",
    });
    const tab1 = await start({ asTab: true });
    const tab2 = await start({ asTab: true });
    await contains(".o-mail-ChatBubble", { target: tab1 });
    await contains(".o-mail-ChatBubble", { target: tab2 });
    await click(".o-mail-ChatBubble[name='Marc']", { target: tab1 });
    await contains(".o-mail-ChatWindow", { target: tab2 }); // open sync
    await click(".o-mail-ChatWindow-command[title='Fold']", { target: tab2 });
    await contains(".o-mail-ChatWindow", { target: tab1, count: 0 }); // fold sync
    await click(".o-mail-ChatBubble[name='Marc'] .o-mail-ChatBubble-close", { target: tab1 });
    await contains(".o-mail-ChatBubble[name='Marc']", { target: tab2, count: 0 }); // close sync
});

test("Chat bubbles do not fetch messages until becoming open", async () => {
    const pyEnv = await startServer();
    const [channeId1, channelId2] = pyEnv["discuss.channel"].create([
        {
            name: "Orange",
            channel_member_ids: [
                Command.create({ fold_state: "folded", partner_id: serverState.partnerId }),
            ],
        },
        {
            name: "Apple",
            channel_member_ids: [
                Command.create({ fold_state: "folded", partner_id: serverState.partnerId }),
            ],
        },
    ]);
    pyEnv["mail.message"].create([
        {
            body: "Orange",
            res_id: channeId1,
            message_type: "comment",
            model: "discuss.channel",
        },
        {
            body: "Apple",
            res_id: channelId2,
            message_type: "comment",
            model: "discuss.channel",
        },
    ]);
    onRpcBefore("/discuss/channel/messages", () => expect.step("fetch_messages"));
    await start();
    await contains(".o-mail-ChatBubble[name='Orange']");
    expect.verifySteps([]);
    await click(".o-mail-ChatBubble[name='Orange']");
    await contains(".o-mail-ChatWindow");
    await contains(".o-mail-Message-content", { text: "Orange" });
    await contains(".o-mail-Message-content", { count: 0, text: "Apple" });
    expect.verifySteps(["fetch_messages"]); // from "Orange" becoming open
});

test("More than 7 actually folded chat windows shows a 'hidden' chat bubble menu", async () => {
    const pyEnv = await startServer();
    for (let i = 1; i <= 8; i++) {
        pyEnv["discuss.channel"].create({
            name: String(i),
            channel_member_ids: [
                Command.create({ fold_state: "folded", partner_id: serverState.partnerId }),
            ],
        });
    }
    await start();
    // Can make chat from hidden menu
    await hover(".o-mail-ChatHub-hiddenBtn");
    await click(".o-mail-ChatHub-hiddenItem");
    await leave(); // FIXME: hover is persistent otherwise
    await contains(".o-mail-ChatHub-hiddenItem", { count: 0 });
    await contains(".o-mail-ChatHub-hiddenBtn", { count: 0 });
    await contains(".o-mail-ChatWindow");
    await click(".o-mail-ChatWindow-command[title='Fold']");
    // Can open hidden chat from messaging menu
    await click("i[aria-label='Messages']");
    await click(".o-mail-NotificationItem", { text: "2" });
    await contains(".o-mail-ChatHub-hiddenItem", { count: 0 });
    await contains(".o-mail-ChatHub-hiddenBtn", { count: 0 });
    await contains(".o-mail-ChatWindow");
    await click(".o-mail-ChatWindow-command[title='Fold']");
    // Can close chat from hidden menu.
    await hover(".o-mail-ChatHub-hiddenBtn");
    await hover(".o-mail-ChatHub-hiddenItem");
    await click(".o-mail-ChatHub-hiddenClose");
    await contains(".o-mail-ChatHub-hiddenItem", { count: 0 });
    await contains(".o-mail-ChatHub-hiddenBtn", { count: 0 });
    await contains(".o-mail-ChatWindow", { count: 0 });
});

test("Can close all chat windows at once", async () => {
    const closed = new Set();
    onRpcBefore("/discuss/channel/fold", (args) => {
        if (args.state === "closed") {
            closed.add(args.channel_id);
        }
        if (closed.size === 20) {
            step("ALL_CLOSED");
        }
    });
    const pyEnv = await startServer();
    const channelIds = pyEnv["discuss.channel"].create(
        Array(20)
            .keys()
            .map((i) => ({
                name: String(i),
                channel_member_ids: [
                    Command.create({ fold_state: "folded", partner_id: serverState.partnerId }),
                ],
            }))
    );
    await start();
    await contains(".o-mail-ChatBubble", { count: 8 }); // max reached
    await contains(".o-mail-ChatBubble", { text: "+13" });
    await hover(".o-mail-ChatHub-hiddenBtn");
    await click("button.fa.fa-ellipsis-h[title='Chat Options']");
    await click("button.o-mail-ChatHub-option", { text: "Close all conversations" });
    await contains(".o-mail-ChatBubble", { count: 0 });
    await assertSteps(["ALL_CLOSED"]);
    const members = pyEnv["discuss.channel.member"].search_read([
        ["channel_id", "in", channelIds],
        ["partner_id", "=", serverState.partnerId],
    ]);
    expect(members.map((member) => member.fold_state)).toEqual(
        [...Array(20).keys()].map(() => "closed")
    );
});

test("Can compact chat hub", async () => {
    // allows to temporarily reduce footprint of chat windows on UI
    const pyEnv = await startServer();
    for (let i = 1; i <= 20; i++) {
        pyEnv["discuss.channel"].create({
            name: String(i),
            channel_member_ids: [
                Command.create({ fold_state: "folded", partner_id: serverState.partnerId }),
            ],
        });
    }
    await start();
    await contains(".o-mail-ChatBubble", { count: 8 }); // max reached
    await contains(".o-mail-ChatBubble", { text: "+13" });
    await hover(".o-mail-ChatHub-hiddenBtn");
    await click("button.fa.fa-ellipsis-h[title='Chat Options']");
    await click("button.o-mail-ChatHub-option", { text: "Hide all conversations" });
    await contains(".o-mail-ChatBubble i.fa.fa-commenting");
    await click(".o-mail-ChatBubble i.fa.fa-commenting");
    await contains(".o-mail-ChatBubble", { count: 8 });
    // alternative compact: click hidden button
    await click(".o-mail-ChatBubble", { text: "+13" });
    await contains(".o-mail-ChatBubble i.fa.fa-commenting");
});

test("Compacted chat hub shows badge with amount of hidden chats with important messages", async () => {
    const pyEnv = await startServer();
    for (let i = 1; i <= 20; i++) {
        const partner_id = pyEnv["res.partner"].create({ name: `partner_${i}` });
        const chatId = pyEnv["discuss.channel"].create({
            name: String(i),
            channel_member_ids: [
                Command.create({
                    fold_state: "folded",
                    partner_id: serverState.partnerId,
                }),
                Command.create({ partner_id }),
            ],
            channel_type: "chat",
        });
        if (i < 10) {
            pyEnv["mail.message"].create({
                body: "Hello!",
                model: "discuss.channel",
                author_id: partner_id,
                res_id: chatId,
            });
        }
    }
    await start();
    await contains(".o-mail-ChatBubble", { count: 8 }); // max reached
    await contains(".o-mail-ChatBubble", { text: "+13" });
    await click(".o-mail-ChatHub-hiddenBtn");
    await contains(".o-mail-ChatBubble i.fa.fa-commenting");
    await contains(".o-mail-ChatBubble .o-discuss-badge", { text: "9" });
});

test("Show IM status", async () => {
    const pyEnv = await startServer();
    const demoId = pyEnv["res.partner"].create({ name: "Demo User", im_status: "online" });
    pyEnv["discuss.channel"].create({
        channel_member_ids: [
            Command.create({
                fold_state: "folded",
                partner_id: serverState.partnerId,
            }),
            Command.create({ partner_id: demoId }),
        ],
        channel_type: "chat",
    });
    await start();
    await contains(".o-mail-ChatBubble .fa-circle.text-success[aria-label='User is online']");
});
