using FrameworkBLL.DirectAction; using FrameworkBLL.EIPConversation.EIPHandlers.EIPConversationBLL; using FrameworkBLL.MessageHubService.TemplateApproval; using FrameworkDAL.CustomCode.DirectAction; using FrameworkDAL.CustomCode.MailTemplate; using FrameworkDAL.DTO.EIPConversation; using FrameworkDAL.DTO.MessageHub; using FrameworkDAL.DTO.MessageHubService.UpdateHandleStatus; using GB5Shared.DTO.Framework.Login; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace FrameworkBLL.MessageHubWebhook { public class MessageHubWebhookBLL : IMessageHubWebhookBLL { private readonly ITemplateApprovalBLL _templateApprovalBLL; private readonly IMailTemplateDAL _mailTemplateDAL; private readonly IEIPConversationBLL _eipConversationBLL; private readonly IDirectActionConfigDAL _configDAL; private readonly IGenericApiDirectActionHandler _directActionHandler; private readonly ILogger _logger; public MessageHubWebhookBLL( ITemplateApprovalBLL templateApprovalBLL, IMailTemplateDAL mailTemplateDAL, IEIPConversationBLL eipConversationBLL, IDirectActionConfigDAL configDAL, IGenericApiDirectActionHandler directActionHandler, ILogger logger) { _templateApprovalBLL = templateApprovalBLL ?? throw new ArgumentNullException(nameof(templateApprovalBLL)); _mailTemplateDAL = mailTemplateDAL ?? throw new ArgumentNullException(nameof(mailTemplateDAL)); _eipConversationBLL = eipConversationBLL ?? throw new ArgumentNullException(nameof(eipConversationBLL)); _configDAL = configDAL ?? throw new ArgumentNullException(nameof(configDAL)); _directActionHandler = directActionHandler ?? throw new ArgumentNullException(nameof(directActionHandler)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public async Task MessageHubWebhook( MessageHubWebhookDTO webhookDTO, LoginDTO loginDTO, string frameworkBaseUrl = "", CancellationToken ct = default) { if (webhookDTO?.Messages == null || webhookDTO.Messages.Count == 0) { _logger.LogWarning("Webhook payload contains no messages."); return "No actionable messages found"; } _logger.LogInformation( "Received Webhook | TotalMessages={Count}", webhookDTO.Messages.Count); foreach (var message in webhookDTO.Messages) { try { _logger.LogInformation( "Processing message | From={From} | Type={Type}", message.From, message.Type); if (IsButtonMessage(message, out var payload)) { await ProcessButtonMessageAsync(message, payload, loginDTO, frameworkBaseUrl, ct); } else { await RouteTextToEipAsync(message, loginDTO, ct); } } catch (Exception ex) { _logger.LogError(ex, "Error processing message | From={From}", message.From); } } _logger.LogInformation("Webhook processing completed | TotalMessages={Count}", webhookDTO.Messages.Count); return "Webhook processed successfully"; } // ───────────────────────────────────────────────────────────── // Button-press path // ───────────────────────────────────────────────────────────── private async Task ProcessButtonMessageAsync( MessageWebhookDTO message, string payload, LoginDTO loginDTO, string frameworkBaseUrl, CancellationToken ct) { _logger.LogInformation( "Button payload detected | From={From} | Payload={Payload}", message.From, payload); // ── Primary path: legacy ACCEPT_/REJECT_/FORWARD_ template approval ── var dto = await TryBuildTemplateApprovalDTOAsync(message, payload, loginDTO, ct); if (dto != null) { _logger.LogInformation( "Calling TemplateApprovalBLL | From={From} | Action={Action}", message.From, dto.ApprovalStatus); await _templateApprovalBLL.UpdateTemplateApproval(dto, loginDTO, dto.Platform); _logger.LogInformation( "Button message processed | From={From} | Action={Action}", message.From, dto.ApprovalStatus); return; } // ── Fallback: generalized DirectAction routing via MDIRECTACTIONDETAIL ─ await TryRouteDirectActionAsync(payload, loginDTO, message.From, frameworkBaseUrl, ct); } private async Task TryRouteDirectActionAsync( string payload, LoginDTO loginDTO, string senderPhone, string frameworkBaseUrl, CancellationToken ct) { try { var allDetails = await _configDAL.GetAllActiveDetailsAsync(loginDTO, ct) .ConfigureAwait(false); var matched = allDetails.FirstOrDefault(d => !string.IsNullOrWhiteSpace(d.ButtonPayloadPrefix) && payload.StartsWith(d.ButtonPayloadPrefix + "_", StringComparison.OrdinalIgnoreCase)); if (matched == null) { _logger.LogWarning( "Button payload did not match any template approval action or DirectAction prefix | From={From} | Payload={Payload}", senderPhone, payload); return; } if (!int.TryParse(payload[(matched.ButtonPayloadPrefix.Length + 1)..], out var contextId)) { _logger.LogWarning( "Could not parse ContextId from DirectAction payload | From={From} | Payload={Payload} | Prefix={Prefix}", senderPhone, payload, matched.ButtonPayloadPrefix); return; } _logger.LogInformation( "Routing to DirectAction | From={From} | ActionCode={Code} | ContextId={Ctx}", senderPhone, matched.ActionCode, contextId); var result = await _directActionHandler .ExecuteAsync(matched.ActionCode, contextId, loginDTO.UserId, null, loginDTO, frameworkBaseUrl, ct) .ConfigureAwait(false); if (!result.Success) _logger.LogWarning( "DirectAction execution failed | From={From} | ActionCode={Code} | ContextId={Ctx} | Error={Error}", senderPhone, matched.ActionCode, contextId, result.ErrorMessage); else _logger.LogInformation( "DirectAction executed | From={From} | ActionCode={Code} | ContextId={Ctx}", senderPhone, matched.ActionCode, contextId); } catch (Exception ex) { _logger.LogError(ex, "DirectAction routing failed | From={From} | Payload={Payload}", senderPhone, payload); } } // ───────────────────────────────────────────────────────────── // Text message path — route to EIP conversation engine (Bug 3) // ───────────────────────────────────────────────────────────── private async Task RouteTextToEipAsync( MessageWebhookDTO message, LoginDTO loginDTO, CancellationToken ct) { var textBody = message.Text?.Body ?? string.Empty; if (string.IsNullOrWhiteSpace(textBody)) { _logger.LogInformation("Skipping empty text message | From={From}", message.From); return; } _logger.LogInformation( "Routing text message to EIP | From={From} | Text={Text}", message.From, textBody); await _eipConversationBLL.HandleIncomingConversationAsync( new EIPConversationDTO { TenantId = loginDTO.ClientId, ChannelType = EIPChannelType.WhatsApp, UserIdentifier = message.From, Message = textBody, Platform = "WhatsApp", Channel = "WHATSAPP" }, loginDTO, ct).ConfigureAwait(false); } // ───────────────────────────────────────────────────────────── // Helpers // ───────────────────────────────────────────────────────────── private bool IsButtonMessage(MessageWebhookDTO message, out string payload) { payload = null; if (!string.Equals(message.Type, "button", StringComparison.OrdinalIgnoreCase)) return false; if (message.Button == null) { _logger.LogWarning("Button object is missing | From={From}", message.From); return false; } payload = message.Button.Payload?.Trim().ToUpperInvariant(); return !string.IsNullOrWhiteSpace(payload); } /// /// Parses button payload in the format ACTION_TemplateId_ObjectId /// (e.g. "ACCEPT_1042_8800") and resolves FlowCode from MMAILTEMPLATE. /// Also handles legacy single-word payloads (APPROVE / REJECT / FORWARD). /// private async Task TryBuildTemplateApprovalDTOAsync( MessageWebhookDTO message, string payload, LoginDTO loginDTO, CancellationToken ct) { ApprovalAction action; int status; int templateId = 0; int objectId = 0; // ── Parse structured payload ACTION_TemplateId_ObjectId ────────── var parts = payload.Split('_'); var actionWord = parts[0]; switch (actionWord) { case "APPROVE": case "ACCEPT": action = ApprovalAction.Accept; status = 1; break; case "REJECT": action = ApprovalAction.Reject; status = 2; break; case "FORWARD": case "RETURN": action = ApprovalAction.Forward; status = 3; break; default: return null; } if (parts.Length >= 3) { int.TryParse(parts[1], out templateId); int.TryParse(parts[2], out objectId); } // ── Resolve FlowCode from DB ────────────────────────────────────── string? flowCode = null; if (templateId > 0) { flowCode = await _mailTemplateDAL .GetFlowCodeByTemplateIdAsync(templateId, loginDTO, ct) .ConfigureAwait(false); if (string.IsNullOrWhiteSpace(flowCode)) _logger.LogWarning( "No FlowCode found for TemplateId={TemplateId} — approval will proceed without flow routing", templateId); } return new TemplateApprovalDTO { TenantId = loginDTO.ClientId, FlowCode = flowCode ?? string.Empty, TemplateID = templateId, ObjectId = objectId, ApprovalStatus = (FrameworkDAL.DTO.MessageHubService.UpdateHandleStatus.ApprovalAction)(ApprovalAction)action, Status = status, UserId = message.From, Recipient = message.From, RawPayload = payload, Platform = "WhatsApp", Choice = message.Button?.Text ?? actionWord }; } } }