/*
Copyright 2024 New Vector Ltd.
Copyright 2024 The Matrix.org Foundation C.I.C.

SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/

/* See readme.md for tips on writing these tests. */

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

import { test, expect } from "../../element-web-test";
import { readSampleFileSync } from "../../sample-files";

const MEDIA_FILE = readSampleFileSync("riot.png", null);

async function waitForMessageSentStatus(msgTile: Locator): Promise<void> {
    await expect(msgTile.getByRole("status")).toHaveAccessibleName("Your message was sent");
}

async function sendMessage(page: Page, message: string): Promise<Locator> {
    await page.getByRole("textbox", { name: "Send an unencrypted message…" }).fill(message);
    await page.getByRole("button", { name: "Send message" }).click();

    const msgTile = page.locator(".mx_EventTile_last");
    await waitForMessageSentStatus(msgTile);
    return msgTile;
}

async function sendMultilineMessages(page: Page, messages: string[]) {
    await page.getByRole("textbox", { name: "Send an unencrypted message…" }).focus();
    for (let i = 0; i < messages.length; i++) {
        await page.keyboard.type(messages[i]);
        if (i < messages.length - 1) await page.keyboard.press("Shift+Enter");
    }

    await page.getByRole("button", { name: "Send message" }).click();

    const msgTile = page.locator(".mx_EventTile_last");
    await waitForMessageSentStatus(msgTile);
    return msgTile;
}

async function replyMessage(page: Page, message: Locator, replyMessage: string): Promise<Locator> {
    const line = message.locator(".mx_EventTile_line");
    await line.hover();
    await line.getByRole("button", { name: "Reply", exact: true }).click();

    await page.getByRole("textbox", { name: "Send an unencrypted reply…" }).fill(replyMessage);
    await page.getByRole("button", { name: "Send message" }).click();

    const msgTile = page.locator(".mx_EventTile_last");
    await waitForMessageSentStatus(msgTile);
    return msgTile;
}

async function editMessage(page: Page, message: Locator, newMsg: string): Promise<void> {
    const line = message.locator(".mx_EventTile_line");
    await line.hover();
    await line.getByRole("button", { name: "Edit" }).click();
    const editComposer = page.getByRole("textbox", { name: "Edit message" });
    await page.getByLabel("User menu").hover(); // Just to un-hover the message line
    await editComposer.fill(newMsg);
    await editComposer.press("Enter");
}

const screenshotOptions = (page?: Page) => ({
    // Hide the jump to bottom button in the timeline to avoid flakiness
    css: `
        .mx_MessageTimestamp {
            visibility: hidden;
        }
        .mx_JumpToBottomButton {
            display: none !important;
        }
    `,
});

test.describe("Message rendering", () => {
    [
        { direction: "ltr", displayName: "Quentin" },
        { direction: "rtl", displayName: "كوينتين" },
    ].forEach(({ direction, displayName }) => {
        test.describe(`with ${direction} display name`, { tag: "@screenshot" }, () => {
            test.use({
                displayName,
                room: async ({ user, app }, use) => {
                    const roomId = await app.client.createRoom({ name: "Test room" });
                    await use({ roomId });
                    await app.closeVerifyToast();
                },
            });

            test(
                "should render a basic LTR text message",
                { tag: "@screenshot" },
                async ({ page, user, app, room }) => {
                    await page.goto(`#/room/${room.roomId}`);

                    const msgTile = await sendMessage(page, "Hello, world!");
                    await expect(msgTile).toMatchScreenshot(
                        `basic-message-ltr-${direction}displayname.png`,
                        screenshotOptions(page),
                    );
                },
            );

            test("should render an LTR emote", async ({ page, user, app, room }) => {
                await page.goto(`#/room/${room.roomId}`);

                const msgTile = await sendMessage(page, "/me lays an egg");
                await expect(msgTile).toMatchScreenshot(`emote-ltr-${direction}displayname.png`, screenshotOptions());
            });

            test("should render an LTR rich text emote", async ({ page, user, app, room }) => {
                await page.goto(`#/room/${room.roomId}`);

                const msgTile = await sendMessage(page, "/me lays a *free range* egg");
                await expect(msgTile).toMatchScreenshot(
                    `emote-rich-ltr-${direction}displayname.png`,
                    screenshotOptions(),
                );
            });

            test("should render an edited LTR message", async ({ page, user, app, room }) => {
                await page.goto(`#/room/${room.roomId}`);

                const msgTile = await sendMessage(page, "Hello, world!");

                await editMessage(page, msgTile, "Hello, universe!");

                await expect(msgTile).toMatchScreenshot(
                    `edited-message-ltr-${direction}displayname.png`,
                    screenshotOptions(page),
                );
            });

            test("should render a reply of a LTR message", async ({ page, user, app, room }) => {
                await page.goto(`#/room/${room.roomId}`);

                const msgTile = await sendMultilineMessages(page, [
                    "Fist line",
                    "Second line",
                    "Third line",
                    "Fourth line",
                ]);

                await replyMessage(page, msgTile, "response to multiline message");
                await expect(msgTile).toMatchScreenshot(
                    `reply-message-ltr-${direction}displayname.png`,
                    screenshotOptions(page),
                );
            });

            test("should render a basic RTL text message", async ({ page, user, app, room }) => {
                await page.goto(`#/room/${room.roomId}`);

                const msgTile = await sendMessage(page, "مرحبا بالعالم!");
                await expect(msgTile).toMatchScreenshot(
                    `basic-message-rtl-${direction}displayname.png`,
                    screenshotOptions(page),
                );
            });

            test("should render an RTL emote", async ({ page, user, app, room }) => {
                await page.goto(`#/room/${room.roomId}`);

                const msgTile = await sendMessage(page, "/me يضع بيضة");
                await expect(msgTile).toMatchScreenshot(`emote-rtl-${direction}displayname.png`, screenshotOptions());
            });

            test("should render a richtext RTL emote", async ({ page, user, app, room }) => {
                await page.goto(`#/room/${room.roomId}`);

                const msgTile = await sendMessage(page, "/me أضع بيضة *حرة النطاق*");
                await expect(msgTile).toMatchScreenshot(
                    `emote-rich-rtl-${direction}displayname.png`,
                    screenshotOptions(),
                );
            });

            test("should render an edited RTL message", async ({ page, user, app, room }) => {
                await page.goto(`#/room/${room.roomId}`);

                const msgTile = await sendMessage(page, "مرحبا بالعالم!");

                await editMessage(page, msgTile, "مرحبا بالكون!");

                await expect(msgTile).toMatchScreenshot(
                    `edited-message-rtl-${direction}displayname.png`,
                    screenshotOptions(page),
                );
            });

            test("should render a reply of a RTL message", async ({ page, user, app, room }) => {
                await page.goto(`#/room/${room.roomId}`);

                const msgTile = await sendMultilineMessages(page, [
                    "مرحبا بالعالم!",
                    "مرحبا بالعالم!",
                    "مرحبا بالعالم!",
                    "مرحبا بالعالم!",
                ]);

                await replyMessage(page, msgTile, "مرحبا بالعالم!");
                await expect(msgTile).toMatchScreenshot(
                    `reply-message-trl-${direction}displayname.png`,
                    screenshotOptions(page),
                );
            });
        });
    });
});

test.describe("Message url previews", () => {
    test.use({
        displayName: "Alice",
        room: async ({ user, app }, use) => {
            const roomId = await app.client.createRoom({ name: "Test room" });
            await use({ roomId });
            await app.closeVerifyToast();
        },
    });
    test("should render a basic preview", { tag: "@screenshot" }, async ({ page, user, app, room, axe }) => {
        axe.disableRules("color-contrast");

        await page.route(/.*\/_matrix\/(client\/v1\/media|media\/v3)\/preview_url.*/, (route, request) => {
            const requestedPage = new URL(request.url()).searchParams.get("url");
            expect(requestedPage).toEqual("https://example.org/");
            return route.fulfill({
                json: {
                    "og:title": "A simple site",
                },
            });
        });
        await page.goto(`#/room/${room.roomId}`);
        const msgTile = await sendMessage(page, "https://example.org");
        await expect(msgTile.getByRole("link", { name: "A simple site" })).toBeVisible();
        await expect(axe).toHaveNoViolations();
        await expect(msgTile).toMatchScreenshot("preview-basic.png", screenshotOptions(page));
    });
    test(
        "should render a preview with a thumbnail",
        { tag: "@screenshot" },
        async ({ page, user, bot, app, room, axe }) => {
            axe.disableRules("color-contrast");
            const mxc = (await bot.uploadContent(MEDIA_FILE, { name: "image.png", type: "image/png" })).content_uri;
            await page.route(/.*\/_matrix\/(client\/v1\/media|media\/v3)\/preview_url.*/, (route, request) => {
                const requestedPage = new URL(request.url()).searchParams.get("url");
                expect(requestedPage).toEqual("https://example.org/");
                return route.fulfill({
                    json: {
                        "og:title": "A simple site",
                        "og:description": "And with a brief description",
                        "og:image": mxc,
                        "og:image:alt": "The riot logo",
                    },
                });
            });
            await page.goto(`#/room/${room.roomId}`);
            const msgTile = await sendMessage(page, "https://example.org");
            await expect(msgTile.getByRole("link", { name: "A simple site" })).toBeVisible();
            await expect(axe).toHaveNoViolations();
            await expect(msgTile).toMatchScreenshot("preview-with-thumb.png", screenshotOptions(page));
        },
    );
});
