// SPEC: PJ2026-010405 云端控制台.
// Implementation reference: draft-2026-07-13-p0-cloud-console.

import { expect, test } from "../fixtures/test";

const viewports = [
  { name: "desktop", width: 1920, height: 1080 },
  { name: "compact", width: 960, height: 600 },
  { name: "mobile", width: 390, height: 844 },
] as const;

test("industrial AppShell keeps one bounded scroll owner across target viewports", async ({
  page,
}, testInfo) => {
  await mockProviderProfiles(page);
  for (const viewport of viewports) {
    await page.setViewportSize({
      width: viewport.width,
      height: viewport.height,
    });
    await page.goto("/admin/provider-profiles");
    await expect(
      page.getByRole("heading", { name: "Provider Profiles", exact: true }),
    ).toBeVisible();
    await expect(page.locator(".resource-collection")).toContainText(
      "codex-api",
    );

    const geometry = await page.evaluate(() => {
      const shell = document.querySelector<HTMLElement>(".platform-shell");
      const content = document.querySelector<HTMLElement>(".platform-content");
      const sidebar = document.querySelector<HTMLElement>(".platform-sidebar");
      return {
        documentScrollWidth: Math.round(document.documentElement.scrollWidth),
        bodyScrollWidth: Math.round(document.body.scrollWidth),
        shellHeight: Math.round(shell?.getBoundingClientRect().height ?? 0),
        contentOverflowY: content
          ? getComputedStyle(content).overflowY
          : "missing",
        sidebarDisplay: sidebar ? getComputedStyle(sidebar).display : "missing",
      };
    });
    expect(geometry.documentScrollWidth).toBeLessThanOrEqual(viewport.width);
    expect(geometry.bodyScrollWidth).toBeLessThanOrEqual(viewport.width);
    expect(geometry.shellHeight).toBe(viewport.height);
    expect(geometry.contentOverflowY).toBe("auto");
    expect(geometry.sidebarDisplay).toBe(
      viewport.name === "mobile" ? "none" : "flex",
    );

    await page.screenshot({
      path: testInfo.outputPath(`console-${viewport.name}.png`),
    });
  }
});

test("typed Dashboard renders status, problems and actions without horizontal overflow", async ({
  page,
}, testInfo) => {
  for (const viewport of viewports) {
    await page.setViewportSize({
      width: viewport.width,
      height: viewport.height,
    });
    await page.goto("/dashboard");
    await expect(page.getByText("HWPOD Node 离线")).toBeVisible();
    await expect(page.getByText("展示 MDTODO 任务详情")).toBeVisible();
    await expect(
      page.getByRole("link", { name: /查看 Node/u }),
    ).toHaveAttribute("href", "/hwpods/nodes?status=offline");
    const width = await page.evaluate(
      () => document.documentElement.scrollWidth,
    );
    expect(width).toBeLessThanOrEqual(viewport.width);
    await page.screenshot({
      path: testInfo.outputPath(`dashboard-${viewport.name}.png`),
      fullPage: true,
    });
  }
});

test("Dialog and mobile Drawer trap, restore and unlock focus without motion", async ({
  page,
}, testInfo) => {
  await mockProviderProfiles(page);
  await page.emulateMedia({ reducedMotion: "reduce" });
  await page.setViewportSize({ width: 1920, height: 1080 });
  await page.goto("/admin/provider-profiles");

  const keyButton = page
    .getByRole("button", { name: "Key", exact: true })
    .first();
  await keyButton.click();
  const dialog = page.getByRole("dialog", { name: "Provider Credential" });
  await expect(dialog).toBeVisible();
  await expect(page.locator("body")).toHaveClass(/console-scroll-locked/u);
  await expect(dialog.locator("#provider-api-key-input")).toBeVisible();
  await page.screenshot({
    path: testInfo.outputPath("console-overlay-desktop.png"),
  });
  await page.keyboard.press("Escape");
  await expect(dialog).toHaveCount(0);
  await expect(page.locator("body")).not.toHaveClass(/console-scroll-locked/u);
  await expect(keyButton).toBeFocused();

  await page.setViewportSize({ width: 390, height: 844 });
  const mobileTrigger = page.getByRole("button", { name: "打开主导航" });
  await mobileTrigger.click();
  const drawer = page.getByRole("dialog", { name: "HWLAB 导航" });
  await expect(drawer).toBeVisible();
  await expect(
    drawer.getByRole("navigation", { name: "主导航" }),
  ).toBeVisible();
  await expect(page.locator("body")).toHaveClass(/console-scroll-locked/u);
  await page.keyboard.press("Escape");
  await expect(drawer).toHaveCount(0);
  await expect(mobileTrigger).toBeFocused();
  await expect(page.locator("body")).not.toHaveClass(/console-scroll-locked/u);
});

test("URL is authoritative across direct links and browser back-forward navigation", async ({
  page,
}) => {
  await mockBillingConsole(page);

  await page.goto("/admin/users?q=alpha&filter=active&role=admin&cursor=2");
  await expect(page.getByPlaceholder("搜索用户名、邮箱或 user id")).toHaveValue(
    "alpha",
  );
  await expect(page.locator(".admin-users-filters select").nth(0)).toHaveValue(
    "active",
  );
  await expect(page.locator(".admin-users-filters select").nth(1)).toHaveValue(
    "admin",
  );
  await expect(page.getByText("第 2 / 3 页，共 60 条")).toBeVisible();
  await page.goto("/admin/users?q=beta&filter=disabled&role=user&cursor=1");
  await expect(page.getByPlaceholder("搜索用户名、邮箱或 user id")).toHaveValue(
    "beta",
  );
  await page.goBack();
  await expect(page.getByPlaceholder("搜索用户名、邮箱或 user id")).toHaveValue(
    "alpha",
  );
  await expect(page.getByText("第 2 / 3 页，共 60 条")).toBeVisible();
  await page.goForward();
  await expect(page.getByPlaceholder("搜索用户名、邮箱或 user id")).toHaveValue(
    "beta",
  );

  await page.goto(
    "/admin/billing?tab=redemptions&view=detail&userId=user-a&codeId=code-a",
  );
  await expect(page.getByPlaceholder("user id")).toHaveValue("user-a");
  await expect(page.getByPlaceholder("code id")).toHaveValue("code-a");
  await page.goto(
    "/admin/billing?tab=redemptions&view=summary&userId=user-b&codeId=code-b",
  );
  await expect(page.getByPlaceholder("user id")).toHaveValue("user-b");
  await page.goBack();
  await expect(page.getByPlaceholder("user id")).toHaveValue("user-a");
  await expect(page.getByPlaceholder("code id")).toHaveValue("code-a");

  await page.goto("/performance?filter=24h");
  await expect(page.getByRole("tab", { name: "24 小时" })).toHaveAttribute(
    "aria-selected",
    "true",
  );
  await page.goto("/performance?filter=5m");
  await expect(
    page.getByRole("tab", { name: "5 分钟", exact: true }),
  ).toHaveAttribute("aria-selected", "true");
  await page.goBack();
  await expect(page.getByRole("tab", { name: "24 小时" })).toHaveAttribute(
    "aria-selected",
    "true",
  );

  await page.goto("/projects/mdtodo?provider=deepseek");
  await expect(page.getByTestId("mdtodo-provider-profile")).toHaveValue(
    "deepseek",
  );
  await page.goto("/projects/mdtodo?provider=codex-api");
  await expect(page.getByTestId("mdtodo-provider-profile")).toHaveValue(
    "codex-api",
  );
  await page.goBack();
  await expect(page.getByTestId("mdtodo-provider-profile")).toHaveValue(
    "deepseek",
  );
});

test("19 console routes stay visible and bounded at 960 and 390", async ({
  page,
}) => {
  await mockProviderProfiles(page);
  await mockBillingConsole(page);
  const routes = [
    "/dashboard",
    "/projects",
    "/projects/mdtodo",
    "/hwpods/devices",
    "/hwpods/nodes",
    "/hwpods/onboarding",
    "/admin/users",
    "/api-keys",
    "/usage",
    "/billing",
    "/skills",
    "/gate",
    "/help",
    "/settings",
    "/admin/billing",
    "/admin/provider-profiles",
    "/performance",
    "/login",
    "/register",
  ];
  const pageErrors: Error[] = [];
  page.on("pageerror", (error) => pageErrors.push(error));
  for (const viewport of [
    { width: 960, height: 720 },
    { width: 390, height: 844 },
  ]) {
    await page.setViewportSize(viewport);
    for (const path of routes) {
      const errorCount = pageErrors.length;
      await page.goto(path);
      await expect(
        page.locator("h1").first(),
        `route ${path} should expose its primary heading`,
      ).toBeVisible();
      const geometry = await page.evaluate(() => ({
        scrollWidth: document.documentElement.scrollWidth,
        clientWidth: document.documentElement.clientWidth,
      }));
      expect(
        geometry.scrollWidth,
        `route ${path} should not overflow at ${viewport.width}px`,
      ).toBeLessThanOrEqual(geometry.clientWidth);
      expect(
        pageErrors.slice(errorCount),
        `route ${path} should not raise pageerror`,
      ).toEqual([]);
    }
  }
});

async function mockProviderProfiles(
  page: import("@playwright/test").Page,
): Promise<void> {
  await page.route("**/v1/admin/provider-profiles", async (route) => {
    await route.fulfill({
      status: 200,
      contentType: "application/json",
      body: JSON.stringify({
        items: [
          {
            profile: "codex-api",
            configured: true,
            secretRef: {
              namespace: "hwlab-v03",
              name: "gpt-pika",
              keys: ["auth.json"],
            },
            credentialHashSuffix: "a19f",
            validationStatus: "completed",
            valuesPrinted: false,
          },
          {
            profile: "deepseek",
            configured: true,
            secretRef: {
              namespace: "hwlab-v03",
              name: "deepseek",
              keys: ["api-key"],
            },
            credentialHashSuffix: "b470",
            validationStatus: "pending",
            valuesPrinted: false,
          },
        ],
      }),
    });
  });
}

async function mockBillingConsole(
  page: import("@playwright/test").Page,
): Promise<void> {
  await page.route("**/v1/admin/billing/plans", (route) =>
    route.fulfill({
      status: 200,
      contentType: "application/json",
      json: { plans: [] },
    }),
  );
  await page.route("**/v1/admin/billing/users?**", (route) =>
    route.fulfill({
      status: 200,
      contentType: "application/json",
      json: {
        users: [
          {
            user: {
              id: "usr-1",
              username: "alpha",
              email: "alpha@example.com",
              role: "admin",
              status: "active",
            },
            planId: "default",
            credits: { balance: 100, reserved: 10, available: 90 },
            usage: { totalCredits: 12, recordCount: 3 },
            apiKeys: { activeCount: 1, totalCount: 2 },
          },
        ],
        total: 60,
        state: { stateAuthority: "pk01-postgres" },
      },
    }),
  );
  await page.route("**/v1/admin/billing/usage?**", (route) =>
    route.fulfill({
      status: 200,
      contentType: "application/json",
      json: { rows: [], total: 0 },
    }),
  );
  await page.route("**/v1/admin/billing/ledger?**", (route) =>
    route.fulfill({
      status: 200,
      contentType: "application/json",
      json: { rows: [], total: 0 },
    }),
  );
  await page.route("**/v1/admin/billing/redeem-codes?**", (route) =>
    route.fulfill({
      status: 200,
      contentType: "application/json",
      json: { codes: [], count: 0 },
    }),
  );
  await page.route("**/v1/admin/billing/redeem-redemptions?**", (route) =>
    route.fulfill({
      status: 200,
      contentType: "application/json",
      json: { redemptions: [], count: 0 },
    }),
  );
}
