import { Types } from 'mongoose';
import { ChatGateway } from './chat.gateway';

const createClient = (userId?: string) =>
  ({
    id: new Types.ObjectId().toString(),
    data: { userId },
    join: jest.fn().mockResolvedValue(undefined),
    leave: jest.fn().mockResolvedValue(undefined),
    emit: jest.fn(),
    to: jest.fn().mockReturnValue({ emit: jest.fn() }),
  }) as any;

describe('ChatGateway realtime events', () => {
  const userId = new Types.ObjectId().toString();
  const conversationId = new Types.ObjectId().toString();
  let chatService: Record<string, jest.Mock>;
  let gateway: ChatGateway;

  beforeEach(() => {
    chatService = {
      assertConversationMember: jest.fn().mockResolvedValue({ id: conversationId }),
      sendMessage: jest.fn(),
      markMessageSeen: jest.fn(),
      markMessageDelivered: jest.fn(),
    };
    gateway = new ChatGateway(
      chatService as any,
      { bindServer: jest.fn() } as any,
      {} as any,
      {} as any,
      {} as any,
    );
    (gateway as any).server = {
      to: jest.fn().mockReturnValue({ emit: jest.fn() }),
    };
  });

  it('join_conversation validates membership before joining the room', async () => {
    const client = createClient(userId);

    await gateway.joinConversation(client, { conversationId });

    expect(chatService.assertConversationMember).toHaveBeenCalledWith(userId, conversationId);
    expect(client.join).toHaveBeenCalledWith(`conversation:${conversationId}`);
    expect(client.emit).toHaveBeenCalledWith('joined_conversation', { conversationId });
  });

  it('leave_conversation leaves only the requested conversation room', async () => {
    const client = createClient(userId);

    await gateway.leaveConversation(client, { conversationId });

    expect(chatService.assertConversationMember).toHaveBeenCalledWith(userId, conversationId);
    expect(client.leave).toHaveBeenCalledWith(`conversation:${conversationId}`);
    expect(client.emit).toHaveBeenCalledWith('left_conversation', { conversationId });
  });

  it('emits socket_error for invalid realtime operations', async () => {
    const client = createClient(userId);
    chatService.assertConversationMember.mockRejectedValue(new Error('Not allowed'));

    await gateway.joinConversation(client, { conversationId });

    expect(client.emit).toHaveBeenCalledWith('socket_error', {
      event: 'join_conversation',
      message: 'Not allowed',
      code: 'ERROR',
    });
  });

  it('mark_delivered emits message_delivered payload returned by the service', async () => {
    const client = createClient(userId);
    const messageId = new Types.ObjectId().toString();
    const delivered = {
      conversationId,
      messageId,
      userId,
      deliveredAt: '2026-06-07T10:00:00.000Z',
    };
    const roomEmitter = { emit: jest.fn() };
    (gateway as any).server.to.mockReturnValue(roomEmitter);
    chatService.markMessageDelivered.mockResolvedValue(delivered);

    await gateway.markDelivered(client, { conversationId, messageId });

    expect(chatService.markMessageDelivered).toHaveBeenCalledWith(userId, conversationId, messageId);
    expect(roomEmitter.emit).toHaveBeenCalledWith('message_delivered', delivered);
  });
});
