// @vitest-environment jsdom

import { afterEach, beforeEach, describe, expect, it } from "vitest";

import type { WorkbenchRealtimeCapabilities } from "@/config/runtime";
import { WorkbenchStreamTransportRuntime, type WorkbenchStreamTransportState } from "./workbench-stream-transport";

const capabilities: WorkbenchRealtimeCapabilities = {
  liveKafkaSse: true,
  kafkaRefreshReplay: true,
  projectionRealtime: false
};

describe("WorkbenchStreamTransportRuntime connection authority", () => {
  const originalEventSource = globalThis.EventSource;

  beforeEach(() => {
    FakeEventSource.instances = [];
    globalThis.EventSource = FakeEventSource as unknown as typeof EventSource;
  });

  afterEach(() => {
    globalThis.EventSource = originalEventSource;
  });

  it("ignores late callbacks from a replaced SSE connection", () => {
    const runtime = new WorkbenchStreamTransportRuntime();
    const states: WorkbenchStreamTransportState[] = [];
    const recoveries: string[] = [];

    runtime.restart({
      realtimeCapabilities: capabilities,
      sessionId: "ses_old",
      onState: (state) => states.push(state),
      onRecovery: (recovery) => recoveries.push(recovery.key),
      onEvent: () => undefined
    });
    const oldSource = FakeEventSource.instances[0];
    expect(oldSource).toBeDefined();
    const lateOpen = oldSource?.onopen;
    const lateError = oldSource?.onerror;

    runtime.restart({
      realtimeCapabilities: capabilities,
      sessionId: "ses_current",
      onState: (state) => states.push(state),
      onRecovery: (recovery) => recoveries.push(recovery.key),
      onEvent: () => undefined
    });
    const currentSource = FakeEventSource.instances[1];
    expect(currentSource).toBeDefined();
    expect(states.at(-1)).toMatchObject({ sessionId: "ses_current", phase: "connecting" });

    lateOpen?.(new Event("open"));
    lateError?.(new Event("error"));

    expect(states.at(-1)).toMatchObject({ sessionId: "ses_current", phase: "connecting" });
    expect(recoveries).toEqual([]);

    currentSource?.emitOpen();
    expect(states.at(-1)).toMatchObject({ sessionId: "ses_current", phase: "open" });
    runtime.stop();
  });
});

class FakeEventSource {
  static instances: FakeEventSource[] = [];
  onopen: ((event: Event) => void) | null = null;
  onerror: ((event: Event) => void) | null = null;
  onmessage: ((event: MessageEvent) => void) | null = null;

  constructor(readonly url: string | URL) {
    FakeEventSource.instances.push(this);
  }

  addEventListener(): void {}
  removeEventListener(): void {}

  close(): void {}

  emitOpen(): void {
    this.onopen?.(new Event("open"));
  }

}
