using IceImportBLL.IceImportRunNotifier; using IceImportDAL.DTO.IceImportPipeline; using IceImportDAL.DTO.IceImportRun; using Microsoft.AspNetCore.SignalR; using Microsoft.Extensions.Logging; namespace IceImportSL.Hubs; // Concrete implementation of the BLL-defined IIceImportRunNotifier, wrapping // IHubContext — this is the one place in the whole module // allowed to know about SignalR, per CLAUDE.md's "BLL must never reference ASP.NET types" rule. // Registered as a Singleton in IceImportModule.Register() (IHubContext itself is singleton and // thread-safe) — mirrors AutomationSL.Hubs.AutomationRunNotifier exactly. public class IceImportRunNotifier : IIceImportRunNotifier { private readonly IHubContext _hubContext; private readonly ILogger _logger; public IceImportRunNotifier( IHubContext hubContext, ILogger logger) { _hubContext = hubContext; _logger = logger; } public async Task NotifyProgressAsync(long runId, string stage, int processedRows, int totalRows, CancellationToken ct = default) { try { await _hubContext.Clients.Group(IceImportRunHub.RunGroup(runId)) .ReceiveProgress(new IceImportProgressDTO(runId, stage, processedRows, totalRows, "Running")); } catch (Exception ex) { _logger.LogWarning(ex, "IceImportRunNotifier: failed to push progress for RunId {RunId}", runId); } } public async Task NotifyRowErrorAsync(long runId, IceImportFieldErrorDTO error, CancellationToken ct = default) { try { await _hubContext.Clients.Group(IceImportRunHub.RunGroup(runId)).ReceiveRowError(error); } catch (Exception ex) { _logger.LogWarning(ex, "IceImportRunNotifier: failed to push row error for RunId {RunId}", runId); } } public async Task NotifyLogAppendedAsync(long runId, string logLevel, string message, CancellationToken ct = default) { try { // No dedicated "log line" client method in this phase's IIceImportRunClient — surfaced // as a Running-status progress event carrying the message in the Stage field so a // connected client still sees it without widening the client contract for a // nice-to-have. Revisit with a dedicated ReceiveLogAppended if the FE (Phase 3) needs a // real log tail. await _hubContext.Clients.Group(IceImportRunHub.RunGroup(runId)) .ReceiveProgress(new IceImportProgressDTO(runId, $"{logLevel}: {message}", 0, 0, "Running")); } catch (Exception ex) { _logger.LogWarning(ex, "IceImportRunNotifier: failed to push log for RunId {RunId}", runId); } } public async Task NotifyCompletedAsync( long runId, string status, int rowTotalCount, int successCount, int failedCount, string? errorMessage, CancellationToken ct = default) { try { await _hubContext.Clients.Group(IceImportRunHub.RunGroup(runId)) .ReceiveCompleted(new IceImportRunSummaryDTO(runId, status, rowTotalCount, successCount, failedCount, errorMessage)); } catch (Exception ex) { _logger.LogWarning(ex, "IceImportRunNotifier: failed to push completion for RunId {RunId}", runId); } } }