using Cleanuparr.Domain.Enums; using Cleanuparr.Infrastructure.Features.Notifications.Models; using Cleanuparr.Persistence; using Cleanuparr.Persistence.Models.Configuration.Notification; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; namespace Cleanuparr.Infrastructure.Features.Notifications; public sealed class NotificationConfigurationService : INotificationConfigurationService { private readonly DataContext _dataContext; private readonly ILogger _logger; private List? _cachedProviders; private readonly SemaphoreSlim _cacheSemaphore = new(1, 1); public NotificationConfigurationService( DataContext dataContext, ILogger logger) { _dataContext = dataContext; _logger = logger; } public async Task> GetActiveProvidersAsync() { await _cacheSemaphore.WaitAsync(); try { if (_cachedProviders != null) { return _cachedProviders.Where(p => p.IsEnabled).ToList(); } } finally { _cacheSemaphore.Release(); } await LoadProvidersAsync(); await _cacheSemaphore.WaitAsync(); try { return _cachedProviders?.Where(p => p.IsEnabled).ToList() ?? new List(); } finally { _cacheSemaphore.Release(); } } public async Task> GetProvidersForEventAsync(NotificationEventType eventType) { var activeProviders = await GetActiveProvidersAsync(); return activeProviders.Where(provider => IsEventEnabled(provider.Events, eventType)).ToList(); } public async Task GetProviderByIdAsync(Guid id) { var allProviders = await GetActiveProvidersAsync(); return allProviders.FirstOrDefault(p => p.Id == id); } public async Task InvalidateCacheAsync() { await _cacheSemaphore.WaitAsync(); try { _cachedProviders = null; } finally { _cacheSemaphore.Release(); } _logger.LogDebug("Notification provider cache invalidated"); } private async Task LoadProvidersAsync() { try { var providers = await _dataContext.Set() .Include(p => p.NotifiarrConfiguration) .Include(p => p.AppriseConfiguration) .Include(p => p.NtfyConfiguration) .Include(p => p.PushoverConfiguration) .AsNoTracking() .ToListAsync(); var dtos = providers.Select(MapToDto).ToList(); await _cacheSemaphore.WaitAsync(); try { _cachedProviders = dtos; } finally { _cacheSemaphore.Release(); } _logger.LogDebug("Loaded {count} notification providers", dtos.Count); } catch (Exception ex) { _logger.LogError(ex, "Failed to load notification providers"); await _cacheSemaphore.WaitAsync(); try { _cachedProviders = new List(); } finally { _cacheSemaphore.Release(); } } } private static NotificationProviderDto MapToDto(NotificationConfig config) { var events = new NotificationEventFlags { OnFailedImportStrike = config.OnFailedImportStrike, OnStalledStrike = config.OnStalledStrike, OnSlowStrike = config.OnSlowStrike, OnQueueItemDeleted = config.OnQueueItemDeleted, OnDownloadCleaned = config.OnDownloadCleaned, OnCategoryChanged = config.OnCategoryChanged }; var configuration = config.Type switch { NotificationProviderType.Notifiarr => config.NotifiarrConfiguration as object, NotificationProviderType.Apprise => config.AppriseConfiguration, NotificationProviderType.Ntfy => config.NtfyConfiguration, NotificationProviderType.Pushover => config.PushoverConfiguration, _ => throw new ArgumentOutOfRangeException(nameof(config), $"Config type for provider type {config.Type.ToString()} is not registered") }; return new NotificationProviderDto { Id = config.Id, Name = config.Name, Type = config.Type, IsEnabled = config.IsEnabled && config.IsConfigured && config.HasAnyEventEnabled, Events = events, Configuration = configuration ?? new object() }; } private static bool IsEventEnabled(NotificationEventFlags events, NotificationEventType eventType) { return eventType switch { NotificationEventType.FailedImportStrike => events.OnFailedImportStrike, NotificationEventType.StalledStrike => events.OnStalledStrike, NotificationEventType.SlowSpeedStrike or NotificationEventType.SlowTimeStrike => events.OnSlowStrike, NotificationEventType.QueueItemDeleted => events.OnQueueItemDeleted, NotificationEventType.DownloadCleaned => events.OnDownloadCleaned, NotificationEventType.CategoryChanged => events.OnCategoryChanged, NotificationEventType.Test => true, _ => throw new ArgumentOutOfRangeException(nameof(eventType), $"Provider type {eventType} is not yet registered") }; } }