using FrameworkDAL.DTO.MessageHub.MessageHubGenerator; using GB5Shared.DTO.Framework.Login; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Polly; using Polly.Retry; using System; using System.Collections.Generic; using System.Net.Http; using System.Net; using System.Text; using System.Threading; using System.Threading.Tasks; namespace FrameworkBLL.MessageHub.MessageHubPlatforms { public class WhatsAppPlatform : IMessageHubPlatform.IWhatsAppPlatform { private readonly ILogger _logger; private readonly IHttpClientFactory _httpClientFactory; private readonly CelitixSettings _celitixSettings; private readonly MetaSettings _metaSettings; private readonly AsyncRetryPolicy _retryPolicy; public WhatsAppPlatform( IHttpClientFactory httpClientFactory, ILogger logger, IOptions celitixSettings, IOptions metaSettings) { _httpClientFactory = httpClientFactory; _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _celitixSettings = celitixSettings?.Value ?? throw new ArgumentNullException(nameof(celitixSettings)); _metaSettings = metaSettings?.Value ?? throw new ArgumentNullException(nameof(metaSettings)); // Retry policy for transient errors _retryPolicy = Policy .Handle() .OrResult(r => !r.IsSuccessStatusCode) .WaitAndRetryAsync(3, retry => TimeSpan.FromSeconds(Math.Pow(2, retry))); _logger.LogInformation( "[WhatsAppPlatform] Initialized | CelitixEnabled={CelitixEnabled} | MetaEnabled={MetaEnabled}", _celitixSettings.Enable, _metaSettings.Enable); } // ============================================================ // SEND TEMPLATE MESSAGE // ============================================================ public async Task SendWhatsappTemplate(MessageHubDTO dto, LoginDTO login, CancellationToken ct) { if (dto == null) return Fail("Template DTO is null"); _logger.LogInformation( "[Template] Start | Recipient={Recipient} | TemplateName={TemplateName} | User={User}", dto.Recipient, dto.TemplateName, login?.UserName); if (string.IsNullOrWhiteSpace(dto.Recipient)) return Fail("Recipient is required"); dto.Parameters ??= new Dictionary(); dto.DefaultEmptyParametersToNone(); if (string.Equals(_celitixSettings.Enable, "Y", StringComparison.OrdinalIgnoreCase)) return await TemplateViaCelitix(dto, ct); if (string.Equals(_metaSettings.Enable, "Y", StringComparison.OrdinalIgnoreCase)) return await TemplateViaMeta(dto, ct); return Fail("No platform enabled."); } // ============================================================ // SEND CONVERSATION MESSAGE // ============================================================ public async Task SendWhatsappConversation(MessageHubDTO dto, LoginDTO login, CancellationToken ct) { if (dto == null) return Fail("DTO is null"); if (string.IsNullOrWhiteSpace(dto.Recipient)) return Fail("Recipient is required"); if (string.IsNullOrWhiteSpace(dto.MessageText)) return Fail("MessageText is required"); _logger.LogInformation("[Conversation] Start | Recipient={Recipient}", dto.Recipient); if (string.Equals(_celitixSettings.Enable, "Y", StringComparison.OrdinalIgnoreCase)) return await ConversationViaCelitix(dto, ct); if (string.Equals(_metaSettings.Enable, "Y", StringComparison.OrdinalIgnoreCase)) return await ConversationViaMeta(dto, ct); return Fail("No platform enabled."); } // ============================================================ // CELITIX TEMPLATE (DYNAMIC PARAMETERS) // ============================================================ private async Task TemplateViaCelitix(MessageHubDTO dto, CancellationToken ct) { try { if (dto == null) return Fail("DTO is null"); if (string.IsNullOrWhiteSpace(dto.Recipient)) return Fail("Recipient is required"); if (string.IsNullOrWhiteSpace(dto.TemplateName)) return Fail("TemplateName is required for Celitix template message."); var finalTemplateName = dto.TemplateName.Trim(); var finalLanguageCode = string.IsNullOrWhiteSpace(dto.LanguageCode) ? "en" : dto.LanguageCode.Trim(); _logger.LogInformation( "[Celitix] Preparing Template | Recipient={Recipient} | TemplateName={TemplateName} | Language={Language}", dto.Recipient, finalTemplateName, finalLanguageCode); dto.Parameters ??= new Dictionary(); var bodyParams = new JArray(); int index = 0; // Add all parameters dynamically foreach (var val in dto.Parameters.Values) { var safeValue = val?.ToString() ?? "NONE"; _logger.LogInformation("[Celitix] Parameter[{Index}] = {Value}", index, safeValue); bodyParams.Add(new JObject { ["type"] = "text", ["text"] = safeValue }); index++; } _logger.LogInformation("[Celitix] Total Parameters Sent={Count}", bodyParams.Count); var payload = new JObject { ["messaging_product"] = "whatsapp", ["recipient_type"] = "individual", ["to"] = dto.Recipient, ["type"] = "template", ["template"] = new JObject { ["name"] = finalTemplateName, ["language"] = new JObject { ["code"] = finalLanguageCode }, ["components"] = new JArray { new JObject { ["type"] = "body", ["parameters"] = bodyParams } } } }; _logger.LogInformation("[Celitix] Final Payload Prepared:\n{Payload}", payload.ToString(Formatting.Indented)); return await SendCelitixHttp(payload, dto.Recipient, ct); } catch (Exception ex) { _logger.LogError(ex, "[Celitix] TemplateViaCelitix Exception"); return Fail(ex.Message); } } // ============================================================ // META TEMPLATE (DYNAMIC) // ============================================================ private async Task TemplateViaMeta(MessageHubDTO dto, CancellationToken ct) { var templateName = dto.TemplateName ?? _metaSettings.DefaultTemplateName; var language = dto.LanguageCode ?? _metaSettings.DefaultLanguageCode ?? "en_US"; var bodyParams = new JArray(); foreach (var val in dto.Parameters.Values) { bodyParams.Add(new JObject { ["type"] = "text", ["text"] = val?.ToString() ?? "NONE" }); } var payload = new JObject { ["messaging_product"] = "whatsapp", ["to"] = dto.Recipient, ["type"] = "template", ["template"] = new JObject { ["name"] = templateName, ["language"] = new JObject { ["code"] = language }, ["components"] = new JArray { new JObject { ["type"] = "body", ["parameters"] = bodyParams } } } }; return await SendMetaHttp(payload, dto.Recipient, ct); } // ============================================================ // CELITIX CONVERSATION // ============================================================ private async Task ConversationViaCelitix(MessageHubDTO dto, CancellationToken ct) { var payload = new JObject { ["messaging_product"] = "whatsapp", ["recipient_type"] = "individual", ["to"] = dto.Recipient, ["type"] = "text", ["text"] = new JObject { ["body"] = dto.GetSafeMessageText() } }; return await SendCelitixHttp(payload, dto.Recipient, ct); } // ============================================================ // META CONVERSATION // ============================================================ private async Task ConversationViaMeta(MessageHubDTO dto, CancellationToken ct) { var payload = new JObject { ["messaging_product"] = "whatsapp", ["to"] = dto.Recipient, ["type"] = "text", ["text"] = new JObject { ["body"] = dto.GetSafeMessageText() } }; return await SendMetaHttp(payload, dto.Recipient, ct); } // ============================================================ // SEND CELITIX HTTP (POST LIKE POSTMAN) // ============================================================ private async Task SendCelitixHttp(JObject payload, string recipient, CancellationToken ct) { try { // Ensure TLS 1.2 ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; // Create HttpClient from factory var client = _httpClientFactory.CreateClient(); client.BaseAddress = new Uri(_celitixSettings.BaseUrl); // Clear default headers client.DefaultRequestHeaders.Clear(); // Add headers dynamically from appsettings if (!string.IsNullOrWhiteSpace(_celitixSettings.KeyHeaderName) && !string.IsNullOrWhiteSpace(_celitixSettings.Key)) { client.DefaultRequestHeaders.Add(_celitixSettings.KeyHeaderName, _celitixSettings.Key); } if (!string.IsNullOrWhiteSpace(_celitixSettings.WabaNumberHeaderName) && !string.IsNullOrWhiteSpace(_celitixSettings.WabaNumber)) { client.DefaultRequestHeaders.Add(_celitixSettings.WabaNumberHeaderName, _celitixSettings.WabaNumber); } // ===== LOG HEADERS ===== _logger.LogInformation("[Celitix] Headers being sent:"); foreach (var header in client.DefaultRequestHeaders) { _logger.LogInformation(" {Key}: {Value}", header.Key, string.Join(",", header.Value)); } // ====================== // Convert payload to JSON var json = payload.ToString(Formatting.None); _logger.LogInformation("[Celitix] Request URL={Url}", _celitixSettings.BaseUrl + _celitixSettings.MessageEndpoint); _logger.LogInformation("[Celitix] Payload={Payload}", json); // Send POST request with retry policy var response = await _retryPolicy.ExecuteAsync(() => client.PostAsync(_celitixSettings.MessageEndpoint, new StringContent(json, Encoding.UTF8, _celitixSettings.ContentType ?? "application/json"), ct)); var responseBody = await response.Content.ReadAsStringAsync(ct); _logger.LogInformation("[Celitix] Status={Status} | Response={Response}", response.StatusCode, responseBody); return new MessageHubResponseDTO { Success = response.IsSuccessStatusCode, Message = response.IsSuccessStatusCode ? "Sent via Celitix" : "Failed via Celitix", ResponseData = responseBody }; } catch (Exception ex) { _logger.LogError(ex, "[Celitix] Error"); return Fail(ex.Message); } } // ============================================================ // SEND META HTTP // ============================================================ private async Task SendMetaHttp(JObject payload, string recipient, CancellationToken ct) { try { var client = _httpClientFactory.CreateClient(); client.BaseAddress = new Uri($"https://graph.facebook.com/v18.0/{_metaSettings.PhoneNumberId}/"); client.DefaultRequestHeaders.Clear(); client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _metaSettings.AuthToken); var json = payload.ToString(Formatting.None); _logger.LogInformation("[Meta] URL={Url}", client.BaseAddress + "messages"); _logger.LogInformation("[Meta] Payload={Payload}", json); var response = await _retryPolicy.ExecuteAsync(() => client.PostAsync("messages", new StringContent(json, Encoding.UTF8, "application/json"), ct)); var responseBody = await response.Content.ReadAsStringAsync(ct); _logger.LogInformation("[Meta] Status={Status} | Response={Response}", response.StatusCode, responseBody); return new MessageHubResponseDTO { Success = response.IsSuccessStatusCode, Message = response.IsSuccessStatusCode ? "Sent via Meta" : "Failed via Meta", ResponseData = responseBody }; } catch (Exception ex) { _logger.LogError(ex, "[Meta] Error"); return Fail(ex.Message); } } private static MessageHubResponseDTO Fail(string message) => new MessageHubResponseDTO { Success = false, Message = message }; } }