import {
  describe,
  it,
  expect,
  beforeEach,
  vi
} from 'vitest';
import { WebSocket } from 'ws';
import { connectedUsers } from './state';
import { ConnectedUser } from './types';
import { handleWebSocketMessage } from './handler';

vi.mock('../services/server-access.service', () => ({
  authorizeWebSocketJoin: vi.fn(async () => ({ allowed: true as const })),
  findServerMembership: vi.fn(async () => ({ id: 'membership-1' })),
  usersShareServerMembership: vi.fn(async () => false)
}));

vi.mock('../services/session-auth.service', () => ({
  consumeSessionToken: vi.fn(async (token: string) => {
    if (token !== 'valid-token') {
      return null;
    }

    return {
      token,
      user: {
        id: 'user-1',
        username: 'alice',
        displayName: 'Alice',
        passwordHash: 'hash',
        createdAt: Date.now()
      },
      issuedAt: Date.now(),
      expiresAt: Date.now() + 60_000
    };
  })
}));

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): ConnectedUser {
  const ws = createMockWs();
  const user: ConnectedUser = {
    oderId: connectionId,
    ws,
    authenticated: false,
    serverIds: new Set(),
    displayName: 'Test User',
    lastPong: Date.now()
  };

  connectedUsers.set(connectionId, user);

  return user;
}

describe('server websocket handler - authentication', () => {
  beforeEach(() => {
    connectedUsers.clear();
    vi.clearAllMocks();
  });

  it('rejects non-identify messages until the connection is authenticated', async () => {
    createConnectedUser('conn-1');

    await handleWebSocketMessage('conn-1', { type: 'typing', serverId: 'server-1' });

    const user = connectedUsers.get('conn-1');
    const sentMessages = (user?.ws as WebSocket & { sentMessages: string[] }).sentMessages;
    const response = JSON.parse(sentMessages[0]) as { type: string };

    expect(response.type).toBe('auth_required');
    expect(user?.authenticated).toBe(false);
  });

  it('rejects identify without a session token', async () => {
    createConnectedUser('conn-1');

    await handleWebSocketMessage('conn-1', {
      type: 'identify',
      oderId: 'user-1',
      displayName: 'Alice'
    });

    const user = connectedUsers.get('conn-1');
    const sentMessages = (user?.ws as WebSocket & { sentMessages: string[] }).sentMessages;
    const response = JSON.parse(sentMessages[0]) as { type: string; code: string };

    expect(response.type).toBe('auth_error');
    expect(response.code).toBe('MISSING_TOKEN');
    expect(user?.authenticated).toBe(false);
  });

  it('binds identify to the authenticated user id from the token', async () => {
    createConnectedUser('conn-1');

    await handleWebSocketMessage('conn-1', {
      type: 'identify',
      token: 'valid-token',
      oderId: 'user-1',
      displayName: 'Alice'
    });

    const user = connectedUsers.get('conn-1');

    expect(user?.authenticated).toBe(true);
    expect(user?.oderId).toBe('user-1');
  });
});
