// e2e proof-of-life: connect → subscribe → emit ping → receive pong via the
// production-shaped binder + Socket.IO server, with cookie-session auth
// against a seeded test session.
import "reflect-metadata";
import { describe, it, expect, beforeEach, afterEach } from "vitest";
import { createServer, type Server as HttpServer } from "node:http";
import { Server as IOServer } from "socket.io";
import { io as ioClient } from "socket.io-client";
import type { AddressInfo } from "node:net";
import {
  RealtimeHandlerRegistry,
  SocketIORealtimeBroadcaster,
  SocketIORealtimeServer,
  realtimePingInboundDescriptor,
  realtimePongChannel,
  type IRealtimeAuthenticator,
} from "@repo/core-realtime";

describe("e2e: realtime-ping exercises all four checkpoints", () => {
  let httpServer: HttpServer;
  let realtimeServer: SocketIORealtimeServer;
  let port: number;

  beforeEach(async () => {
    httpServer = createServer();
    const io = new IOServer(httpServer);
    const broadcaster = new SocketIORealtimeBroadcaster(io);
    const registry = new RealtimeHandlerRegistry();
    registry.register(realtimePingInboundDescriptor(broadcaster));
    registry.registerChannel(realtimePongChannel);

    const authenticator: IRealtimeAuthenticator = {
      authenticate: async ({ cookies }) =>
        cookies.session === "valid-session"
          ? { userId: "user_test", roles: [] }
          : null,
    };

    realtimeServer = new SocketIORealtimeServer({
      httpServer,
      io,
      authenticator,
      registry,
    });
    await realtimeServer.start();

    await new Promise<void>((r) => httpServer.listen(0, r));
    port = (httpServer.address() as AddressInfo).port;
  });

  afterEach(async () => {
    await realtimeServer.stop();
    await new Promise<void>((r) => httpServer.close(() => r()));
  });

  it("authenticated client gets pong after ping", async () => {
    const client = ioClient(`http://localhost:${port}`, {
      extraHeaders: { Cookie: "session=valid-session" },
    });
    await new Promise<void>((r) => client.on("connect", () => r()));

    const subAck = await new Promise<{ ok: boolean }>((r) =>
      client.emit("subscribe", "realtime.pong", r),
    );
    expect(subAck.ok).toBe(true);

    const pongs: { at: string; echo: string }[] = [];
    client.on("realtime.pong", (p) => pongs.push(p));

    const sentAt = "2026-05-08T12:00:00.000Z";
    const pingAck = await new Promise<{ ok: boolean }>((r) =>
      client.emit("realtime.ping", { at: sentAt }, r),
    );
    expect(pingAck.ok).toBe(true);

    // Pong arrives synchronously after handler runs.
    await new Promise<void>((r) => setImmediate(r));

    expect(pongs).toHaveLength(1);
    expect(pongs[0]).toEqual({ at: sentAt, echo: "user_test" });

    client.disconnect();
  });

  it("anonymous client cannot subscribe to pong (authenticated scope)", async () => {
    const client = ioClient(`http://localhost:${port}`);
    await new Promise<void>((r) => client.on("connect", () => r()));

    const subAck = await new Promise<{ ok: boolean; error?: string }>((r) =>
      client.emit("subscribe", "realtime.pong", r),
    );

    expect(subAck.ok).toBe(false);
    expect(subAck.error).toBe("forbidden");
    client.disconnect();
  });
});
