/*
Copyright 2025 New Vector Ltd.

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

import { input } from "zod/mini";

import { test as base, expect } from "../../playwright/element-web-test.ts";
import { type ConfigSchema } from "../src/config.ts";

const test = base.extend<{
    // Resolver for when to respond to the navigation.json request
    navigationJsonResolver: PromiseWithResolvers<void>;
}>({
    navigationJsonResolver: async ({}, use) => {
        await use(Promise.withResolvers<void>());
    },
});

test.describe("Banner", () => {
    test.use({
        displayName: "Timmy",
        page: async ({ context, page, moduleDir }, use) => {
            for (const path of ["logo.svg", "app1.png", "app2.png", "opendesk/"]) {
                await context.route(`/${path}*`, async (route) => {
                    const file = new URL(route.request().url()).pathname;
                    await route.fulfill({ path: `${moduleDir}/e2e/fixture/${file}` });
                });
            }

            await page.goto("/");
            await use(page);
        },
    });

    test("should not error if config is missing", async ({ page }) => {
        await expect(page.getByText("Your Element is misconfigured")).not.toBeVisible();
        await expect(page.getByText("Errors in module configuration")).not.toBeVisible();
        // We don't take a screenshot as we don't want to assert Element's styling, only our own
    });

    test.describe("invalid config", () => {
        test.use({
            config: {
                "io.element.element-web-modules.banner": {
                    type: "invalid",
                } as any,
            },
        });

        test("should render error", async ({ page }) => {
            await expect(page.getByText("Your Element is misconfigured")).toBeVisible();
            await expect(page.getByText("Errors in module configuration")).toBeVisible();
            // We don't take a screenshot as we don't want to assert Element's styling, only our own
        });
    });

    const configs: input<ConfigSchema>[] = [
        {
            logo_url: "http://localhost:8080/logo.svg",
            logo_link_url: "https://example.com/portal",
            menu: {
                type: "static",
                categories: [
                    {
                        name: "Applications",
                        links: [
                            {
                                icon_uri: "http://localhost:8080/app1.png",
                                name: "E-Mail",
                                link_url: "https://example.com/email",
                                target: "app1",
                            },
                            {
                                icon_uri: "http://localhost:8080/app2.png",
                                name: "Riot",
                                link_url: "https://riot.im/app",
                                target: "riot-im",
                            },
                        ],
                    },
                    {
                        name: "Links",
                        links: [
                            {
                                icon_uri: "http://localhost:8080/app1.png",
                                name: "Link",
                                link_url: "https://example.com/link1",
                            },
                        ],
                    },
                ],
            },
        },
        {
            logo_url: "http://localhost:8080/opendesk/logomark.svg",
            logo_link_url: "https://example.com/portal",
            menu: {
                type: "univention",
                logo_url: "http://localhost:8080/opendesk/logofull.svg",
                ics_url: "http://localhost:8080/ics/",
            },
            theme: {
                triggerBackgroundColorHover: "#571EFA",
                triggerBackgroundColorPressed: "#4519C2",
            },
        },
    ];

    for (const config of configs) {
        const type = config.menu.type;

        test.describe(`${type} config`, () => {
            test.use({
                config: {
                    "io.element.element-web-modules.banner": config,
                },
            });

            test.beforeEach(async ({ context, moduleDir, navigationJsonResolver }) => {
                await context.route("http://localhost:8080/ics/navigation.json*", async (route) => {
                    await navigationJsonResolver.promise;
                    await route.fulfill({
                        path: `${moduleDir}/e2e/fixture/navigation.json`,
                        contentType: "application/json",
                    });
                });
                await context.route("http://localhost:8080/ics/silent", async (route) => {
                    await route.fulfill({
                        path: `${moduleDir}/e2e/fixture/silent/index.html`,
                        contentType: "text/html",
                    });
                });
            });

            test("should render", { tag: ["@screenshot"] }, async ({ page, axe, navigationJsonResolver }) => {
                await expect(page.getByRole("heading", { name: "Be in your element" })).toBeVisible();
                await expect(page.getByLabel("Show portal")).toHaveAttribute("href", "https://example.com/portal");

                const nav = page.locator("nav");

                const trigger = page.getByLabel("Show menu");
                await expect(trigger).toBeVisible();

                // Assert the banner looks as we expect
                await expect(nav).toMatchAriaSnapshot();
                await expect(nav).toMatchScreenshot(`${type}_nav.png`);

                // Check hover styles
                await trigger.hover();
                await expect(nav).toMatchScreenshot(`${type}_nav_hover.png`);

                await test.step("open menu", async () => {
                    await trigger.click();
                    const sidebar = page.getByRole("dialog");

                    if (type === "univention") {
                        await expect(sidebar).toMatchScreenshot(`${type}_menu_loading.png`);
                        navigationJsonResolver.resolve();
                    }
                    await page.pause();

                    const emailApp = page.getByText("E-Mail");
                    await expect(emailApp).toHaveAttribute("href", "https://example.com/email");
                    await emailApp.hover();

                    // Assert the sidebar looks as we expect
                    await expect(axe).toHaveNoViolations();
                    await expect(sidebar).toMatchAriaSnapshot();
                    await expect(page).toMatchScreenshot(`${type}_menu.png`, {
                        // We exclude this as we don't want to assert Element's styling, only our own
                        css: `
                            #matrixchat {
                                opacity: 0;
                                background: orchid;
                            }
                        `,
                    });

                    // Verify that the #matrixchat root got shrunk to fit rather than exploding the viewport
                    const bodyBoundingBox = await page.locator("body").boundingBox();
                    const rootBoundingBox = await page.locator("#matrixchat").boundingBox();
                    const headerBoundingBox = await page.getByTestId("banner").boundingBox();
                    expect(rootBoundingBox!.height).toBeLessThan(bodyBoundingBox!.height);
                    expect(rootBoundingBox!.height + headerBoundingBox!.height).toEqual(bodyBoundingBox!.height);
                });

                await test.step("close menu", async () => {
                    await expect(axe).toHaveNoViolations();

                    // Assert it closes by clicking the overlay
                    await page.getByTestId("dialog-overlay").click();
                    await expect(page.getByRole("dialog")).not.toBeVisible();
                });
            });
        });
    }

    test.describe("univention config", () => {
        test.use({
            config: {
                "io.element.element-web-modules.banner": {
                    logo_url: "http://localhost:8080/opendesk/logomark.svg",
                    logo_link_url: "https://example.com/portal",
                    menu: {
                        type: "univention",
                        logo_url: "http://localhost:8080/opendesk/logofull.svg",
                        ics_url: "http://localhost:8080/ics/",
                    },
                },
            },
        });

        test.beforeEach(async ({ context }) => {
            await context.route("http://localhost:8080/ics/silent", async (route) => {
                await route.fulfill({ status: 500 });
            });
            await context.route("http://localhost:8080/ics/navigation.json*", async (route) => {
                await route.fulfill({ status: 500 });
            });
        });

        test("should render error", { tag: ["@screenshot"] }, async ({ page, axe }) => {
            await expect(page.getByRole("heading", { name: "Be in your element" })).toBeVisible();
            await expect(page.getByLabel("Show portal")).toHaveAttribute("href", "https://example.com/portal");

            const trigger = page.getByLabel("Show menu");

            await trigger.click();
            const sidebar = page.getByRole("dialog");
            await expect(sidebar.getByText("Failed to load")).toBeVisible();
            await expect(sidebar).toMatchScreenshot("univention_error.png");
            await expect(axe).toHaveNoViolations();
        });
    });
});
