using System.Collections.Immutable; using Microsoft.Extensions.Logging; using StellaOps.Notify.Models; using StellaOps.Notifier.Worker.Storage; namespace StellaOps.Notifier.Worker.Escalation; /// /// Default implementation of on-call schedule resolution. /// public sealed class DefaultOnCallResolver : IOnCallResolver { private readonly IOnCallScheduleService? _scheduleService; private readonly TimeProvider _timeProvider; private readonly ILogger _logger; public DefaultOnCallResolver( TimeProvider timeProvider, ILogger logger, IOnCallScheduleService? scheduleService = null) { _timeProvider = timeProvider ?? TimeProvider.System; _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _scheduleService = scheduleService; } public async Task ResolveAsync( string tenantId, string scheduleId, DateTimeOffset? evaluationTime = null, CancellationToken cancellationToken = default) { ArgumentException.ThrowIfNullOrWhiteSpace(tenantId); ArgumentException.ThrowIfNullOrWhiteSpace(scheduleId); if (_scheduleService is null) { _logger.LogWarning("On-call schedule repository not available"); return new NotifyOnCallResolution(scheduleId, evaluationTime ?? _timeProvider.GetUtcNow(), ImmutableArray.Empty); } var schedule = await _scheduleService.GetScheduleAsync(tenantId, scheduleId, cancellationToken).ConfigureAwait(false); if (schedule is null) { _logger.LogWarning("On-call schedule {ScheduleId} not found for tenant {TenantId}", scheduleId, tenantId); return new NotifyOnCallResolution(scheduleId, evaluationTime ?? _timeProvider.GetUtcNow(), ImmutableArray.Empty); } return ResolveAt(schedule, evaluationTime ?? _timeProvider.GetUtcNow()); } public NotifyOnCallResolution ResolveAt( OnCallSchedule schedule, DateTimeOffset evaluationTime) { ArgumentNullException.ThrowIfNull(schedule); var layer = schedule.Layers .Where(l => l.Users is { Count: > 0 }) .OrderByDescending(l => l.Priority) .FirstOrDefault(); if (layer is null) { _logger.LogDebug("No active on-call layer found for schedule {ScheduleId} at {EvaluationTime}", schedule.ScheduleId, evaluationTime); return new NotifyOnCallResolution(schedule.ScheduleId, evaluationTime, ImmutableArray.Empty); } var user = layer.Users.First(); var participant = NotifyOnCallParticipant.Create(user.UserId, user.Name, user.Email, user.Phone); return new NotifyOnCallResolution( schedule.ScheduleId, evaluationTime, ImmutableArray.Create(participant), sourceLayer: layer.Name); } }