📄 MatDenDagen/Services/NotificationService.cs
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using HuaweiWifiSms.Grpc;
using MatDenDagen.Infrastructure.Storage.Database;
using MatDenDagen.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace MatDenDagen.Services;

public sealed class NotificationService(
    ILogger<NotificationService> logger,
    IOptions<NotificationServiceOptions> options,
    DateService dateService,
    QuestionnaireContext questionnaireContext,
    SmsSender.SmsSenderClient smsClient,
    TimeProvider timeProvider
)
{
    private readonly TimeSpan interval = options.Value.CheckInterval;

    public async Task<DateTimeOffset> CheckAndSendNotificationsAsync(
        DateTimeOffset? lastCheck,
        CancellationToken cancellationToken
    )
    {
        var utcNow = timeProvider.GetUtcNow();
        lastCheck ??= utcNow - interval;

        logger.LogDebug("Checking for notifications between {LastCheck} and {Now}", lastCheck, utcNow);

        var dateConfig = await dateService.GetDateConfigAsync();
        if (dateConfig?.TheDay is not DateOnly theDay)
        {
            logger.LogDebug("No date configured in database, cannot send notifications");
            return utcNow;
        }

        var eventDate = new DateTimeOffset(theDay.ToDateTime(TimeOnly.MinValue, DateTimeKind.Utc), TimeSpan.Zero);

        var participants = await questionnaireContext
            .Participants.AsNoTracking()
            .Where(p => p.NotificationMinutesOffset.HasValue)
            .ToListAsync(cancellationToken);

        foreach (var participant in participants)
        {
            if (participant.NotificationMinutesOffset is not int offset)
            {
                continue;
            }

            var notificationTime = eventDate.AddMinutes(offset);

            if (lastCheck <= notificationTime && notificationTime <= utcNow)
            {
                await SendNotificationAsync(participant, theDay, cancellationToken);
            }
        }

        return utcNow;
    }

    private async Task SendNotificationAsync(
        Participant participant,
        DateOnly theDay,
        CancellationToken cancellationToken
    )
    {
        try
        {
            logger.LogInformation("Sending notification to {PhoneNumber} for scheduled time", participant.PhoneNumber);

            var request = new SmsRequest
            {
                RecipientPhoneNumber = participant.PhoneNumber,
                Content = $"Mat den Dagen påminnelse: Dagen är här! {theDay:yyyy-MM-dd}",
            };

            await smsClient.SendSmsAsync(request, cancellationToken: cancellationToken);

            logger.LogDebug(
                "Notification sent successfully to {PhoneNumber}: {Message}",
                participant.PhoneNumber,
                request
            );
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "Failed to send notification to {PhoneNumber}", participant.PhoneNumber);
        }
    }
}