using System.Text.Json; using GB5Shared.ActionProcessor; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace FrameworkBLL.ActionProcessor.Handlers { /// /// ActionType = 3 (Correspondence — GB5Solution/Correspondence module, ECP Phase 2). /// Dispatches a TCORRESPONDENCE send to the underlying Email(0)/SMS(1) handler depending on /// TCORRESPONDENCE.MODE (carried as "Mode" in dto.Payload). /// /// Deliberately lives here (FrameworkBLL), not inside CorrespondenceBLL, even though the plan's /// module file layout listed "CorrespondenceBLL/Handlers/CorrespondenceActionHandler.cs" as one /// option: ActionProcessorWorker (the real consumer that resolves IActionHandlerResolver, see /// FrameworkSL/Controllers/ActionProcessor/ActionProcessorWorker.cs) runs inside FrameworkSL's /// own standalone host process — FrameworkSL is a self-contained Sdk.Web app with its own /// Program.cs/DI container, not loaded by any of the GB5Solution/Hosts/* consolidated hosts. /// FrameworkSL never references business modules like Correspondence, so any IActionHandler it /// must discover at runtime has to live in an assembly FrameworkSL already scans — FrameworkBLL /// (see EmailActionHandler/SmsActionHandler/WebhookActionHandler, all in this same folder). /// Placing it in CorrespondenceBLL instead would require FrameworkSL to add a project reference /// to a business module, inverting the framework/module dependency direction. This is the /// plan's explicitly-offered alternative ("lives alongside FrameworkBLL/ActionProcessor/Handlers /// or a Correspondence-owned equivalent") — chosen for this concrete architectural reason. /// /// This handler needs no reference to CorrespondenceDAL/BLL at all: CorrespondenceBLL. /// SaveAndSendCorrespondenceAsync already resolved everything needed (recipients, subject, /// mode, the merged document's AttachmentId) into the ActionEventDto payload at queue-time — /// this handler only re-targets that payload to the correct underlying ActionType and delegates. /// public sealed class CorrespondenceActionHandler : IActionHandler { public int ActionType => 3; private const int EmailActionType = 0; private const int SmsActionType = 1; // Resolved lazily from IServiceProvider at dispatch time, not injected directly — // ActionHandlerResolver's constructor enumerates every IActionHandler (including this // one), so a direct IActionHandlerResolver constructor dependency here is a DI cycle. private readonly IServiceProvider _serviceProvider; private readonly ILogger _logger; public CorrespondenceActionHandler( IServiceProvider serviceProvider, ILogger logger) { _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public async Task HandleAsync(ActionEventDto dto, CancellationToken ct = default) { if (dto == null) return ActionResult.Permanent("ActionEventDto cannot be null"); var mode = ReadMode(dto); var targetActionType = mode switch { 2 => SmsActionType, // TCORRESPONDENCE.MODE 2 = sms _ => EmailActionType, // default / MODE 1 = mail }; var resolver = _serviceProvider.GetRequiredService(); var handler = resolver.Resolve(targetActionType); if (handler is null) { _logger.LogError( "CorrespondenceActionHandler: no handler registered for underlying ActionType={Type} | ActionRunId={RunId}", targetActionType, dto.ActionRunId); return ActionResult.Permanent($"No handler registered for underlying ActionType {targetActionType}"); } // Re-target the same payload to the resolved handler's ActionType — SendTo/CC/BCC, // the Subject/Body carried in Payload, and AttachmentId all pass through unchanged so // EmailActionHandler/SmsActionHandler's own recipient-resolution and (for Email) // attachment logic runs exactly as it would for any other action. var delegateDto = new ActionEventDto { ActionRunId = dto.ActionRunId, ActionId = dto.ActionId, ActionType = targetActionType, TenantId = dto.TenantId, DatabaseName = dto.DatabaseName, SendTo = dto.SendTo, ToDeliveryType = dto.ToDeliveryType, ToContentType = dto.ToContentType, TemplateId = dto.TemplateId, MailCc = dto.MailCc, MailCcDeliveryType = dto.MailCcDeliveryType, MailBcc = dto.MailBcc, MailBccDeliveryType = dto.MailBccDeliveryType, ReplyTo = dto.ReplyTo, CorrelationKey = dto.CorrelationKey, Payload = dto.Payload, AttachmentId = dto.AttachmentId, ContextJson = dto.ContextJson, }; _logger.LogInformation( "CorrespondenceActionHandler: dispatching to underlying ActionType={Type} | ActionRunId={RunId} SendTo={SendTo} AttachmentId={AttachmentId}", targetActionType, dto.ActionRunId, dto.SendTo ?? "(empty)", dto.AttachmentId); return await handler.HandleAsync(delegateDto, ct).ConfigureAwait(false); } private int ReadMode(ActionEventDto dto) { try { if (dto.Payload.ValueKind == JsonValueKind.Object && dto.Payload.TryGetProperty("Mode", out var modeEl) && modeEl.TryGetInt32(out var mode)) { return mode; } } catch (Exception ex) { _logger.LogWarning(ex, "CorrespondenceActionHandler: failed to read Mode from payload — defaulting to Mail | ActionRunId={RunId}", dto.ActionRunId); } return 1; // default: mail } } }