import { Result } from '@core/shared/domain/Result';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { NotificationsService } from './NotificationsService';

describe('NotificationsService', () => {
  const mockGetUnreadNotificationsUseCase = { execute: vi.fn() };
  const mockGetAllNotificationsUseCase = { execute: vi.fn() };
  const mockMarkNotificationReadUseCase = { execute: vi.fn() };

  let service: NotificationsService;

  beforeEach(() => {
    vi.clearAllMocks();
    service = new NotificationsService(
      mockGetUnreadNotificationsUseCase as never,
      mockGetAllNotificationsUseCase as never,
      mockMarkNotificationReadUseCase as never,
    );
  });

  describe('getUnreadNotifications', () => {
    it('should return unread notifications on success', async () => {
      const mockNotification = {
        toJSON: () => ({ id: '1', message: 'Test notification' }),
      };

      mockGetUnreadNotificationsUseCase.execute.mockResolvedValue(
        Result.ok({ notifications: [mockNotification] })
      );

      const result = await service.getUnreadNotifications('user-123');

      expect(mockGetUnreadNotificationsUseCase.execute).toHaveBeenCalledWith({
        recipientId: 'user-123',
      });
      expect(result).toEqual([{ id: '1', message: 'Test notification' }]);
    });

    it('should throw error when use case fails', async () => {
      mockGetUnreadNotificationsUseCase.execute.mockResolvedValue(
        Result.err({ code: 'ERROR', details: { message: 'Failed to get notifications' } })
      );

      await expect(service.getUnreadNotifications('user-123')).rejects.toThrow(
        'Failed to get notifications'
      );
    });

    it('should throw generic error when no message provided', async () => {
      mockGetUnreadNotificationsUseCase.execute.mockResolvedValue(
        Result.err({ code: 'ERROR', details: {} })
      );

      await expect(service.getUnreadNotifications('user-123')).rejects.toThrow(
        'Failed to get unread notifications'
      );
    });

    it('should handle empty notifications list', async () => {
      mockGetUnreadNotificationsUseCase.execute.mockResolvedValue(
        Result.ok({ notifications: [] })
      );

      const result = await service.getUnreadNotifications('user-123');

      expect(result).toEqual([]);
    });

    it('should handle multiple notifications', async () => {
      const mockNotifications = [
        { toJSON: () => ({ id: '1', message: 'Notification 1' }) },
        { toJSON: () => ({ id: '2', message: 'Notification 2' }) },
        { toJSON: () => ({ id: '3', message: 'Notification 3' }) },
      ];

      mockGetUnreadNotificationsUseCase.execute.mockResolvedValue(
        Result.ok({ notifications: mockNotifications })
      );

      const result = await service.getUnreadNotifications('user-123');

      expect(result).toHaveLength(3);
      expect(result[0]).toEqual({ id: '1', message: 'Notification 1' });
      expect(result[1]).toEqual({ id: '2', message: 'Notification 2' });
      expect(result[2]).toEqual({ id: '3', message: 'Notification 3' });
    });
  });

  describe('getAllNotifications', () => {
    it('should return all notifications on success', async () => {
      const mockNotification = {
        toJSON: () => ({ id: '1', message: 'Test notification' }),
      };

      mockGetAllNotificationsUseCase.execute.mockResolvedValue(
        Result.ok({ notifications: [mockNotification] })
      );

      const result = await service.getAllNotifications('user-123');

      expect(mockGetAllNotificationsUseCase.execute).toHaveBeenCalledWith({
        recipientId: 'user-123',
      });
      expect(result).toEqual([{ id: '1', message: 'Test notification' }]);
    });

    it('should throw error when use case fails', async () => {
      mockGetAllNotificationsUseCase.execute.mockResolvedValue(
        Result.err({ code: 'ERROR', details: { message: 'Failed to get notifications' } })
      );

      await expect(service.getAllNotifications('user-123')).rejects.toThrow(
        'Failed to get notifications'
      );
    });

    it('should throw generic error when no message provided', async () => {
      mockGetAllNotificationsUseCase.execute.mockResolvedValue(
        Result.err({ code: 'ERROR', details: {} })
      );

      await expect(service.getAllNotifications('user-123')).rejects.toThrow(
        'Failed to get all notifications'
      );
    });

    it('should handle empty notifications list', async () => {
      mockGetAllNotificationsUseCase.execute.mockResolvedValue(
        Result.ok({ notifications: [] })
      );

      const result = await service.getAllNotifications('user-123');

      expect(result).toEqual([]);
    });

    it('should handle multiple notifications', async () => {
      const mockNotifications = [
        { toJSON: () => ({ id: '1', message: 'Notification 1' }) },
        { toJSON: () => ({ id: '2', message: 'Notification 2' }) },
        { toJSON: () => ({ id: '3', message: 'Notification 3' }) },
      ];

      mockGetAllNotificationsUseCase.execute.mockResolvedValue(
        Result.ok({ notifications: mockNotifications })
      );

      const result = await service.getAllNotifications('user-123');

      expect(result).toHaveLength(3);
      expect(result[0]).toEqual({ id: '1', message: 'Notification 1' });
      expect(result[1]).toEqual({ id: '2', message: 'Notification 2' });
      expect(result[2]).toEqual({ id: '3', message: 'Notification 3' });
    });
  });

  describe('markAsRead', () => {
    it('should mark notification as read on success', async () => {
      mockMarkNotificationReadUseCase.execute.mockResolvedValue(Result.ok(undefined));

      await service.markAsRead('notification-123', 'user-123');

      expect(mockMarkNotificationReadUseCase.execute).toHaveBeenCalledWith({
        notificationId: 'notification-123',
        recipientId: 'user-123',
      });
    });

    it('should throw error when use case fails', async () => {
      mockMarkNotificationReadUseCase.execute.mockResolvedValue(
        Result.err({ code: 'ERROR', details: { message: 'Failed to mark as read' } })
      );

      await expect(service.markAsRead('notification-123', 'user-123')).rejects.toThrow(
        'Failed to mark as read'
      );
    });

    it('should throw generic error when no message provided', async () => {
      mockMarkNotificationReadUseCase.execute.mockResolvedValue(
        Result.err({ code: 'ERROR', details: {} })
      );

      await expect(service.markAsRead('notification-123', 'user-123')).rejects.toThrow(
        'Failed to mark notification as read'
      );
    });

    it('should handle different notification IDs', async () => {
      mockMarkNotificationReadUseCase.execute.mockResolvedValue(Result.ok(undefined));

      await service.markAsRead('notification-456', 'user-123');

      expect(mockMarkNotificationReadUseCase.execute).toHaveBeenCalledWith({
        notificationId: 'notification-456',
        recipientId: 'user-123',
      });
    });

    it('should handle different user IDs', async () => {
      mockMarkNotificationReadUseCase.execute.mockResolvedValue(Result.ok(undefined));

      await service.markAsRead('notification-123', 'user-456');

      expect(mockMarkNotificationReadUseCase.execute).toHaveBeenCalledWith({
        notificationId: 'notification-123',
        recipientId: 'user-456',
      });
    });
  });
});
