using System.Text.Json; using Microsoft.Extensions.Logging; using Quartz; using UniVerse.Application.DTOs.Notifications; using UniVerse.Application.Interfaces; namespace UniVerse.Infrastructure.Notifications; public class QuartzNotificationScheduler : INotificationScheduler { private const string NotificationGroup = "notifications"; private readonly ISchedulerFactory _schedulerFactory; private readonly ILogger _logger; public QuartzNotificationScheduler(ISchedulerFactory schedulerFactory, ILogger logger) { _schedulerFactory = schedulerFactory; _logger = logger; } public async Task ScheduleAsync( NotificationMessage message, DateTimeOffset sendAt, string? jobId = null, CancellationToken cancellationToken = default) { if (sendAt <= DateTimeOffset.UtcNow) throw new ArgumentException("Scheduled notification time must be in the future.", nameof(sendAt)); var scheduler = await _schedulerFactory.GetScheduler(cancellationToken); jobId ??= Guid.NewGuid().ToString("N"); var jobKey = new JobKey(jobId, NotificationGroup); var payload = JsonSerializer.Serialize(message); var job = JobBuilder.Create() .WithIdentity(jobKey) .UsingJobData(NotificationJob.MessageDataKey, payload) .Build(); var trigger = TriggerBuilder.Create() .WithIdentity($"{jobId}.trigger", NotificationGroup) .ForJob(job) .StartAt(sendAt) .Build(); await scheduler.ScheduleJob(job, trigger, cancellationToken); _logger.LogInformation("Scheduled notification job {JobId} for {SendAt}", jobId, sendAt); return new ScheduledNotificationResponse(jobId, sendAt); } public async Task CancelAsync(string jobId, CancellationToken cancellationToken = default) { ArgumentException.ThrowIfNullOrWhiteSpace(jobId); var scheduler = await _schedulerFactory.GetScheduler(cancellationToken); var deleted = await scheduler.DeleteJob(new JobKey(jobId, NotificationGroup), cancellationToken); if (deleted) _logger.LogInformation("Cancelled notification job {JobId}", jobId); } }