import { BadRequestException, NotFoundException } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { NotificationsService } from './notifications.service';

function createQueryBuilder() {
  return {
    where: jest.fn().mockReturnThis(),
    orderBy: jest.fn().mockReturnThis(),
    take: jest.fn().mockReturnThis(),
    andWhere: jest.fn().mockReturnThis(),
    getMany: jest.fn().mockResolvedValue([]),
  };
}

describe('NotificationsService boundaries', () => {
  it('rejects empty recipients and de-duplicates repeated recipients', async () => {
    const repo = {
      save: jest.fn().mockImplementation(async (rows) => rows),
    };
    const service = new NotificationsService(repo as never, { emit: jest.fn() } as never);

    await expect(
      service.create({ recipientIds: [], type: 'test', title: '标题' }),
    ).rejects.toBeInstanceOf(BadRequestException);

    const saved = await service.create({ recipientIds: [1, 1, 2], type: 'test', title: '标题' });
    expect(saved).toHaveLength(2);
    expect(repo.save).toHaveBeenCalledWith([
      expect.objectContaining({ recipientId: 1 }),
      expect.objectContaining({ recipientId: 2 }),
    ]);
  });

  it('clamps service-level page size to protect callers outside the controller', async () => {
    const qb = createQueryBuilder();
    const service = new NotificationsService(
      { createQueryBuilder: jest.fn().mockReturnValue(qb) } as never,
      new EventEmitter2(),
    );

    await service.findByUser(7, undefined, 1000);
    expect(qb.take).toHaveBeenCalledWith(100);
  });

  it('does not allow marking another user notification as read', async () => {
    const repo = { findOne: jest.fn().mockResolvedValue(null), update: jest.fn() };
    const service = new NotificationsService(repo as never, new EventEmitter2());
    await expect(service.markRead(3, 7)).rejects.toBeInstanceOf(NotFoundException);
    expect(repo.update).not.toHaveBeenCalled();
  });

  it('treats marking an already-read notification as idempotent', async () => {
    const repo = {
      findOne: jest.fn().mockResolvedValue({ id: 3, recipientId: 7, isRead: true }),
      update: jest.fn(),
    };
    const service = new NotificationsService(repo as never, new EventEmitter2());
    await expect(service.markRead(3, 7)).resolves.toBeUndefined();
    expect(repo.update).not.toHaveBeenCalled();
  });
});
