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
}
}
}