using GB5Shared.DirectAction; using GB5Shared.DTO.DirectAction; using FrameworkBLL.DirectAction; using FrameworkDAL.CustomCode.DirectAction; using FrameworkDAL.DTO.DirectAction; using FrameworkDAL.DTO.EIPConversation; using GB5Shared.DTO.Framework.Login; using Microsoft.Extensions.Logging; using System; using System.Globalization; using System.Threading; using System.Threading.Tasks; namespace FrameworkBLL.EIPConversation.EIPHandlers.EIPEngine.ActionHandler { // ============================================================ // DirectActionExecuteActionHandler — ActionCode = "DIRECTACTION_EXECUTE". // // The completion hook for the DirectAction NeedsInput=1 conversational // handoff: the final step of a DirectAction-driven flow is a CAPABILITY // step with CapabilityCode="DIRECTACTION_EXECUTE", which EIPCapabilityEngine // dispatches here (via IEIPActionExecutor's exact-ActionCode match) once the // flow has finished collecting whatever input it needed. // // Pulls the DirectAction context that DirectActionConversationBLL seeded // into the EIP session's Variables (carried automatically turn-to-turn via // ContextJson) back out of context.Payload, records the token as used HERE // — the actual moment of effect, mirroring ActionExecuteEndpoint/ // ActionSubmitEndpoint's own one-time-use recording — then calls the same // IGenericApiDirectActionHandler the direct (NeedsInput=0) path uses. // ============================================================ public class DirectActionExecuteActionHandler : IEIPActionHandler { public string ActionCode => "DIRECTACTION_EXECUTE"; private readonly IGenericApiDirectActionHandler _directActionHandler; private readonly IDirectActionTokenDAL _tokenDAL; private readonly ILogger _logger; public DirectActionExecuteActionHandler( IGenericApiDirectActionHandler directActionHandler, IDirectActionTokenDAL tokenDAL, ILogger logger) { _directActionHandler = directActionHandler ?? throw new ArgumentNullException(nameof(directActionHandler)); _tokenDAL = tokenDAL ?? throw new ArgumentNullException(nameof(tokenDAL)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public bool CanHandle(string actionCode) => string.Equals(actionCode, ActionCode, StringComparison.OrdinalIgnoreCase); public async Task HandleAsync( EIPActionContextDTO context, LoginDTO loginDTO, CancellationToken ct = default) { if (!TryGetString(context, "_DirectAction.ActionCode", out var directActionCode) || !TryGetInt(context, "_DirectAction.ContextId", out var contextId) || !TryGetInt(context, "_DirectAction.AssigneeUserId", out var assigneeUserId) || !TryGetString(context, "_DirectAction.TokenHash", out var tokenHash) || !TryGetString(context, "_DirectAction.TokenExpiresAt", out var tokenExpiresAtRaw) || !TryGetInt(context, "_DirectAction.TenantId", out var tenantId) || !TryGetString(context, "_DirectAction.FrameworkBaseUrl", out var frameworkBaseUrl)) { _logger.LogError( "DIRECTACTION_EXECUTE: missing DirectAction context in flow Variables | ConversationId={ConversationId}", context.ConversationId); return new EIPActionResultDTO { IsSuccess = false, Message = "Internal error: DirectAction context was lost.", ErrorMessage = "Missing _DirectAction.* variables" }; } var tokenExpiresAt = DateTime.Parse(tokenExpiresAtRaw, null, DateTimeStyles.RoundtripKind); // Collect whatever the flow gathered as the "remarks" equivalent — flows built for // this handoff should store their final collected value under the "Remarks" key, // same PAYLOADTEMPLATE {Remarks} placeholder the static remarks form already uses. TryGetString(context, "Remarks", out var remarks); try { await _tokenDAL.InsertUsedAsync(new DirectActionTokenDTO { TokenHash = tokenHash, ContextId = contextId, ActionCode = directActionCode, AssigneeUserId = assigneeUserId, TenantId = tenantId, ExpiresAt = tokenExpiresAt, UsedAt = DateTime.UtcNow, Status = 1, Remarks = remarks }, loginDTO, ct).ConfigureAwait(false); } catch (Exception ex) { // Duplicate key (already recorded) means this token was already executed — // do not call the API a second time. _logger.LogWarning(ex, "DIRECTACTION_EXECUTE: token already recorded as used — skipping duplicate execution | ActionCode={ActionCode} ContextId={ContextId}", directActionCode, contextId); return new EIPActionResultDTO { IsSuccess = false, Message = "This action has already been processed." }; } var result = await _directActionHandler.ExecuteAsync( directActionCode, contextId, assigneeUserId, remarks, loginDTO, frameworkBaseUrl, ct) .ConfigureAwait(false); return new EIPActionResultDTO { IsSuccess = result.Success, Message = result.Success ? $"Action '{directActionCode}' for item #{contextId} has been processed successfully." : $"The action could not be completed: {result.ErrorMessage}", ErrorMessage = result.Success ? null : result.ErrorMessage }; } private static bool TryGetString(EIPActionContextDTO context, string key, out string value) { value = string.Empty; if (context.Payload.TryGetValue(key, out var raw) && raw is not null) { value = raw.ToString() ?? string.Empty; return !string.IsNullOrWhiteSpace(value); } return false; } private static bool TryGetInt(EIPActionContextDTO context, string key, out int value) { value = 0; return TryGetString(context, key, out var raw) && int.TryParse(raw, out value); } } }