using Microsoft.Extensions.Logging; using Microsoft.EntityFrameworkCore; using UniVerse.Application.DTOs.Common; using UniVerse.Application.DTOs.Notifications; using UniVerse.Application.Interfaces; using UniVerse.Domain.Entities; using UniVerse.Infrastructure.Data; namespace UniVerse.Infrastructure.Notifications; public class NotificationService : INotificationService { private readonly AppDbContext _db; private readonly IEnumerable _providers; private readonly INotificationScheduler _scheduler; private readonly ILogger _logger; public NotificationService( AppDbContext db, IEnumerable providers, INotificationScheduler scheduler, ILogger logger) { _db = db; _providers = providers; _scheduler = scheduler; _logger = logger; } public async Task SendAsync(NotificationMessage message, CancellationToken cancellationToken = default) { ArgumentException.ThrowIfNullOrWhiteSpace(message.Channel); ArgumentException.ThrowIfNullOrWhiteSpace(message.Recipient); ArgumentException.ThrowIfNullOrWhiteSpace(message.Subject); ArgumentException.ThrowIfNullOrWhiteSpace(message.Body); var provider = _providers.FirstOrDefault(p => string.Equals(p.Channel, message.Channel, StringComparison.OrdinalIgnoreCase)) ?? throw new InvalidOperationException($"Notification provider for channel '{message.Channel}' is not registered."); _logger.LogInformation("Dispatching notification through {Channel} to {Recipient}", message.Channel, message.Recipient); await provider.SendAsync(message, cancellationToken); } public Task ScheduleAsync(ScheduleNotificationRequest request, CancellationToken cancellationToken = default) { var message = new NotificationMessage( request.Channel, request.Recipient, request.Subject, request.Body, request.RecipientName, request.Metadata); return _scheduler.ScheduleAsync(message, request.SendAt, cancellationToken); } public async Task CreateUserNotificationAsync( int userId, string type, string title, string body, CancellationToken cancellationToken = default) { ArgumentException.ThrowIfNullOrWhiteSpace(type); ArgumentException.ThrowIfNullOrWhiteSpace(title); ArgumentException.ThrowIfNullOrWhiteSpace(body); var notification = new UserNotification { UserId = userId, Type = type, Title = title, Body = body }; _db.UserNotifications.Add(notification); await _db.SaveChangesAsync(cancellationToken); return ToDto(notification); } public async Task> GetUserNotificationsAsync( int userId, PaginationRequest pagination, CancellationToken cancellationToken = default) { var query = _db.UserNotifications.Where(n => n.UserId == userId); var total = await query.CountAsync(cancellationToken); var items = await query .OrderByDescending(n => n.CreatedAt) .Skip((pagination.Page - 1) * pagination.PageSize) .Take(pagination.PageSize) .Select(n => new UserNotificationDto( n.Id, n.Type, n.Title, n.Body, n.IsRead, n.CreatedAt)) .ToListAsync(cancellationToken); return PagedResult.Create(items, total, pagination.Page, pagination.PageSize); } public async Task MarkAllReadAsync(int userId, CancellationToken cancellationToken = default) { await _db.UserNotifications .Where(n => n.UserId == userId && !n.IsRead) .ExecuteUpdateAsync(setters => setters.SetProperty(n => n.IsRead, true), cancellationToken); } private static UserNotificationDto ToDto(UserNotification notification) => new( notification.Id, notification.Type, notification.Title, notification.Body, notification.IsRead, notification.CreatedAt ); }