import { mkdirSync, writeFileSync } from "node:fs";
import { resolve } from "node:path";

import { expect, test } from "@playwright/test";
import { createServer, type ViteDevServer } from "vite";

import { createApp } from "../../apps/api/src/app.js";
import { EventHub } from "../../apps/api/src/event-hub.js";

const eventHub = new EventHub();
let app: Awaited<ReturnType<typeof createApp>>;
let vite: ViteDevServer;
let webUrl: string;

test.beforeAll(async () => {
  app = await createApp({
    browserGate: false,
    eventHub,
    networkBoundary: { allowTestPort: true },
  });
  app.get("/__test/entity", async (request) => ({ entity_ref: (request.query as { ref?: string }).ref ?? null }));
  app.get("/__test/models", async () => ({ source: "rest" }));
  const apiUrl = await app.listen({ host: "127.0.0.1", port: 0 });

  vite = await createServer({
    configFile: false,
    root: process.cwd(),
    server: {
      host: "127.0.0.1",
      port: 0,
      proxy: {
        "/__test": apiUrl,
        "/api": apiUrl,
      },
    },
  });
  await vite.listen();
  const address = vite.httpServer?.address();
  if (!address || typeof address === "string") throw new Error("Vite did not expose a test port.");
  webUrl = `http://127.0.0.1:${address.port}`;
});

test.afterAll(async () => {
  eventHub.disconnectAll();
  await vite.close();
  await app.close();
});

test("SSE remains a hint and recovers through REST", async ({ page }) => {
  const published = [
    {
      entity_ref: "projects:project-1",
      event_id: 40,
      event_type: "project_state_changed",
      occurred_at: "2026-07-27T09:00:00.000Z",
      state_version: 2,
    },
    {
      config_set_version: 9,
      entity_ref: "models:current",
      event_id: 42,
      event_type: "model_config_changed",
      occurred_at: "2026-07-27T09:00:01.000Z",
    },
  ] as const;

  await page.goto(`${webUrl}/tests/e2e/fixtures/event-sync.html`);
  await expect(page.locator("#status")).toHaveText("connected");
  expect(eventHub.subscriberCount).toBe(1);

  eventHub.publish(published[0]);
  await expect
    .poll(() => page.evaluate(() => window.wp0EventFixture.timeline.filter((entry) => entry.action === "refetch_entity").length))
    .toBe(1);

  eventHub.publish(published[1]);
  await expect
    .poll(() => page.evaluate(() => window.wp0EventFixture.timeline.filter((entry) => entry.action === "bootstrap").length))
    .toBe(1);
  await expect
    .poll(() => page.evaluate(() => window.wp0EventFixture.timeline.filter((entry) => entry.action === "refetch_models").length))
    .toBe(1);

  eventHub.disconnectAll();
  await expect
    .poll(() => page.evaluate(() => window.wp0EventFixture.timeline.filter((entry) => entry.action === "bootstrap").length))
    .toBe(2);

  const timeline = await page.evaluate(() => window.wp0EventFixture.timeline);
  const evidenceDirectory = process.env.DADA_EVIDENCE_DIR_EVT;
  if (evidenceDirectory) {
    mkdirSync(evidenceDirectory, { recursive: true });
    writeFileSync(
      resolve(evidenceDirectory, "sse-events.json"),
      `${JSON.stringify({ events: published, status: "passed" }, null, 2)}\n`,
    );
    writeFileSync(
      resolve(evidenceDirectory, "network-timeline.json"),
      `${JSON.stringify({ status: "passed", timeline }, null, 2)}\n`,
    );
  }
});
