using System.Globalization; using line_gestao_api.Data; using line_gestao_api.Models; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; namespace line_gestao_api.Services; public class VigenciaNotificationBackgroundService : BackgroundService { private readonly IServiceScopeFactory _scopeFactory; private readonly ILogger _logger; private readonly NotificationOptions _options; public VigenciaNotificationBackgroundService( IServiceScopeFactory scopeFactory, IOptions options, ILogger logger) { _scopeFactory = scopeFactory; _logger = logger; _options = options.Value; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { var intervalMinutes = _options.CheckIntervalMinutes <= 0 ? 60 : _options.CheckIntervalMinutes; using var timer = new PeriodicTimer(TimeSpan.FromMinutes(intervalMinutes)); await RunOnceAsync(stoppingToken); while (await timer.WaitForNextTickAsync(stoppingToken)) { await RunOnceAsync(stoppingToken); } } private async Task RunOnceAsync(CancellationToken stoppingToken) { try { using var scope = _scopeFactory.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); var today = DateTime.UtcNow.Date; var reminderDays = _options.ReminderDays .Distinct() .Where(d => d > 0) .OrderBy(d => d) .ToList(); var users = await db.Users.AsNoTracking() .Select(u => new { u.Id, u.Name, u.Email }) .ToListAsync(stoppingToken); var userByName = users .Where(u => !string.IsNullOrWhiteSpace(u.Name)) .ToDictionary(u => u.Name.Trim().ToLowerInvariant(), u => u.Id); var userByEmail = users .Where(u => !string.IsNullOrWhiteSpace(u.Email)) .ToDictionary(u => u.Email.Trim().ToLowerInvariant(), u => u.Id); var vigencias = await db.VigenciaLines.AsNoTracking() .Where(v => v.DtTerminoFidelizacao != null) .ToListAsync(stoppingToken); var candidates = new List(); foreach (var vigencia in vigencias) { if (vigencia.DtTerminoFidelizacao is null) { continue; } var endDate = vigencia.DtTerminoFidelizacao.Value.Date; var usuario = vigencia.Usuario?.Trim(); var cliente = vigencia.Cliente?.Trim(); var linha = vigencia.Linha?.Trim(); var usuarioKey = usuario?.ToLowerInvariant(); Guid? userId = null; if (!string.IsNullOrWhiteSpace(usuarioKey)) { if (userByEmail.TryGetValue(usuarioKey, out var matchedByEmail)) { userId = matchedByEmail; } else if (userByName.TryGetValue(usuarioKey, out var matchedByName)) { userId = matchedByName; } } if (endDate < today) { var notification = BuildNotification( tipo: "Vencido", titulo: $"Linha vencida{FormatLinha(linha)}", mensagem: $"A linha{FormatLinha(linha)} do cliente {cliente ?? "(sem cliente)"} venceu em {endDate:dd/MM/yyyy}.", referenciaData: endDate, diasParaVencer: 0, userId: userId, usuario: usuario, cliente: cliente, linha: linha, vigenciaLineId: vigencia.Id); candidates.Add(notification); continue; } var daysUntil = (endDate - today).Days; if (reminderDays.Contains(daysUntil)) { var notification = BuildNotification( tipo: "AVencer", titulo: $"Linha a vencer em {daysUntil} dias{FormatLinha(linha)}", mensagem: $"A linha{FormatLinha(linha)} do cliente {cliente ?? "(sem cliente)"} vence em {endDate:dd/MM/yyyy}.", referenciaData: endDate, diasParaVencer: daysUntil, userId: userId, usuario: usuario, cliente: cliente, linha: linha, vigenciaLineId: vigencia.Id); candidates.Add(notification); } } if (candidates.Count == 0) { return; } var dedupKeys = candidates.Select(c => c.DedupKey).Distinct().ToList(); var existingKeys = await db.Notifications.AsNoTracking() .Where(n => dedupKeys.Contains(n.DedupKey)) .Select(n => n.DedupKey) .ToListAsync(stoppingToken); var existingSet = new HashSet(existingKeys); var toInsert = candidates .Where(c => !existingSet.Contains(c.DedupKey)) .ToList(); if (toInsert.Count == 0) { return; } await db.Notifications.AddRangeAsync(toInsert, stoppingToken); await db.SaveChangesAsync(stoppingToken); } catch (Exception ex) { _logger.LogError(ex, "Erro ao gerar notificações de vigência."); } } private static Notification BuildNotification( string tipo, string titulo, string mensagem, DateTime referenciaData, int diasParaVencer, Guid? userId, string? usuario, string? cliente, string? linha, Guid vigenciaLineId) { return new Notification { Tipo = tipo, Titulo = titulo, Mensagem = mensagem, Data = DateTime.UtcNow, ReferenciaData = referenciaData, DiasParaVencer = diasParaVencer, Lida = false, DedupKey = BuildDedupKey(tipo, vigenciaLineId, referenciaData, diasParaVencer, usuario, cliente, linha), UserId = userId, Usuario = usuario, Cliente = cliente, Linha = linha, VigenciaLineId = vigenciaLineId }; } private static string BuildDedupKey( string tipo, Guid vigenciaLineId, DateTime referenciaData, int diasParaVencer, string? usuario, string? cliente, string? linha) { var parts = new[] { tipo.Trim().ToLowerInvariant(), vigenciaLineId.ToString(), referenciaData.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture), diasParaVencer.ToString(CultureInfo.InvariantCulture), (usuario ?? string.Empty).Trim().ToLowerInvariant(), (cliente ?? string.Empty).Trim().ToLowerInvariant(), (linha ?? string.Empty).Trim().ToLowerInvariant() }; return string.Join('|', parts); } private static string FormatLinha(string? linha) { return string.IsNullOrWhiteSpace(linha) ? string.Empty : $" {linha}"; } }