import { NotFoundException } from '@nestjs/common';
import { plainToInstance } from 'class-transformer';
import { validate } from 'class-validator';
import { NotificationUnreadCountQueryDto } from './dto/notification-query.dto';
import { NotificationsService } from './notifications.service';

describe('NotificationsService', () => {
  it('rejects invalid unread-count category through DTO validation', async () => {
    const dto = plainToInstance(NotificationUnreadCountQueryDto, { category: 'badges' });

    const errors = await validate(dto);

    expect(errors.length).toBeGreaterThan(0);
    expect(errors[0].property).toBe('category');
  });

  it('creates mention notifications with mention type', async () => {
    const notificationsRepository = {
      create: jest.fn().mockResolvedValue({ toJSON: () => ({ _id: 'notification-1' }) }),
      countUnread: jest.fn().mockResolvedValue(5),
      countUnreadByFilter: jest.fn().mockResolvedValue(0),
    };
    const notificationsGateway = {
      emitCreated: jest.fn(),
    };

    const service = new NotificationsService(
      notificationsRepository as any,
      notificationsGateway as any,
    );

    await service.createMentionNotification(
      '507f1f77bcf86cd799439011',
      '507f191e810c19729de860ea',
      '507f1f77bcf86cd799439012',
      { previewText: 'Hello @rami' },
    );

    expect(notificationsRepository.create).toHaveBeenCalledWith(
      expect.objectContaining({
        type: 'mention',
        previewText: 'Hello @rami',
      }),
    );
  });

  it('recalculates unread count after markAllRead', async () => {
    const notificationsRepository = {
      markAllRead: jest.fn().mockResolvedValue(4),
      countUnread: jest.fn().mockResolvedValue(2),
      countUnreadByFilter: jest.fn().mockResolvedValue(0),
    };
    const notificationsGateway = {
      emitUnreadCount: jest.fn(),
    };

    const service = new NotificationsService(
      notificationsRepository as any,
      notificationsGateway as any,
    );

    await expect(service.markAllRead('user-1')).resolves.toEqual({
      message: 'All notifications marked as read',
      modifiedCount: 4,
      updatedCount: 4,
      unreadCount: 2,
    });
    expect(notificationsGateway.emitUnreadCount).toHaveBeenCalledWith(
      'user-1',
      2,
      expect.objectContaining({
        total: 2,
        interactions: 0,
        messages: 0,
      }),
    );
  });

  it('throws not found for invalid notification id in markRead', async () => {
    const notificationsRepository = {
      markRead: jest.fn(),
      countUnread: jest.fn(),
    };
    const notificationsGateway = {
      emitUnreadCount: jest.fn(),
    };

    const service = new NotificationsService(
      notificationsRepository as any,
      notificationsGateway as any,
    );

    await expect(service.markRead('user-1', 'invalid-id')).rejects.toBeInstanceOf(NotFoundException);
    expect(notificationsRepository.markRead).not.toHaveBeenCalled();
  });

  it('returns total unread count for the current user in the notifications list', async () => {
    const notificationsRepository = {
      findMine: jest.fn().mockResolvedValue([{ id: 'notification-1' }]),
      countMine: jest.fn().mockResolvedValue(30),
      countUnread: jest.fn().mockResolvedValue(7),
    };
    const notificationsGateway = {
      emitUnreadCount: jest.fn(),
    };

    const service = new NotificationsService(
      notificationsRepository as any,
      notificationsGateway as any,
    );

    await expect(
      service.getMine('507f1f77bcf86cd799439011', {
        page: 1,
        limit: 20,
        sortOrder: 'desc' as any,
      }),
    ).resolves.toMatchObject({
      count: 1,
      page: 1,
      limit: 20,
      total: 30,
      unreadCount: 7,
      pagination: {
        mode: 'offset',
        hasNextPage: true,
      },
    });
    expect(notificationsRepository.countUnread).toHaveBeenCalledWith('507f1f77bcf86cd799439011');
  });

  it('returns total unread count without category for backward compatibility', async () => {
    const notificationsRepository = {
      countUnread: jest.fn().mockResolvedValue(10),
    };
    const notificationsGateway = {};

    const service = new NotificationsService(
      notificationsRepository as any,
      notificationsGateway as any,
    );

    await expect(service.getUnreadCountByCategory('507f1f77bcf86cd799439011')).resolves.toEqual({
      unreadCount: 10,
    });
    expect(notificationsRepository.countUnread).toHaveBeenCalledWith('507f1f77bcf86cd799439011');
  });

  it('returns unread count for interactions category only', async () => {
    const notificationsRepository = {
      countUnreadByFilter: jest.fn().mockResolvedValue(4),
    };
    const notificationsGateway = {};

    const service = new NotificationsService(
      notificationsRepository as any,
      notificationsGateway as any,
    );

    await expect(
      service.getUnreadCountByCategory('507f1f77bcf86cd799439011', 'interactions'),
    ).resolves.toEqual({
      unreadCount: 4,
      category: 'interactions',
    });
    expect(notificationsRepository.countUnreadByFilter).toHaveBeenCalledWith(
      '507f1f77bcf86cd799439011',
      {
        type: {
          $in: [
            'like',
            'comment',
            'reply',
            'mention',
            'save',
            'share',
            'collaboration_request',
            'system',
          ],
        },
      },
    );
  });

  it('returns unread count for messages category only', async () => {
    const notificationsRepository = {
      countUnreadByFilter: jest.fn().mockResolvedValue(3),
    };
    const notificationsGateway = {};

    const service = new NotificationsService(
      notificationsRepository as any,
      notificationsGateway as any,
    );

    await expect(
      service.getUnreadCountByCategory('507f1f77bcf86cd799439011', 'messages'),
    ).resolves.toEqual({
      unreadCount: 3,
      category: 'messages',
    });
    expect(notificationsRepository.countUnreadByFilter).toHaveBeenCalledWith(
      '507f1f77bcf86cd799439011',
      { type: { $in: ['message'] } },
    );
  });

  it('returns unread count for follow requests category only', async () => {
    const notificationsRepository = {
      countUnreadByFilter: jest.fn().mockResolvedValue(2),
    };
    const notificationsGateway = {};

    const service = new NotificationsService(
      notificationsRepository as any,
      notificationsGateway as any,
    );

    await expect(
      service.getUnreadCountByCategory('507f1f77bcf86cd799439011', 'follow_requests'),
    ).resolves.toEqual({
      unreadCount: 2,
      category: 'follow_requests',
    });
    expect(notificationsRepository.countUnreadByFilter).toHaveBeenCalledWith(
      '507f1f77bcf86cd799439011',
      {
        type: {
          $in: ['follow_request', 'follow_request_approved', 'follow_request_rejected'],
        },
      },
    );
  });

  it('returns grouped unread counts for badge hydration', async () => {
    const notificationsRepository = {
      countUnread: jest.fn().mockResolvedValue(10),
      countUnreadByFilter: jest
        .fn()
        .mockResolvedValueOnce(4)
        .mockResolvedValueOnce(3)
        .mockResolvedValueOnce(1)
        .mockResolvedValueOnce(2)
        .mockResolvedValueOnce(1)
        .mockResolvedValueOnce(0),
    };
    const notificationsGateway = {};

    const service = new NotificationsService(
      notificationsRepository as any,
      notificationsGateway as any,
    );

    await expect(service.getUnreadCounts('507f1f77bcf86cd799439011')).resolves.toEqual({
      total: 10,
      interactions: 4,
      messages: 3,
      follows: 1,
      followRequests: 2,
      collaboration: 1,
      system: 0,
    });
  });

  it('filters notifications by interactions category', async () => {
    const notificationsRepository = {
      findMine: jest.fn().mockResolvedValue([]),
      countMine: jest.fn().mockResolvedValue(0),
      countUnread: jest.fn().mockResolvedValue(0),
    };
    const notificationsGateway = {
      emitUnreadCount: jest.fn(),
    };

    const service = new NotificationsService(
      notificationsRepository as any,
      notificationsGateway as any,
    );

    await service.getMine('507f1f77bcf86cd799439011', {
      page: 1,
      limit: 20,
      category: 'interactions',
    });

    expect(notificationsRepository.findMine).toHaveBeenCalledWith(
      '507f1f77bcf86cd799439011',
      {
        type: {
          $in: [
            'like',
            'comment',
            'reply',
            'mention',
            'save',
            'share',
            'collaboration_request',
            'system',
          ],
        },
      },
      0,
      20,
      { createdAt: -1 },
    );
  });

  it('keeps type filter priority over category for backward compatibility', async () => {
    const notificationsRepository = {
      findMine: jest.fn().mockResolvedValue([]),
      countMine: jest.fn().mockResolvedValue(0),
      countUnread: jest.fn().mockResolvedValue(0),
    };
    const notificationsGateway = {
      emitUnreadCount: jest.fn(),
    };

    const service = new NotificationsService(
      notificationsRepository as any,
      notificationsGateway as any,
    );

    await service.getMine('507f1f77bcf86cd799439011', {
      page: 1,
      limit: 20,
      type: 'message',
      category: 'interactions',
    });

    expect(notificationsRepository.findMine).toHaveBeenCalledWith(
      '507f1f77bcf86cd799439011',
      { type: 'message' },
      0,
      20,
      { createdAt: -1 },
    );
  });

  it('creates system notifications with system resource mapping when requested', async () => {
    const notificationsRepository = {
      create: jest.fn().mockResolvedValue({ toJSON: () => ({ _id: 'notification-1' }) }),
      countUnread: jest.fn().mockResolvedValue(1),
      countUnreadByFilter: jest.fn().mockResolvedValue(0),
    };
    const notificationsGateway = {
      emitCreated: jest.fn(),
    };

    const service = new NotificationsService(
      notificationsRepository as any,
      notificationsGateway as any,
    );

    await service.create({
      actorId: '507f1f77bcf86cd799439011',
      recipientId: '507f191e810c19729de860ea',
      type: 'system',
    });

    expect(notificationsRepository.create).toHaveBeenCalledWith(
      expect.objectContaining({
        type: 'system',
        resourceType: 'system',
        deepLink: '',
      }),
    );
  });
});
