import type { PeerData } from '../realtime.types';
import { PeerConnectionManager } from '../peer-connection-manager/peer-connection.manager';
import { IncomingSignalingMessageHandler } from './signaling-message-handler';
import { ServerSignalingCoordinator } from './server-signaling-coordinator';

describe('IncomingSignalingMessageHandler user_left handling', () => {
  it('preserves an active voice peer on a transient user_left signal', () => {
    const context = createHandlerContext({ voiceConnected: true });

    context.coordinator.addJoinedServer('ws://signal-a', 'server-1');
    context.coordinator.trackPeerInServer('peer-a', 'server-1', 'ws://signal-a');
    context.peerManager.activePeerConnections.set('peer-a', createPeerData('connected', 'open'));

    context.handler.handleMessage({ type: 'user_left', oderId: 'peer-a', serverId: 'server-1' }, 'ws://signal-a');

    expect(context.peerManager.removePeer).not.toHaveBeenCalled();
    expect(context.coordinator.getPeerSignalUrl('peer-a')).toBe('ws://signal-a');
    expect(context.coordinator.hasTrackedPeerServers('peer-a')).toBe(true);
  });

  it('removes a peer on user_left when local voice is not active', () => {
    const context = createHandlerContext({ voiceConnected: false });

    context.coordinator.trackPeerInServer('peer-a', 'server-1', 'ws://signal-a');
    context.peerManager.activePeerConnections.set('peer-a', createPeerData('connected', 'open'));

    context.handler.handleMessage({ type: 'user_left', oderId: 'peer-a', serverId: 'server-1' }, 'ws://signal-a');

    expect(context.peerManager.removePeer).toHaveBeenCalledWith('peer-a');
    expect(context.coordinator.getPeerSignalUrl('peer-a')).toBeUndefined();
  });

  it('removes a stale voice peer when no active P2P transport remains', () => {
    const context = createHandlerContext({ voiceConnected: true });

    context.coordinator.trackPeerInServer('peer-a', 'server-1', 'ws://signal-a');
    context.peerManager.activePeerConnections.set('peer-a', createPeerData('failed', 'closed'));

    context.handler.handleMessage({ type: 'user_left', oderId: 'peer-a', serverId: 'server-1' }, 'ws://signal-a');

    expect(context.peerManager.removePeer).toHaveBeenCalledWith('peer-a');
    expect(context.coordinator.getPeerSignalUrl('peer-a')).toBeUndefined();
  });
});

interface HandlerContext {
  coordinator: ServerSignalingCoordinator<unknown>;
  handler: IncomingSignalingMessageHandler;
  peerManager: PeerConnectionManager & {
    activePeerConnections: Map<string, PeerData>;
    removePeer: ReturnType<typeof vi.fn>;
  };
}

function createHandlerContext(options: { voiceConnected: boolean }): HandlerContext {
  const coordinator = new ServerSignalingCoordinator<unknown>({
    createManager: vi.fn(),
    handleConnectionStatus: vi.fn(),
    handleHeartbeatTick: vi.fn(),
    handleMessage: vi.fn()
  });
  const peerManager = {
    activePeerConnections: new Map<string, PeerData>(),
    removePeer: vi.fn()
  } as unknown as HandlerContext['peerManager'];
  const handler = new IncomingSignalingMessageHandler({
    getEffectiveServerId: () => 'server-1',
    getLocalOderId: () => 'local-user',
    isVoiceConnected: () => options.voiceConnected,
    logger: {
      error: vi.fn(),
      info: vi.fn(),
      logStream: vi.fn(),
      traffic: vi.fn(),
      warn: vi.fn()
    } as unknown as ConstructorParameters<typeof IncomingSignalingMessageHandler>[0]['logger'],
    peerManager,
    setServerTime: vi.fn(),
    signalingCoordinator: coordinator
  });

  return { coordinator, handler, peerManager };
}

function createPeerData(
  connectionState: RTCPeerConnectionState,
  dataChannelState: RTCDataChannelState
): PeerData {
  return {
    audioSender: undefined,
    connection: {
      connectionState
    } as RTCPeerConnection,
    createdAt: Date.now(),
    dataChannel: {
      readyState: dataChannelState
    } as RTCDataChannel,
    isInitiator: true,
    pendingIceCandidates: [],
    remoteCameraStreamIds: new Set<string>(),
    remoteScreenShareStreamIds: new Set<string>(),
    remoteVoiceStreamIds: new Set<string>(),
    videoSender: undefined
  };
}
