import { Test } from '@nestjs/testing';
import { NotFoundException, ForbiddenException } from '@nestjs/common';
import { ChannelsService } from './channels.service';
import { PrismaService } from '@chatapp/database';

describe('ChannelsService', () => {
  let service: ChannelsService;
  let prisma: any;

  beforeEach(async () => {
    prisma = {
      channel: {
        create: jest.fn(),
        findUnique: jest.fn(),
        findFirst: jest.fn(),
        update: jest.fn(),
      },
      channelMember: {
        findMany: jest.fn(),
        createMany: jest.fn(),
        upsert: jest.fn(),
        updateMany: jest.fn(),
      },
    };

    const moduleRef = await Test.createTestingModule({
      providers: [
        ChannelsService,
        { provide: PrismaService, useValue: prisma },
      ],
    }).compile();

    service = moduleRef.get<ChannelsService>(ChannelsService);
  });

  describe('listChannels()', () => {
    it('devuelve canales del workspace', async () => {
      prisma.channelMember.findMany.mockResolvedValue([
        {
          channel: {
            id: 'ch-1',
            name: 'general',
            type: 'public',
            topic: 'General',
            purpose: 'General chat',
            isArchived: false,
            memberCount: 5,
          },
          unreadCount: 2,
          unreadMentionCount: 0,
          lastReadTs: '1234567890.000000',
          muted: false,
          notificationPref: 'all',
        },
      ]);

      const result = await service.listChannels('ws-1', 'user-1', {});

      expect(prisma.channelMember.findMany).toHaveBeenCalledWith({
        include: { channel: true },
        where: { leftAt: null, userId: 'user-1', workspaceId: 'ws-1' },
      });
      expect(result.channels).toHaveLength(1);
      expect(result.channels[0].name).toBe('general');
      expect(result.total).toBe(1);
    });
  });

  describe('create()', () => {
    it('crea canal correctamente', async () => {
      prisma.channel.create.mockResolvedValue({
        id: 'ch-1',
        name: 'new-channel',
        type: 'public',
      });

      const result = await service.create('ws-1', 'user-1', {
        name: 'New Channel',
        type: 'public',
      });

      expect(prisma.channel.create).toHaveBeenCalled();
      expect(result.id).toBe('ch-1');
      expect(result.name).toBe('new-channel');
    });

    it('crea canal con user_ids adicionales', async () => {
      prisma.channel.create.mockResolvedValue({
        id: 'ch-1',
        name: 'test',
        type: 'public',
      });

      await service.create('ws-1', 'user-1', {
        name: 'test',
        user_ids: ['user-2', 'user-3'],
      });

      expect(prisma.channelMember.createMany).toHaveBeenCalledWith({
        data: [
          { channelId: 'ch-1', userId: 'user-2', workspaceId: 'ws-1', notificationPref: 'all' },
          { channelId: 'ch-1', userId: 'user-3', workspaceId: 'ws-1', notificationPref: 'all' },
        ],
        skipDuplicates: true,
      });
    });
  });

  describe('join()', () => {
    it('agrega usuario como miembro en canal publico', async () => {
      prisma.channel.findUnique.mockResolvedValue({
        id: 'ch-1',
        type: 'public',
      });
      prisma.channelMember.upsert.mockResolvedValue({});

      const result = await service.join('ch-1', 'user-1', 'ws-1');

      expect(prisma.channelMember.upsert).toHaveBeenCalledWith({
        where: { channelId_userId: { channelId: 'ch-1', userId: 'user-1' } },
        create: { channelId: 'ch-1', userId: 'user-1', workspaceId: 'ws-1', notificationPref: 'all' },
        update: { leftAt: null },
      });
      expect(result.joined).toBe(true);
    });

    it('lanza NotFoundException si canal no existe', async () => {
      prisma.channel.findUnique.mockResolvedValue(null);

      await expect(service.join('ch-x', 'user-1', 'ws-1')).rejects.toThrow(NotFoundException);
    });

    it('lanza ForbiddenException al intentar unirse a canal privado', async () => {
      prisma.channel.findUnique.mockResolvedValue({
        id: 'ch-1',
        type: 'private',
      });

      await expect(service.join('ch-1', 'user-1', 'ws-1')).rejects.toThrow(ForbiddenException);
    });
  });

  describe('leave()', () => {
    it('marca leftAt en el membership', async () => {
      prisma.channelMember.updateMany.mockResolvedValue({ count: 1 });

      const result = await service.leave('ch-1', 'user-1');

      expect(prisma.channelMember.updateMany).toHaveBeenCalledWith({
        where: { channelId: 'ch-1', userId: 'user-1', leftAt: null },
        data: { leftAt: expect.any(Date) },
      });
      expect(result.left).toBe(true);
    });
  });
});