import { describe, it, expect, vi } from "vitest";
import { z } from "zod";
import { RealtimeHandlerRegistry } from "@/realtime-handler-registry";
import { defineRealtimeChannel } from "@/realtime-channel";

const ch = defineRealtimeChannel(
  "test.ch",
  z.object({ x: z.number() }).strict(),
  { scope: "authenticated" },
);

describe("RealtimeHandlerRegistry", () => {
  it("registers and retrieves a handler by channel name", () => {
    const reg = new RealtimeHandlerRegistry();
    const handler = vi.fn();
    reg.register({ descriptor: ch, handler });
    const got = reg.getInboundDescriptor("test.ch");
    expect(got).not.toBeNull();
    expect(got!.descriptor.name).toBe("test.ch");
    expect(got!.handler).toBe(handler);
  });

  it("returns null for unknown channel name", () => {
    const reg = new RealtimeHandlerRegistry();
    expect(reg.getInboundDescriptor("unknown")).toBeNull();
  });

  it("list() returns all registered descriptors", () => {
    const reg = new RealtimeHandlerRegistry();
    reg.register({ descriptor: ch, handler: vi.fn() });
    expect(reg.list()).toHaveLength(1);
    expect(reg.list()[0]!.descriptor.name).toBe("test.ch");
  });

  it("re-registering the same channel replaces the previous entry", () => {
    const reg = new RealtimeHandlerRegistry();
    const h1 = vi.fn();
    const h2 = vi.fn();
    reg.register({ descriptor: ch, handler: h1 });
    reg.register({ descriptor: ch, handler: h2 });
    expect(reg.getInboundDescriptor("test.ch")!.handler).toBe(h2);
    expect(reg.list()).toHaveLength(1);
  });

  it("registerChannel stores a descriptor that appears in listChannels() but not in list()", () => {
    const reg = new RealtimeHandlerRegistry();
    const outboundCh = defineRealtimeChannel(
      "test.outbound",
      z.object({ y: z.string() }).strict(),
      { scope: "authenticated" },
    );
    reg.registerChannel(outboundCh);
    expect(reg.listChannels()).toHaveLength(1);
    expect(reg.listChannels()[0]!.name).toBe("test.outbound");
    expect(reg.list()).toHaveLength(0);
  });

  it("register auto-populates listChannels()", () => {
    const reg = new RealtimeHandlerRegistry();
    reg.register({ descriptor: ch, handler: vi.fn() });
    expect(reg.listChannels()).toHaveLength(1);
    expect(reg.listChannels()[0]!.name).toBe("test.ch");
  });

  it("listChannels() returns both inbound and outbound-only channels when both are registered", () => {
    const reg = new RealtimeHandlerRegistry();
    const outboundCh = defineRealtimeChannel(
      "test.outbound",
      z.object({ y: z.string() }).strict(),
      { scope: "authenticated" },
    );
    reg.register({ descriptor: ch, handler: vi.fn() });
    reg.registerChannel(outboundCh);
    expect(reg.listChannels()).toHaveLength(2);
    const names = reg.listChannels().map((c) => c.name).sort();
    expect(names).toEqual(["test.ch", "test.outbound"]);
  });

  it("re-registering an outbound-only channel via registerChannel replaces the previous entry", () => {
    const reg = new RealtimeHandlerRegistry();
    const outboundCh = defineRealtimeChannel(
      "test.outbound",
      z.object({ y: z.string() }).strict(),
      { scope: "authenticated" },
    );
    reg.registerChannel(outboundCh);
    reg.registerChannel(outboundCh);
    expect(reg.listChannels()).toHaveLength(1);
    expect(reg.listChannels()[0]!.name).toBe("test.outbound");
  });
});
