import {
  beforeEach,
  describe,
  expect,
  it
} from 'vitest';
import { WebSocket } from 'ws';
import { connectedUsers } from './state';
import { ConnectedUser } from './types';
import {
  broadcastToServer,
  findUserByOderId,
  findVoiceActiveConnection
} from './broadcast';

function createMockWs(): WebSocket & { sentMessages: string[] } {
  const sent: string[] = [];
  const ws = {
    readyState: WebSocket.OPEN,
    send: (data: string) => { sent.push(data); },
    close: () => {},
    sentMessages: sent
  } as unknown as WebSocket & { sentMessages: string[] };

  return ws;
}

function createConnectedUser(
  connectionId: string,
  oderId: string,
  overrides: Partial<ConnectedUser> = {}
): ConnectedUser {
  const user: ConnectedUser = {
    oderId,
    ws: createMockWs(),
    authenticated: true,
    serverIds: new Set(['server-1']),
    displayName: 'Test User',
    lastPong: Date.now(),
    ...overrides
  };

  connectedUsers.set(connectionId, user);

  return user;
}

describe('broadcastToServer', () => {
  beforeEach(() => {
    connectedUsers.clear();
  });

  it('delivers chat_message to every connection in the server except the sender connection', () => {
    createConnectedUser('conn-a1', 'user-1');
    const connA2 = createConnectedUser('conn-a2', 'user-1');
    const connB = createConnectedUser('conn-b', 'user-2');

    broadcastToServer('server-1', { type: 'chat_message', text: 'hello' }, {
      excludeConnectionId: 'conn-a1'
    });

    expect((connA2.ws as WebSocket & { sentMessages: string[] }).sentMessages).toHaveLength(1);
    expect((connB.ws as WebSocket & { sentMessages: string[] }).sentMessages).toHaveLength(1);
    expect(connectedUsers.get('conn-a1')?.ws).toBeDefined();
    expect((connectedUsers.get('conn-a1')!.ws as WebSocket & { sentMessages: string[] }).sentMessages).toHaveLength(0);
  });

  it('excludes every connection for an identity when excludeIdentityOderId is set', () => {
    const connA1 = createConnectedUser('conn-a1', 'user-1');
    const connA2 = createConnectedUser('conn-a2', 'user-1');
    const connB = createConnectedUser('conn-b', 'user-2');

    broadcastToServer('server-1', { type: 'user_left', oderId: 'user-1' }, {
      excludeIdentityOderId: 'user-1'
    });

    expect((connA1.ws as WebSocket & { sentMessages: string[] }).sentMessages).toHaveLength(0);
    expect((connA2.ws as WebSocket & { sentMessages: string[] }).sentMessages).toHaveLength(0);
    expect((connB.ws as WebSocket & { sentMessages: string[] }).sentMessages).toHaveLength(1);
  });
});

describe('findVoiceActiveConnection', () => {
  beforeEach(() => {
    connectedUsers.clear();
  });

  it('returns the connection marked voiceActive for the user', () => {
    createConnectedUser('conn-passive', 'user-1', { voiceActive: false });
    const active = createConnectedUser('conn-active', 'user-1', { voiceActive: true });

    expect(findVoiceActiveConnection('user-1')).toBe(active);
  });

  it('returns undefined when no voiceActive connection exists', () => {
    createConnectedUser('conn-1', 'user-1');

    expect(findVoiceActiveConnection('user-1')).toBeUndefined();
  });

  it('findUserByOderId falls back to any open connection when no voiceActive connection exists', () => {
    const fallback = createConnectedUser('conn-1', 'user-1');

    expect(findUserByOderId('user-1')).toBe(fallback);
  });
});
