/**
 * E2E tests for the Tool Health page (/health).
 *
 * Covers:
 *   - Server list rendering with status indicators
 *   - Expanded row showing health history
 *   - Filter pills (All / Up / Degraded / Down)
 *   - Search filtering
 *   - Run Check button interaction
 *   - Empty state when no servers configured
 *   - Alert banner when servers are down
 *
 * All API calls intercepted — no real backend required.
 */
import { test, expect, type Page, type Route } from "@playwright/test";
import { mockApiRoutes, MOCK_HEALTH_SERVERS } from "./fixtures";

/* ── Authenticated helper ──────────────────────────────────────── */

async function gotoHealth(page: Page): Promise<void> {
  await mockApiRoutes(page);
  await page.goto("/health");
  // If redirected to /login, sign in
  if (page.url().includes("/login")) {
    await page.fill('[id="email"]', "admin@langsight.dev");
    await page.fill('[id="password"]', "demo123");
    await page.getByRole("button", { name: /sign in/i }).click();
    await page.waitForURL("**/*", { timeout: 10_000 });
    await page.goto("/health");
  }
}

/* ── Server list rendering ─────────────────────────────────────── */

test.describe("Health page — server list", () => {
  test.beforeEach(async ({ page }) => {
    await gotoHealth(page);
  });

  test("renders all three server names from mock data", async ({ page }) => {
    await expect(page.getByText("postgres-mcp").first()).toBeVisible({
      timeout: 8_000,
    });
    await expect(page.getByText("s3-mcp").first()).toBeVisible();
    await expect(page.getByText("redis-mcp").first()).toBeVisible();
  });

  test("shows correct server count in subtitle", async ({ page }) => {
    await expect(page.getByText("3 servers").first()).toBeVisible({
      timeout: 8_000,
    });
  });

  test("shows refresh interval hint in subtitle", async ({ page }) => {
    await expect(
      page.getByText(/refreshes every 30s/i).first()
    ).toBeVisible({ timeout: 8_000 });
  });

  test("renders server latency for up servers", async ({ page }) => {
    // postgres-mcp has latency_ms: 42
    await expect(page.getByText("42ms").first()).toBeVisible({
      timeout: 8_000,
    });
  });

  test("renders server latency for degraded servers", async ({ page }) => {
    // s3-mcp has latency_ms: 350
    await expect(page.getByText("350ms").first()).toBeVisible({
      timeout: 8_000,
    });
  });

  test("shows error message for down server", async ({ page }) => {
    await expect(
      page.getByText(/connection refused/i).first()
    ).toBeVisible({ timeout: 8_000 });
  });

  test("down servers appear before up servers (sorted by severity)", async ({
    page,
  }) => {
    // Wait for servers to render
    await page.getByText("redis-mcp").first().waitFor({ timeout: 8_000 });

    // Get all server name elements and check order
    // redis-mcp (down) should appear before s3-mcp (degraded) before postgres-mcp (up)
    const serverNames = await page
      .locator('[style*="font-geist-mono"]')
      .filter({ hasText: /-mcp$/ })
      .allTextContents();

    if (serverNames.length >= 3) {
      const redisIdx = serverNames.findIndex((n) => n.includes("redis"));
      const postgresIdx = serverNames.findIndex((n) => n.includes("postgres"));
      expect(redisIdx).toBeLessThan(postgresIdx);
    }
  });
});

/* ── Alert banner ──────────────────────────────────────────────── */

test.describe("Health page — alert banner", () => {
  test("shows alert banner when servers are down", async ({ page }) => {
    await gotoHealth(page);
    await expect(
      page.getByText(/\d+.*server.*down|down.*server/i).first()
    ).toBeVisible({ timeout: 8_000 });
  });

  test("alert banner mentions immediate attention required", async ({
    page,
  }) => {
    await gotoHealth(page);
    await expect(
      page.getByText(/immediate attention required/i).first()
    ).toBeVisible({ timeout: 8_000 });
  });

  test("no alert banner when all servers are up", async ({ page }) => {
    await mockApiRoutes(page);

    // Override health endpoint to return all healthy servers
    await page.route("**/api/proxy/health/servers", async (route: Route) => {
      await route.fulfill({
        status: 200,
        contentType: "application/json",
        body: JSON.stringify(
          MOCK_HEALTH_SERVERS.map((s) => ({
            ...s,
            status: "up",
            latency_ms: 42,
            error: null,
          }))
        ),
      });
    });
    await page.route("**/api/health/servers", async (route: Route) => {
      await route.fulfill({
        status: 200,
        contentType: "application/json",
        body: JSON.stringify(
          MOCK_HEALTH_SERVERS.map((s) => ({
            ...s,
            status: "up",
            latency_ms: 42,
            error: null,
          }))
        ),
      });
    });

    await page.goto("/health");
    if (page.url().includes("/login")) {
      await page.fill('[id="email"]', "admin@langsight.dev");
      await page.fill('[id="password"]', "demo123");
      await page.getByRole("button", { name: /sign in/i }).click();
      await page.waitForURL("**/*", { timeout: 10_000 });
      await page.goto("/health");
    }

    // Wait for data to load
    await page.getByText("postgres-mcp").first().waitFor({ timeout: 8_000 });

    // Alert banner should NOT be visible
    await expect(
      page.getByText(/immediate attention required/i)
    ).not.toBeVisible({ timeout: 2_000 });
  });
});

/* ── Filter pills ──────────────────────────────────────────────── */

test.describe("Health page — filter pills", () => {
  test.beforeEach(async ({ page }) => {
    await gotoHealth(page);
  });

  test("All filter shows all servers", async ({ page }) => {
    await page.getByText("postgres-mcp").first().waitFor({ timeout: 8_000 });
    const allBtn = page.getByRole("button", { name: /^all/i }).first();
    await allBtn.click();
    await expect(page.getByText("postgres-mcp").first()).toBeVisible();
    await expect(page.getByText("s3-mcp").first()).toBeVisible();
    await expect(page.getByText("redis-mcp").first()).toBeVisible();
  });

  test("Up filter shows only up servers", async ({ page }) => {
    await page.getByText("postgres-mcp").first().waitFor({ timeout: 8_000 });
    const upBtn = page.getByRole("button", { name: /^up/i }).first();
    await upBtn.click();
    await expect(page.getByText("postgres-mcp").first()).toBeVisible();
    // s3-mcp is degraded, redis-mcp is down — should not be visible
    await expect(page.getByText("s3-mcp")).not.toBeVisible({ timeout: 2_000 });
    await expect(page.getByText("redis-mcp")).not.toBeVisible({
      timeout: 2_000,
    });
  });

  test("Down filter shows only down servers", async ({ page }) => {
    await page.getByText("postgres-mcp").first().waitFor({ timeout: 8_000 });
    const downBtn = page.getByRole("button", { name: /^down/i }).first();
    await downBtn.click();
    await expect(page.getByText("redis-mcp").first()).toBeVisible();
    await expect(page.getByText("postgres-mcp")).not.toBeVisible({
      timeout: 2_000,
    });
  });

  test("Degraded filter shows only degraded servers", async ({ page }) => {
    await page.getByText("postgres-mcp").first().waitFor({ timeout: 8_000 });
    const degradedBtn = page
      .getByRole("button", { name: /^degraded/i })
      .first();
    await degradedBtn.click();
    await expect(page.getByText("s3-mcp").first()).toBeVisible();
    await expect(page.getByText("postgres-mcp")).not.toBeVisible({
      timeout: 2_000,
    });
  });
});

/* ── Search filtering ──────────────────────────────────────────── */

test.describe("Health page — search", () => {
  test.beforeEach(async ({ page }) => {
    await gotoHealth(page);
  });

  test("typing in search filters servers by name", async ({ page }) => {
    await page.getByText("postgres-mcp").first().waitFor({ timeout: 8_000 });
    const searchInput = page.getByPlaceholder(/filter servers/i);
    await searchInput.fill("postgres");
    await expect(page.getByText("postgres-mcp").first()).toBeVisible();
    await expect(page.getByText("s3-mcp")).not.toBeVisible({
      timeout: 2_000,
    });
    await expect(page.getByText("redis-mcp")).not.toBeVisible({
      timeout: 2_000,
    });
  });

  test("search is case-insensitive", async ({ page }) => {
    await page.getByText("postgres-mcp").first().waitFor({ timeout: 8_000 });
    const searchInput = page.getByPlaceholder(/filter servers/i);
    await searchInput.fill("POSTGRES");
    await expect(page.getByText("postgres-mcp").first()).toBeVisible();
  });

  test("no match shows empty state", async ({ page }) => {
    await page.getByText("postgres-mcp").first().waitFor({ timeout: 8_000 });
    const searchInput = page.getByPlaceholder(/filter servers/i);
    await searchInput.fill("nonexistent-server-xyz");
    await expect(
      page.getByText(/no servers match/i).first()
    ).toBeVisible({ timeout: 3_000 });
  });

  test("clearing search restores all servers", async ({ page }) => {
    await page.getByText("postgres-mcp").first().waitFor({ timeout: 8_000 });
    const searchInput = page.getByPlaceholder(/filter servers/i);
    await searchInput.fill("postgres");
    await expect(page.getByText("s3-mcp")).not.toBeVisible({
      timeout: 2_000,
    });
    await searchInput.clear();
    await expect(page.getByText("s3-mcp").first()).toBeVisible();
    await expect(page.getByText("redis-mcp").first()).toBeVisible();
  });
});

/* ── Expanded row ──────────────────────────────────────────────── */

test.describe("Health page — expanded row", () => {
  test.beforeEach(async ({ page }) => {
    await gotoHealth(page);
  });

  test("clicking a server row expands it with history", async ({ page }) => {
    await page.getByText("postgres-mcp").first().waitFor({ timeout: 8_000 });
    await page.getByText("postgres-mcp").first().click();
    // Expanded row shows uptime, avg latency, and checks count
    await expect(
      page.getByText(/uptime/i).first()
    ).toBeVisible({ timeout: 8_000 });
  });

  test("expanded row shows Avg latency stat", async ({ page }) => {
    await page.getByText("postgres-mcp").first().waitFor({ timeout: 8_000 });
    await page.getByText("postgres-mcp").first().click();
    await expect(
      page.getByText(/avg latency/i).first()
    ).toBeVisible({ timeout: 8_000 });
  });

  test("expanded row shows Checks count", async ({ page }) => {
    await page.getByText("postgres-mcp").first().waitFor({ timeout: 8_000 });
    await page.getByText("postgres-mcp").first().click();
    await expect(
      page.getByText(/checks/i).first()
    ).toBeVisible({ timeout: 8_000 });
  });

  test("expanded row shows history table with Time column", async ({
    page,
  }) => {
    await page.getByText("postgres-mcp").first().waitFor({ timeout: 8_000 });
    await page.getByText("postgres-mcp").first().click();
    await expect(
      page.getByText("Time").first()
    ).toBeVisible({ timeout: 8_000 });
  });

  test("clicking the same server row again collapses it", async ({ page }) => {
    await page.getByText("postgres-mcp").first().waitFor({ timeout: 8_000 });
    // Expand
    await page.getByText("postgres-mcp").first().click();
    await expect(
      page.getByText(/uptime/i).first()
    ).toBeVisible({ timeout: 8_000 });
    // Collapse
    await page.getByText("postgres-mcp").first().click();
    // The uptime text from the expanded section should disappear
    // (note: "Uptime" may still appear as a column label, but the expanded stats should be gone)
    await page.waitForTimeout(500);
  });

  test("only one server row is expanded at a time", async ({ page }) => {
    await page.getByText("postgres-mcp").first().waitFor({ timeout: 8_000 });
    // Expand postgres-mcp
    await page.getByText("postgres-mcp").first().click();
    await expect(
      page.getByText(/uptime/i).first()
    ).toBeVisible({ timeout: 8_000 });
    // Now click on s3-mcp — postgres-mcp should collapse
    await page.getByText("s3-mcp").first().click();
    // Wait a bit for the state to settle
    await page.waitForTimeout(300);
  });
});

/* ── Run Check button ──────────────────────────────────────────── */

test.describe("Health page — Run Check", () => {
  test("Run Check button triggers health check", async ({ page }) => {
    await gotoHealth(page);
    let healthCheckCalled = false;

    // Intercept the health check POST
    await page.route("**/api/proxy/health/check", async (route: Route) => {
      healthCheckCalled = true;
      await route.fulfill({
        status: 200,
        contentType: "application/json",
        body: JSON.stringify(MOCK_HEALTH_SERVERS),
      });
    });

    const runBtn = page.getByRole("button", { name: /run check/i });
    await runBtn.click();
    // Wait for the button to return to normal state
    await expect(runBtn).toBeEnabled({ timeout: 5_000 });
    expect(healthCheckCalled).toBe(true);
  });

  test("Run Check button shows Checking... state during request", async ({
    page,
  }) => {
    await gotoHealth(page);

    // Add delay to health check so we can see the loading state
    await page.route("**/api/proxy/health/check", async (route: Route) => {
      await new Promise((r) => setTimeout(r, 500));
      await route.fulfill({
        status: 200,
        contentType: "application/json",
        body: JSON.stringify(MOCK_HEALTH_SERVERS),
      });
    });

    const runBtn = page.getByRole("button", { name: /run check/i });
    await runBtn.click();

    // Should briefly show checking state
    await expect(page.getByText(/checking/i)).toBeVisible({
      timeout: 2_000,
    }).catch(() => {
      // May be too fast to catch
    });

    // Should return to normal
    await expect(runBtn).toBeEnabled({ timeout: 5_000 });
  });
});

/* ── Empty state ───────────────────────────────────────────────── */

test.describe("Health page — empty state", () => {
  test("shows empty state when no servers are configured", async ({
    page,
  }) => {
    await mockApiRoutes(page);

    // Override to return empty server list
    await page.route("**/api/proxy/health/servers", async (route: Route) => {
      await route.fulfill({
        status: 200,
        contentType: "application/json",
        body: JSON.stringify([]),
      });
    });
    await page.route("**/api/health/servers", async (route: Route) => {
      await route.fulfill({
        status: 200,
        contentType: "application/json",
        body: JSON.stringify([]),
      });
    });

    await page.goto("/health");
    if (page.url().includes("/login")) {
      await page.fill('[id="email"]', "admin@langsight.dev");
      await page.fill('[id="password"]', "demo123");
      await page.getByRole("button", { name: /sign in/i }).click();
      await page.waitForURL("**/*", { timeout: 10_000 });
      await page.goto("/health");
    }

    await expect(
      page.getByText(/no servers configured/i).first()
    ).toBeVisible({ timeout: 8_000 });
  });

  test("empty state suggests running langsight init", async ({ page }) => {
    await mockApiRoutes(page);
    await page.route("**/api/proxy/health/servers", async (route: Route) => {
      await route.fulfill({
        status: 200,
        contentType: "application/json",
        body: JSON.stringify([]),
      });
    });
    await page.route("**/api/health/servers", async (route: Route) => {
      await route.fulfill({
        status: 200,
        contentType: "application/json",
        body: JSON.stringify([]),
      });
    });

    await page.goto("/health");
    if (page.url().includes("/login")) {
      await page.fill('[id="email"]', "admin@langsight.dev");
      await page.fill('[id="password"]', "demo123");
      await page.getByRole("button", { name: /sign in/i }).click();
      await page.waitForURL("**/*", { timeout: 10_000 });
      await page.goto("/health");
    }

    await expect(
      page.getByText(/langsight init/i).first()
    ).toBeVisible({ timeout: 8_000 });
  });
});
