/*
Copyright 2023 Suguru Hirahara

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 { Download, type Page } from "@playwright/test";

import { test, expect } from "../../element-web-test";
import { viewRoomSummaryByName } from "./utils";

const ROOM_NAME = "Test room";
const NAME = "Alice";

async function uploadFile(page: Page, file: string) {
    // Upload a file from the message composer
    await page.locator(".mx_MessageComposer_actions input[type='file']").setInputFiles(file);

    await page.locator(".mx_Dialog").getByRole("button", { name: "Upload" }).click();

    // Wait until the file is sent
    await expect(page.locator(".mx_RoomView_statusArea_expanded")).not.toBeVisible();
    await expect(page.locator(".mx_EventTile.mx_EventTile_last .mx_EventTile_receiptSent")).toBeVisible();
}

test.describe("FilePanel", () => {
    test.use({
        displayName: NAME,
    });

    test.beforeEach(async ({ page, user, app }) => {
        await app.client.createRoom({ name: ROOM_NAME });

        // Open the file panel
        await viewRoomSummaryByName(page, app, ROOM_NAME);
        await page.getByRole("menuitem", { name: "Files" }).click();
        await expect(page.locator(".mx_FilePanel")).toBeVisible();
    });

    test.describe("render", () => {
        test("should render empty state", async ({ page }) => {
            // Wait until the information about the empty state is rendered
            await expect(page.locator(".mx_EmptyState")).toBeVisible();

            // Take a snapshot of RightPanel - fix https://github.com/vector-im/element-web/issues/25332
            await expect(page.locator(".mx_RightPanel")).toMatchScreenshot("empty.png");
        });

        test("should list tiles on the panel", async ({ page }) => {
            // Upload multiple files
            await uploadFile(page, "playwright/sample-files/riot.png"); // Image
            await uploadFile(page, "playwright/sample-files/1sec.ogg"); // Audio
            await uploadFile(page, "playwright/sample-files/matrix-org-client-versions.json"); // JSON

            const roomViewBody = page.locator(".mx_RoomView_body");
            // Assert that all of the file were uploaded and rendered
            await expect(roomViewBody.locator(".mx_EventTile[data-layout='group']")).toHaveCount(3);

            // Assert that the image exists and has the alt string
            await expect(roomViewBody.locator(".mx_EventTile[data-layout='group'] img[alt='riot.png']")).toBeVisible();

            // Assert that the audio player is rendered
            await expect(
                roomViewBody.locator(".mx_EventTile[data-layout='group'] .mx_AudioPlayer_container"),
            ).toBeVisible();

            // Assert that the file button exists
            await expect(
                roomViewBody.locator(".mx_EventTile_last[data-layout='group'] .mx_MFileBody", { hasText: ".json" }),
            ).toBeVisible();

            const filePanel = page.locator(".mx_FilePanel");
            // Assert that the file panel is opened inside mx_RightPanel and visible
            await expect(filePanel).toBeVisible();

            const filePanelMessageList = filePanel.locator(".mx_RoomView_MessageList");

            // Assert that data-layout attribute is not applied to file tiles on the panel
            await expect(filePanelMessageList.locator(".mx_EventTile[data-layout]")).not.toBeVisible();

            // Assert that all of the file tiles are rendered
            await expect(filePanelMessageList.locator(".mx_EventTile")).toHaveCount(3);

            // Assert that the download links are rendered
            await expect(filePanelMessageList.locator(".mx_MFileBody_download")).toHaveCount(3);

            // Assert that the sender of the files is rendered on all of the tiles
            await expect(filePanelMessageList.getByText(NAME)).toHaveCount(3);

            // Detect the image file
            const image = filePanelMessageList.locator(".mx_EventTile_mediaLine.mx_EventTile_image .mx_MImageBody");
            // Assert that the image is specified as thumbnail and has the alt string
            await expect(image.locator("img[class='mx_MImageBody_thumbnail']")).toBeVisible();
            await expect(image.locator("img[alt='riot.png']")).toBeVisible();

            // Detect the audio file
            const audio = filePanelMessageList.locator(
                ".mx_EventTile_mediaLine .mx_MAudioBody .mx_AudioPlayer_container",
            );
            // Assert that the play button is rendered
            await expect(audio.getByRole("button", { name: "Play" })).toBeVisible();

            // Detect the JSON file
            // Assert that the tile is rendered as a button
            const file = filePanelMessageList.locator(
                ".mx_EventTile_mediaLine .mx_MFileBody .mx_MFileBody_info[role='button'] .mx_MFileBody_info_filename",
            );
            // Assert that the file name is rendered inside the button with ellipsis
            await expect(file.getByText(/matrix.*?\.json/)).toBeVisible();

            // Make the viewport tall enough to display all of the file tiles on FilePanel
            await page.setViewportSize({ width: 800, height: 1000 });

            // In case the panel is scrollable on the resized viewport
            // Assert that the value for flexbox is applied
            await expect(filePanel.locator(".mx_ScrollPanel .mx_RoomView_MessageList")).toHaveCSS(
                "justify-content",
                "flex-end",
            );
            // Assert that all of the file tiles are visible before taking a snapshot
            await expect(filePanelMessageList.locator(".mx_MImageBody")).toBeVisible(); // top
            await expect(filePanelMessageList.locator(".mx_MAudioBody")).toBeVisible(); // middle
            const senderDetails = filePanelMessageList.locator(".mx_EventTile_last .mx_EventTile_senderDetails");
            await expect(senderDetails.locator(".mx_DisambiguatedProfile")).toBeVisible();
            await expect(senderDetails.locator(".mx_MessageTimestamp")).toBeVisible();

            // Take a snapshot of file tiles list on FilePanel
            await expect(filePanelMessageList).toMatchScreenshot("file-tiles-list.png", {
                // Exclude timestamps & flaky seek bar from snapshot
                mask: [page.locator(".mx_MessageTimestamp, .mx_AudioPlayer_seek")],
            });
        });

        test("should render the audio player and play the audio file on the panel", async ({ page }) => {
            // Upload an image file
            await uploadFile(page, "playwright/sample-files/1sec.ogg");

            const audioBody = page.locator(
                ".mx_FilePanel .mx_RoomView_MessageList .mx_EventTile_mediaLine .mx_MAudioBody .mx_AudioPlayer_container",
            );
            // Assert that the audio player is rendered
            // Assert that the audio file information is rendered
            const mediaInfo = audioBody.locator(".mx_AudioPlayer_mediaInfo");
            await expect(mediaInfo.locator(".mx_AudioPlayer_mediaName").getByText("1sec.ogg")).toBeVisible();
            await expect(mediaInfo.locator(".mx_AudioPlayer_byline", { hasText: "00:01" })).toBeVisible();
            await expect(mediaInfo.locator(".mx_AudioPlayer_byline", { hasText: "(3.56 KB)" })).toBeVisible(); // actual size

            // Assert that the duration counter is 00:01 before clicking the play button
            await expect(audioBody.locator(".mx_AudioPlayer_mediaInfo time", { hasText: "00:01" })).toBeVisible();

            // Assert that the counter is zero before clicking the play button
            await expect(audioBody.locator(".mx_AudioPlayer_seek [role='timer']", { hasText: "00:00" })).toBeVisible();

            // Click the play button
            await audioBody.getByRole("button", { name: "Play" }).click();

            // Assert that the pause button is rendered
            await expect(audioBody.getByRole("button", { name: "Pause" })).toBeVisible();

            // Assert that the timer is reset when the audio file finished playing
            await expect(audioBody.locator(".mx_AudioPlayer_seek [role='timer']", { hasText: "00:00" })).toBeVisible();

            // Assert that the play button is rendered
            await expect(audioBody.getByRole("button", { name: "Play" })).toBeVisible();
        });

        test("should render file size in kibibytes on a file tile", async ({ page }) => {
            const size = "1.12 KB"; // actual file size in kibibytes (1024 bytes)

            // Upload a file
            await uploadFile(page, "playwright/sample-files/matrix-org-client-versions.json");

            const tile = page.locator(".mx_FilePanel .mx_EventTile");
            // Assert that the file size is displayed in kibibytes, not kilobytes (1000 bytes)
            // See: https://github.com/vector-im/element-web/issues/24866
            await expect(tile.locator(".mx_MFileBody_info_filename", { hasText: size })).toBeVisible();
            await expect(tile.locator(".mx_MFileBody_download a", { hasText: size })).toBeVisible();
            await expect(tile.locator(".mx_MFileBody_download .mx_MImageBody_size", { hasText: size })).toBeVisible();
        });
    });

    test.describe("download", () => {
        test("should download an image via the link on the panel", async ({ page, context }) => {
            // Upload an image file
            await uploadFile(page, "playwright/sample-files/riot.png");

            // Detect the image file on the panel
            const imageBody = page.locator(
                ".mx_FilePanel .mx_RoomView_MessageList .mx_EventTile_mediaLine.mx_EventTile_image .mx_MImageBody",
            );

            const link = imageBody.locator(".mx_MFileBody_download a");

            const newPagePromise = context.waitForEvent("page");

            const downloadPromise = new Promise<Download>((resolve) => {
                page.once("download", resolve);
            });

            // Click the anchor link (not the image itself)
            await link.click();

            const newPage = await newPagePromise;
            // XXX: Clicking the link opens the image in a new tab on some browsers rather than downloading
            await expect(newPage)
                .toHaveURL(/.+\/_matrix\/media\/\w+\/download\/localhost\/\w+/)
                .catch(async () => {
                    const download = await downloadPromise;
                    expect(download.suggestedFilename()).toBe("riot.png");
                });
        });
    });
});
