import { MessagingGateway } from '../gateways/messaging.gateway';

describe('MessagingGateway', () => {
  let gateway: MessagingGateway;
  let mockTokenService: { verifyAccessToken: ReturnType<typeof vi.fn> };
  let mockLogger: { log: ReturnType<typeof vi.fn>; warn: ReturnType<typeof vi.fn>; error: ReturnType<typeof vi.fn>; debug: ReturnType<typeof vi.fn> };
  let mockCommandBus: { execute: ReturnType<typeof vi.fn> };
  let mockConversationRepo: {
    findByUserId: ReturnType<typeof vi.fn>;
    findById: ReturnType<typeof vi.fn>;
  };

  beforeEach(() => {
    mockTokenService = { verifyAccessToken: vi.fn() };
    mockLogger = { log: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() };
    mockCommandBus = { execute: vi.fn() };
    mockConversationRepo = {
      findByUserId: vi.fn().mockResolvedValue([]),
      findById: vi.fn(),
    };

    gateway = new MessagingGateway(
      mockTokenService as any,
      mockLogger as any,
      mockCommandBus as any,
      mockConversationRepo as any,
    );
    gateway.server = {
      to: vi.fn().mockReturnThis(),
      emit: vi.fn(),
    } as any;
  });

  describe('handleConnection', () => {
    it('authenticates client and joins rooms', async () => {
      mockTokenService.verifyAccessToken.mockReturnValue({ sub: 'user-1', role: 'BUYER' });
      mockConversationRepo.findByUserId.mockResolvedValue([
        { id: 'conv-1' },
        { id: 'conv-2' },
      ]);

      const client = {
        id: 'socket-1',
        data: {},
        handshake: { auth: { token: 'valid-token' }, headers: {}, query: {} },
        join: vi.fn(),
        emit: vi.fn(),
        disconnect: vi.fn(),
      };

      await gateway.handleConnection(client as any);

      expect(client.data['userId']).toBe('user-1');
      expect(client.join).toHaveBeenCalledWith('user:user-1');
      expect(client.join).toHaveBeenCalledWith('conversation:conv-1');
      expect(client.join).toHaveBeenCalledWith('conversation:conv-2');
    });

    it('disconnects client with no token', async () => {
      const client = {
        id: 'socket-1',
        data: {},
        handshake: { auth: {}, headers: {}, query: {} },
        join: vi.fn(),
        emit: vi.fn(),
        disconnect: vi.fn(),
      };

      mockTokenService.verifyAccessToken.mockReturnValue(null);

      await gateway.handleConnection(client as any);

      expect(client.disconnect).toHaveBeenCalledWith(true);
    });

    it('disconnects client with invalid token', async () => {
      mockTokenService.verifyAccessToken.mockReturnValue(null);

      const client = {
        id: 'socket-1',
        data: {},
        handshake: { auth: { token: 'invalid' }, headers: {}, query: {} },
        join: vi.fn(),
        emit: vi.fn(),
        disconnect: vi.fn(),
      };

      await gateway.handleConnection(client as any);

      expect(client.disconnect).toHaveBeenCalledWith(true);
    });
  });

  describe('handleDisconnect', () => {
    it('cleans up socket tracking', () => {
      // First connect
      (gateway as any).userSockets.set('user-1', new Set(['socket-1']));

      const client = {
        id: 'socket-1',
        data: { userId: 'user-1' },
      };

      gateway.handleDisconnect(client as any);

      expect((gateway as any).userSockets.has('user-1')).toBe(false);
    });
  });

  describe('handleMessageSent', () => {
    it('emits message:new to conversation room', async () => {
      const event = {
        aggregateId: 'msg-1',
        conversationId: 'conv-1',
        senderId: 'user-1',
        type: 'TEXT',
        content: 'Hello!',
        occurredAt: new Date(),
      };

      await gateway.handleMessageSent(event as any);

      expect(gateway.server.to).toHaveBeenCalledWith('conversation:conv-1');
    });
  });

  describe('handleSendMessage', () => {
    it('sends message via command bus', async () => {
      mockCommandBus.execute.mockResolvedValue({ id: 'msg-1' });

      const client = {
        data: { userId: 'user-1' },
        emit: vi.fn(),
      };

      await gateway.handleSendMessage(
        client as any,
        { conversationId: 'conv-1', content: 'Hello!' },
      );

      expect(mockCommandBus.execute).toHaveBeenCalled();
      expect(client.emit).toHaveBeenCalledWith('message:sent', {
        messageId: 'msg-1',
        conversationId: 'conv-1',
      });
    });

    it('emits error when send fails', async () => {
      mockCommandBus.execute.mockRejectedValue(new Error('Send failed'));

      const client = {
        data: { userId: 'user-1' },
        emit: vi.fn(),
      };

      await gateway.handleSendMessage(
        client as any,
        { conversationId: 'conv-1', content: 'Hello!' },
      );

      expect(client.emit).toHaveBeenCalledWith('message:error', {
        conversationId: 'conv-1',
        error: 'Send failed',
      });
    });
  });

  describe('handleJoinConversation', () => {
    it('joins room when user is participant', async () => {
      mockConversationRepo.findById.mockResolvedValue({
        participants: [{ userId: 'user-1' }],
      });

      const client = {
        data: { userId: 'user-1' },
        join: vi.fn(),
      };

      await gateway.handleJoinConversation(client as any, { conversationId: 'conv-1' });

      expect(client.join).toHaveBeenCalledWith('conversation:conv-1');
    });

    it('does not join room when user is not participant', async () => {
      mockConversationRepo.findById.mockResolvedValue({
        participants: [{ userId: 'user-2' }],
      });

      const client = {
        data: { userId: 'user-1' },
        join: vi.fn(),
      };

      await gateway.handleJoinConversation(client as any, { conversationId: 'conv-1' });

      expect(client.join).not.toHaveBeenCalled();
    });
  });
});
