import AxeBuilder from "@axe-core/playwright";
import { expect, test, type Page } from "@playwright/test";

const linkedServiceIds = [
  "vaultwarden",
  "forgejo",
  "wiki",
  "aws-start",
  "adguard-primary",
  "adguard-secondary",
  "grafana",
  "uptime-kuma",
  "prometheus",
  "backrest",
  "n8n",
  "open-webui",
  "comfyui",
  "models",
  "prompt-registry",
  "adminer",
  "assistant",
  "suna",
  "cockpit-infra",
  "cockpit-gpu",
  "cockpit-network-core",
  "forgejo-ssh-relay",
];

test.describe("dashboard page QA gate", () => {
  test("renders the model-driven dashboard on desktop", async ({
    page,
  }, testInfo) => {
    test.skip(testInfo.project.name !== "chromium-desktop");

    await page.goto("/");
    await waitForDashboardReady(page);

    await expect(page.getByRole("main")).toHaveAttribute(
      "aria-labelledby",
      /title/,
    );
    await expect(
      page.getByRole("heading", { level: 1, name: "System Overview" }),
    ).toBeVisible();
    await expect(page.getByText("Infra RAM")).toBeVisible();
    await expect(page.getByRole("link", { name: /Vaultwarden/i })).toBeVisible();
    await expect(page.locator("[data-model-id='vaultwarden']")).toBeVisible();
    await expect(page.locator(".service-row .icon-glyph svg").first()).toBeVisible();

    const bodyBox = await page.locator("body").boundingBox();
    expect(bodyBox?.width).toBeGreaterThan(1000);

    await expect(page).toHaveScreenshot("dashboard-desktop.png", {
      fullPage: true,
    });
  });

  test("fits the operational dashboard into a 1470 by 956 viewport", async ({
    page,
  }, testInfo) => {
    test.skip(testInfo.project.name !== "chromium-desktop");

    await page.setViewportSize({ width: 1470, height: 956 });
    await page.goto("/");
    await waitForDashboardReady(page);

    const metrics = await page.evaluate(() => {
      const footer = document.querySelector("[data-model-id='footer-status']");
      const runtime = document.querySelector("[data-model-id='runtime-health']");
      const visibleItems = [
        ...document.querySelectorAll(".telemetry-card"),
        ...document.querySelectorAll(".service-row"),
        ...document.querySelectorAll(".footer-cell"),
      ].map((element) => {
        const rect = element.getBoundingClientRect();
        return {
          bottom: rect.bottom,
          height: rect.height,
          id: element.getAttribute("data-model-id"),
          top: rect.top,
          width: rect.width,
        };
      });

      return {
        clippedItems: visibleItems.filter((item) =>
          item.top < 0 ||
          item.bottom > window.innerHeight ||
          item.width <= 0 ||
          item.height <= 0
        ),
        footerCellCount: document.querySelectorAll(".footer-cell").length,
        scrollWidth: document.documentElement.scrollWidth,
        scrollHeight: document.documentElement.scrollHeight,
        serviceRowCount: document.querySelectorAll(".service-row").length,
        telemetryCardCount: document.querySelectorAll(".telemetry-card").length,
        footerBottom: footer?.getBoundingClientRect().bottom ?? 0,
        runtimeBottom: runtime?.getBoundingClientRect().bottom ?? 0,
      };
    });

    expect(metrics.scrollWidth).toBeLessThanOrEqual(1470);
    expect(metrics.scrollHeight).toBeLessThanOrEqual(956);
    expect(metrics.runtimeBottom).toBeLessThanOrEqual(956);
    expect(metrics.footerBottom).toBeLessThanOrEqual(956);
    expect(metrics.telemetryCardCount).toBe(16);
    expect(metrics.serviceRowCount).toBe(28);
    expect(metrics.footerCellCount).toBe(5);
    expect(metrics.clippedItems).toEqual([]);
  });

  test("keeps intermediate viewports scrollable without horizontal clipping", async ({
    page,
  }, testInfo) => {
    test.skip(testInfo.project.name !== "chromium-desktop");

    for (const viewport of [
      { width: 900, height: 956 },
      { width: 1024, height: 768 },
    ]) {
      await page.setViewportSize(viewport);
      await page.goto("/");
      await waitForDashboardReady(page);

      const metrics = await page.evaluate(() => {
        const footer = document.querySelector("[data-model-id='footer-status']");
        const trackedElements = [
          document.querySelector(".dashboard-frame__header"),
          ...document.querySelectorAll(".telemetry-card"),
          ...document.querySelectorAll(".service-panel"),
          document.querySelector("[data-model-id='runtime-health']"),
          footer,
        ].filter((element): element is Element => Boolean(element));

        const footerRect = footer?.getBoundingClientRect();
        const clippedRight = trackedElements
          .map((element) => {
            const rect = element.getBoundingClientRect();
            return {
              id: element.getAttribute("data-model-id") || element.className,
              right: rect.right,
              width: rect.width,
            };
          })
          .filter((item) => item.right > window.innerWidth + 1 || item.width <= 0);

        return {
          clippedRight,
          footerBottomInDocument: (footerRect?.bottom ?? 0) + window.scrollY,
          frameOverflow: window.getComputedStyle(
            document.querySelector(".dashboard-frame") as Element,
          ).overflow,
          scrollHeight: document.documentElement.scrollHeight,
          scrollWidth: document.documentElement.scrollWidth,
        };
      });

      expect(metrics.scrollWidth).toBeLessThanOrEqual(viewport.width);
      expect(metrics.scrollHeight).toBeGreaterThanOrEqual(
        Math.ceil(metrics.footerBottomInDocument),
      );
      expect(metrics.frameOverflow).not.toBe("hidden");
      expect(metrics.clippedRight).toEqual([]);
    }
  });

  test("renders bookmark rows as a compact divider list", async ({ page }, testInfo) => {
    test.skip(testInfo.project.name !== "chromium-desktop");

    await page.goto("/");
    await waitForDashboardReady(page);

    const bookmarkDensity = await page.evaluate(() => {
      const panelBody = document.querySelector(".service-panel .panel__body");
      const rows = Array.from(document.querySelectorAll<HTMLElement>(".service-panel .service-row"));
      const first = rows[0];
      const second = rows[1];
      if (!panelBody || !first || !second) {
        throw new Error("expected at least two bookmark rows");
      }

      const bodyStyle = window.getComputedStyle(panelBody);
      const rowStyle = window.getComputedStyle(first);
      const firstRect = first.getBoundingClientRect();
      const secondRect = second.getBoundingClientRect();

      return {
        backgroundColor: rowStyle.backgroundColor,
        borderBottomWidth: Number.parseFloat(rowStyle.borderBottomWidth),
        borderLeftWidth: Number.parseFloat(rowStyle.borderLeftWidth),
        borderRightWidth: Number.parseFloat(rowStyle.borderRightWidth),
        columnGap: Number.parseFloat(rowStyle.columnGap),
        paddingBottom: Number.parseFloat(rowStyle.paddingBottom),
        paddingTop: Number.parseFloat(rowStyle.paddingTop),
        panelGap: Number.parseFloat(bodyStyle.rowGap),
        rowGap: secondRect.top - firstRect.bottom,
      };
    });

    expect(bookmarkDensity.panelGap).toBe(0);
    expect(bookmarkDensity.rowGap).toBeLessThanOrEqual(1);
    expect(bookmarkDensity.paddingTop).toBeLessThanOrEqual(3);
    expect(bookmarkDensity.paddingBottom).toBeLessThanOrEqual(3);
    expect(bookmarkDensity.columnGap).toBeLessThanOrEqual(4);
    expect(bookmarkDensity.borderBottomWidth).toBeGreaterThanOrEqual(1);
    expect(bookmarkDensity.borderLeftWidth).toBe(0);
    expect(bookmarkDensity.borderRightWidth).toBe(0);
    expect(bookmarkDensity.backgroundColor).toBe("rgba(0, 0, 0, 0)");
  });

  test("balances dashboard typography at the target viewport", async ({ page }, testInfo) => {
    test.skip(testInfo.project.name !== "chromium-desktop");

    await page.setViewportSize({ width: 1470, height: 956 });
    await page.goto("/");
    await waitForDashboardReady(page);

    const typeScale = await page.evaluate(() => {
      const fontSize = (selector: string) => {
        const element = document.querySelector(selector);
        if (!element) throw new Error(`missing ${selector}`);
        return Number.parseFloat(window.getComputedStyle(element).fontSize);
      };

      return {
        h1: fontSize("h1"),
        panelTitle: fontSize(".service-panel h2"),
        serviceDescription: fontSize(".service-row p"),
        serviceLabel: fontSize(".service-row h3"),
        telemetryLabel: fontSize(".telemetry-card h3"),
        telemetryValue: fontSize(".telemetry-card strong"),
      };
    });

    expect(typeScale.serviceLabel).toBeGreaterThanOrEqual(11.4);
    expect(typeScale.serviceDescription).toBeGreaterThanOrEqual(8.8);
    expect(typeScale.h1).toBeLessThanOrEqual(49);
    expect(typeScale.panelTitle).toBeLessThanOrEqual(28);
    expect(typeScale.telemetryLabel).toBeLessThanOrEqual(9.5);
    expect(typeScale.telemetryValue).toBeLessThanOrEqual(36);
  });

  test("keeps the first screen usable on mobile", async ({ page }, testInfo) => {
    test.skip(testInfo.project.name !== "chromium-mobile");

    await page.goto("/");
    await waitForDashboardReady(page);

    await expect(
      page.getByRole("heading", { level: 1, name: "System Overview" }),
    ).toBeVisible();
    await expect(page.getByLabel("Service groups")).toBeVisible();
    await expect(page.locator(".service-row .icon-glyph svg").first()).toBeVisible();

    await expect(page).toHaveScreenshot("dashboard-mobile.png", {
      fullPage: true,
    });
  });

  test("exposes usable landmarks and a visible keyboard focus state", async ({
    page,
  }) => {
    await page.goto("/");
    await waitForDashboardReady(page);

    await expect(page.getByRole("main")).toHaveCount(1);
    await page.keyboard.press("Tab");
    const themeToggle = page.getByRole("button", { name: "Light theme" });
    await expect(themeToggle).toBeFocused();
    await expect(themeToggle).toHaveAttribute("aria-pressed", "false");
    const themeFocusBoxShadow = await themeToggle.evaluate((element) => {
      return window.getComputedStyle(element).boxShadow;
    });
    expect(themeFocusBoxShadow).not.toBe("none");

    for (const serviceId of linkedServiceIds) {
      await page.keyboard.press("Tab");

      const focused = page.locator(":focus");
      await expect(focused).toHaveAttribute("data-model-id", serviceId);

      const focusBoxShadow = await focused.evaluate((element) => {
        return window.getComputedStyle(element).boxShadow;
      });
      expect(focusBoxShadow).not.toBe("none");
    }
  });

  test("passes automated accessibility checks", async ({ page }) => {
    await page.goto("/");
    await waitForDashboardReady(page);

    const results = await new AxeBuilder({ page }).analyze();
    expect(results.violations).toEqual([]);
  });

  test("passes automated accessibility checks in light mode", async ({
    page,
  }, testInfo) => {
    test.skip(testInfo.project.name !== "chromium-desktop");

    await page.goto("/");
    await waitForDashboardReady(page);
    await page.getByRole("button", { name: "Light theme" }).click();
    await expect(page.locator("html")).toHaveAttribute("data-ui-theme", "light");
    await expect(page.getByRole("button", { name: "Light theme" })).toHaveAttribute(
      "aria-pressed",
      "true",
    );

    const results = await new AxeBuilder({ page }).analyze();
    expect(results.violations).toEqual([]);

    await expect(page).toHaveScreenshot("dashboard-light-desktop.png", {
      fullPage: true,
    });
  });

  test("honors reduced-motion preferences", async ({ page }) => {
    await page.emulateMedia({ reducedMotion: "reduce" });

    await page.goto("/");
    await waitForDashboardReady(page);
    const durations = await page.evaluate(() => {
      const element = document.createElement("div");
      element.style.animation = "qa-motion-check 10s infinite";
      element.style.transition = "opacity 10s linear";
      document.body.append(element);

      const styles = window.getComputedStyle(element);
      return {
        animation: styles.animationDuration,
        transition: styles.transitionDuration,
      };
    });

    expect(cssDurationToMilliseconds(durations.animation)).toBeLessThanOrEqual(
      0.01,
    );
    expect(cssDurationToMilliseconds(durations.transition)).toBeLessThanOrEqual(
      0.01,
    );
  });

  test("toggles the dashboard between dark and light themes", async ({ page }) => {
    await page.goto("/");
    await waitForDashboardReady(page);

    await expect(page.locator("html")).toHaveAttribute("data-ui-theme", "dark");

    const switchToLight = page.getByRole("button", {
      name: "Light theme",
    });
    await expect(switchToLight).toBeVisible();
    await expect(switchToLight).toHaveAttribute("aria-pressed", "false");
    await switchToLight.click();

    await expect(page.locator("html")).toHaveAttribute("data-ui-theme", "light");
    await expect(page.getByRole("button", { name: "Light theme" })).toHaveAttribute(
      "aria-pressed",
      "true",
    );

    await page.reload();
    await waitForDashboardReady(page);

    await expect(page.locator("html")).toHaveAttribute("data-ui-theme", "light");
  });
});

async function waitForDashboardReady(page: Page): Promise<void> {
  await expect(
    page.getByRole("heading", { level: 1, name: "System Overview" }),
  ).toBeVisible();
  await expect(page.locator("[data-model-id='vaultwarden']")).toBeVisible();
}

function cssDurationToMilliseconds(duration: string): number {
  if (duration.endsWith("ms")) return Number.parseFloat(duration);
  if (duration.endsWith("s")) return Number.parseFloat(duration) * 1000;
  return Number.NaN;
}
