/*
Copyright 2023 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import { Page, Request } from "@playwright/test";

import { test, expect } from "../../element-web-test";
import type { ElementAppPage } from "../../pages/ElementAppPage";
import type { Bot } from "../../pages/bot";

test.describe("Sliding Sync", () => {
    let roomId: string;

    test.beforeEach(async ({ slidingSyncProxy, page, user, app }) => {
        roomId = await app.client.createRoom({ name: "Test Room" });
    });

    const checkOrder = async (wantOrder: string[], page: Page) => {
        await expect(page.getByRole("group", { name: "Rooms" }).locator(".mx_RoomTile_title")).toHaveText(wantOrder);
    };

    const bumpRoom = async (roomId: string, app: ElementAppPage) => {
        // Send a message into the given room, this should bump the room to the top
        console.log("sendEvent", app.client.sendEvent);
        await app.client.sendEvent(roomId, null, "m.room.message", {
            body: "Hello world",
            msgtype: "m.text",
        });
    };

    const createAndJoinBot = async (app: ElementAppPage, bot: Bot): Promise<Bot> => {
        await bot.prepareClient();
        const bobUserId = await bot.evaluate((client) => client.getUserId());
        await app.client.evaluate(
            async (client, { bobUserId, roomId }) => {
                await client.invite(roomId, bobUserId);
            },
            { bobUserId, roomId },
        );
        await bot.joinRoom(roomId);
        return bot;
    };

    test.skip("should render the Rooms list in reverse chronological order by default and allowing sorting A-Z", async ({
        page,
        app,
    }) => {
        // create rooms and check room names are correct
        for (const fruit of ["Apple", "Pineapple", "Orange"]) {
            await app.client.createRoom({ name: fruit });
            await expect(page.getByRole("treeitem", { name: fruit })).toBeVisible();
        }

        // Check count, 3 fruits + 1 room created in beforeEach = 4
        await expect(page.locator(".mx_RoomSublist_tiles").getByRole("treeitem")).toHaveCount(4);
        await checkOrder(["Orange", "Pineapple", "Apple", "Test Room"], page);

        const locator = page.getByRole("group", { name: "Rooms" }).locator(".mx_RoomSublist_headerContainer");
        await locator.hover();
        await locator.getByRole("button", { name: "List options" }).click();

        // force click as the radio button's size is zero
        await page.getByRole("menuitemradio", { name: "A-Z" }).dispatchEvent("click");
        await expect(page.locator(".mx_StyledRadioButton_checked").getByText("A-Z")).toBeVisible();

        await page.pause();
        await checkOrder(["Apple", "Orange", "Pineapple", "Test Room"], page);
    });

    test.skip("should move rooms around as new events arrive", async ({ page, app }) => {
        // create rooms and check room names are correct
        const roomIds: string[] = [];
        for (const fruit of ["Apple", "Pineapple", "Orange"]) {
            const id = await app.client.createRoom({ name: fruit });
            roomIds.push(id);
            await expect(page.getByRole("treeitem", { name: fruit })).toBeVisible();
        }

        // Select the Test Room
        await page.getByRole("treeitem", { name: "Test Room" }).click();
        const [apple, pineapple, orange] = roomIds;
        await checkOrder(["Orange", "Pineapple", "Apple", "Test Room"], page);
        await bumpRoom(apple, app);
        await checkOrder(["Apple", "Orange", "Pineapple", "Test Room"], page);
        await bumpRoom(orange, app);
        await checkOrder(["Orange", "Apple", "Pineapple", "Test Room"], page);
        await bumpRoom(orange, app);
        await checkOrder(["Orange", "Apple", "Pineapple", "Test Room"], page);
        await bumpRoom(pineapple, app);
        await checkOrder(["Pineapple", "Orange", "Apple", "Test Room"], page);
    });

    test.skip("should not move the selected room: it should be sticky", async ({ page, app }) => {
        // create rooms and check room names are correct
        const roomIds: string[] = [];
        for (const fruit of ["Apple", "Pineapple", "Orange"]) {
            const id = await app.client.createRoom({ name: fruit });
            roomIds.push(id);
            await expect(page.getByRole("treeitem", { name: fruit })).toBeVisible();
        }

        // Given a list of Orange, Pineapple, Apple - if Pineapple is active and a message is sent in Apple, the list should
        // turn into Apple, Pineapple, Orange - the index position of Pineapple never changes even though the list should technically
        // be Apple, Orange Pineapple - only when you click on a different room do things reshuffle.

        // Select the Pineapple room
        await page.getByRole("treeitem", { name: "Pineapple" }).click();
        await checkOrder(["Orange", "Pineapple", "Apple", "Test Room"], page);

        // Move Apple
        await bumpRoom(roomIds[0], app);
        await checkOrder(["Apple", "Pineapple", "Orange", "Test Room"], page);

        // Select the Test Room
        await page.getByRole("treeitem", { name: "Test Room" }).click();

        // the rooms reshuffle to match reality
        await checkOrder(["Apple", "Orange", "Pineapple", "Test Room"], page);
    });

    test.skip("should show the right unread notifications", async ({ page, app, user, bot }) => {
        const bob = await createAndJoinBot(app, bot);

        // send a message in the test room: unread notification count should increment
        await bob.sendMessage(roomId, "Hello World");

        const treeItemLocator1 = page.getByRole("treeitem", { name: "Test Room 1 unread message." });
        await expect(treeItemLocator1.locator(".mx_NotificationBadge_count")).toHaveText("1");
        // await expect(page.locator(".mx_NotificationBadge")).not.toHaveClass("mx_NotificationBadge_highlighted");
        await expect(treeItemLocator1.locator(".mx_NotificationBadge")).not.toHaveClass(
            /mx_NotificationBadge_highlighted/,
        );

        // send an @mention: highlight count (red) should be 2.
        await bob.sendMessage(roomId, `Hello ${user.displayName}`);
        const treeItemLocator2 = page.getByRole("treeitem", {
            name: "Test Room 2 unread messages including mentions.",
        });
        await expect(treeItemLocator2.locator(".mx_NotificationBadge_count")).toHaveText("2");
        await expect(treeItemLocator2.locator(".mx_NotificationBadge")).toHaveClass(/mx_NotificationBadge_highlighted/);

        // click on the room, the notif counts should disappear
        await page.getByRole("treeitem", { name: "Test Room 2 unread messages including mentions." }).click();
        await expect(
            page.getByRole("treeitem", { name: "Test Room" }).locator("mx_NotificationBadge_count"),
        ).not.toBeAttached();
    });

    test.skip("should not show unread indicators", async ({ page, app, bot }) => {
        // TODO: for now. Later we should.
        await createAndJoinBot(app, bot);

        // disable notifs in this room (TODO: CS API call?)
        const locator = page.getByRole("treeitem", { name: "Test Room" });
        await locator.hover();
        await locator.getByRole("button", { name: "Notification options" }).click();
        await page.getByRole("menuitemradio", { name: "Mute room" }).click();

        // create a new room so we know when the message has been received as it'll re-shuffle the room list
        await app.client.createRoom({ name: "Dummy" });

        await checkOrder(["Dummy", "Test Room"], page);

        await bot.sendMessage(roomId, "Do you read me?");

        // wait for this message to arrive, tell by the room list resorting
        await checkOrder(["Test Room", "Dummy"], page);

        await expect(
            page.getByRole("treeitem", { name: "Test Room" }).locator(".mx_NotificationBadge"),
        ).not.toBeAttached();
    });

    test("should update user settings promptly", async ({ page, app }) => {
        await app.settings.openUserSettings("Preferences");
        const locator = page.locator(".mx_SettingsFlag").filter({ hasText: "Show timestamps in 12 hour format" });
        expect(locator).toBeVisible();
        expect(locator.locator(".mx_ToggleSwitch_on")).not.toBeAttached();
        await locator.locator(".mx_ToggleSwitch_ball").click();
        expect(locator.locator(".mx_ToggleSwitch_on")).toBeAttached();
    });

    test.skip("should show and be able to accept/reject/rescind invites", async ({ page, app, bot }) => {
        await createAndJoinBot(app, bot);

        const clientUserId = await app.client.evaluate((client) => client.getUserId());

        // invite bot into 3 rooms:
        // - roomJoin: will join this room
        // - roomReject: will reject the invite
        // - roomRescind: will make Bob rescind the invite
        const roomNames = ["Room to Join", "Room to Reject", "Room to Rescind"];
        const roomRescind = await bot.evaluate(
            async (client, { roomNames, clientUserId }) => {
                const rooms = await Promise.all(roomNames.map((name) => client.createRoom({ name })));
                await Promise.all(rooms.map((room) => client.invite(room.room_id, clientUserId)));
                return rooms[2].room_id;
            },
            { roomNames, clientUserId },
        );

        await expect(
            page.getByRole("group", { name: "Invites" }).locator(".mx_RoomSublist_tiles").getByRole("treeitem"),
        ).toHaveCount(3);

        // Select the room to join
        await page.getByRole("treeitem", { name: "Room to Join" }).click();

        // Accept the invite
        await page.locator(".mx_RoomView").getByRole("button", { name: "Accept" }).click();

        await checkOrder(["Room to Join", "Test Room"], page);

        // Select the room to reject
        await page.getByRole("treeitem", { name: "Room to Reject" }).click();

        // Reject the invite
        await page.locator(".mx_RoomView").getByRole("button", { name: "Reject", exact: true }).click();

        await expect(
            page.getByRole("group", { name: "Invites" }).locator(".mx_RoomSublist_tiles").getByRole("treeitem"),
        ).toHaveCount(2);

        // check the lists are correct
        await checkOrder(["Room to Join", "Test Room"], page);

        const titleLocator = page.getByRole("group", { name: "Invites" }).locator(".mx_RoomTile_title");
        await expect(titleLocator).toHaveCount(1);
        await expect(titleLocator).toHaveText("Room to Rescind");

        // now rescind the invite
        await bot.evaluate(
            async (client, { roomRescind, clientUserId }) => {
                client.kick(roomRescind, clientUserId);
            },
            { roomRescind, clientUserId },
        );

        // Wait for the rescind to take effect and check the joined list once more
        await expect(
            page.getByRole("group", { name: "Rooms" }).locator(".mx_RoomSublist_tiles").getByRole("treeitem"),
        ).toHaveCount(2);

        await checkOrder(["Room to Join", "Test Room"], page);
    });

    test("should show a favourite DM only in the favourite sublist", async ({ page, app }) => {
        const roomId = await app.client.createRoom({
            name: "Favourite DM",
            is_direct: true,
        });
        await app.client.evaluate(async (client, roomId) => {
            client.setRoomTag(roomId, "m.favourite", { order: 0.5 });
        }, roomId);
        await expect(page.getByRole("group", { name: "Favourites" }).getByText("Favourite DM")).toBeVisible();
        await expect(page.getByRole("group", { name: "People" }).getByText("Favourite DM")).not.toBeAttached();
    });

    // Regression test for a bug in SS mode, but would be useful to have in non-SS mode too.
    // This ensures we are setting RoomViewStore state correctly.
    test.skip("should clear the reply to field when swapping rooms", async ({ page, app }) => {
        await app.client.createRoom({ name: "Other Room" });
        await expect(page.getByRole("treeitem", { name: "Other Room" })).toBeVisible();
        await app.client.sendMessage(roomId, "Hello world");

        // select the room
        await page.getByRole("treeitem", { name: "Test Room" }).click();

        await expect(page.locator(".mx_ReplyPreview")).not.toBeAttached();

        // click reply-to on the Hello World message
        const locator = page.locator(".mx_EventTile_last");
        await locator.getByText("Hello world").hover();
        await locator.getByRole("button", { name: "Reply", exact: true }).click({});

        // check it's visible
        await expect(page.locator(".mx_ReplyPreview")).toBeVisible();

        // now click Other Room
        await page.getByRole("treeitem", { name: "Other Room" }).click();

        // ensure the reply-to disappears
        await expect(page.locator(".mx_ReplyPreview")).not.toBeAttached();

        // click back
        await page.getByRole("treeitem", { name: "Test Room" }).click();

        // ensure the reply-to reappears
        await expect(page.locator(".mx_ReplyPreview")).toBeVisible();
    });

    // Regression test for https://github.com/vector-im/element-web/issues/21462
    test.skip("should not cancel replies when permalinks are clicked", async ({ page, app }) => {
        // we require a first message as you cannot click the permalink text with the avatar in the way
        await app.client.sendMessage(roomId, "First message");
        await app.client.sendMessage(roomId, "Permalink me");
        await app.client.sendMessage(roomId, "Reply to me");

        // select the room
        await page.getByRole("treeitem", { name: "Test Room" }).click();
        await expect(page.locator(".mx_ReplyPreview")).not.toBeAttached();

        // click reply-to on the Reply to me message
        const locator = page.locator(".mx_EventTile").last();
        await locator.getByText("Reply to me").hover();
        await locator.getByRole("button", { name: "Reply", exact: true }).click();

        // check it's visible
        await expect(page.locator(".mx_ReplyPreview")).toBeVisible();

        // now click on the permalink for Permalink me
        await page.locator(".mx_EventTile").filter({ hasText: "Permalink me" }).locator("a").dispatchEvent("click");

        // make sure it is now selected with the little green |
        await expect(page.locator(".mx_EventTile_selected").filter({ hasText: "Permalink me" })).toBeVisible();

        // ensure the reply-to does not disappear
        await expect(page.locator(".mx_ReplyPreview")).toBeVisible();
    });

    test.skip("should send unsubscribe_rooms for every room switch", async ({ page, app }) => {
        // create rooms and check room names are correct
        const roomIds: string[] = [];
        for (const fruit of ["Apple", "Pineapple", "Orange"]) {
            const id = await app.client.createRoom({ name: fruit });
            roomIds.push(id);
            await expect(page.getByRole("treeitem", { name: fruit })).toBeVisible();
        }
        const [roomAId, roomPId] = roomIds;

        const assertUnsubExists = (request: Request, subRoomId: string, unsubRoomId: string) => {
            const body = request.postDataJSON();
            // There may be a request without a txn_id, ignore it, as there won't be any subscription changes
            if (body.txn_id === undefined) {
                return;
            }
            expect(body.unsubscribe_rooms).toEqual([unsubRoomId]);
            expect(body.room_subscriptions).not.toHaveProperty(unsubRoomId);
            expect(body.room_subscriptions).toHaveProperty(subRoomId);
        };

        let promise = page.waitForRequest(/sync/);

        // Select the Test Room
        await page.getByRole("treeitem", { name: "Apple", exact: true }).click();

        // and wait for playwright to get the request
        const roomSubscriptions = (await promise).postDataJSON().room_subscriptions;
        expect(roomSubscriptions, "room_subscriptions is object").toBeDefined();

        // Switch to another room
        promise = page.waitForRequest(/sync/);
        await page.getByRole("treeitem", { name: "Pineapple", exact: true }).click();
        assertUnsubExists(await promise, roomPId, roomAId);

        // And switch to even another room
        promise = page.waitForRequest(/sync/);
        await page.getByRole("treeitem", { name: "Apple", exact: true }).click();
        assertUnsubExists(await promise, roomPId, roomAId);

        // TODO: Add tests for encrypted rooms
    });
});
