using System.Text.Json; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using SPMS.Application.DTOs.Webhook; using SPMS.Application.Interfaces; using SPMS.Domain.Entities; using SPMS.Domain.Enums; using SPMS.Domain.Interfaces; namespace SPMS.Infrastructure.Webhook; public class WebhookService : IWebhookService { private readonly IServiceProvider _serviceProvider; private readonly IHttpClientFactory _httpClientFactory; private readonly ILogger _logger; private const int MaxRetries = 3; private const int RetryDelaySeconds = 30; private const int TimeoutSeconds = 10; public WebhookService( IServiceProvider serviceProvider, IHttpClientFactory httpClientFactory, ILogger logger) { _serviceProvider = serviceProvider; _httpClientFactory = httpClientFactory; _logger = logger; } public Task SendAsync(long serviceId, WebhookEvent eventType, object data) { _ = Task.Run(() => SendWithRetryAsync(serviceId, eventType, data)); return Task.CompletedTask; } private async Task SendWithRetryAsync(long serviceId, WebhookEvent eventType, object data) { using var scope = _serviceProvider.CreateScope(); var serviceRepo = scope.ServiceProvider.GetRequiredService(); var webhookLogRepo = scope.ServiceProvider.GetRequiredService(); var unitOfWork = scope.ServiceProvider.GetRequiredService(); var service = await serviceRepo.GetByIdAsync(serviceId); if (service == null || string.IsNullOrEmpty(service.WebhookUrl)) return; var eventName = EventTypeToString(eventType); if (!IsEventSubscribed(service, eventName)) return; var payload = new WebhookPayloadDto { EventType = eventName, Timestamp = DateTime.UtcNow, ServiceCode = service.ServiceCode, Data = data }; var jsonPayload = JsonSerializer.Serialize(payload); var client = _httpClientFactory.CreateClient("Webhook"); client.Timeout = TimeSpan.FromSeconds(TimeoutSeconds); int? responseCode = null; string? responseBody = null; var success = false; for (var attempt = 1; attempt <= MaxRetries; attempt++) { try { var content = new StringContent(jsonPayload, System.Text.Encoding.UTF8, "application/json"); var response = await client.PostAsync(service.WebhookUrl, content); responseCode = (int)response.StatusCode; responseBody = await response.Content.ReadAsStringAsync(); if (response.IsSuccessStatusCode) { success = true; _logger.LogInformation("Webhook sent successfully to {Url} (attempt {Attempt})", service.WebhookUrl, attempt); break; } _logger.LogWarning("Webhook failed with status {StatusCode} (attempt {Attempt}/{MaxRetries})", responseCode, attempt, MaxRetries); } catch (Exception ex) { responseBody = ex.Message; _logger.LogWarning(ex, "Webhook request failed (attempt {Attempt}/{MaxRetries})", attempt, MaxRetries); } if (attempt < MaxRetries) await Task.Delay(TimeSpan.FromSeconds(RetryDelaySeconds)); } var log = new WebhookLog { ServiceId = serviceId, WebhookUrl = service.WebhookUrl, EventType = eventType, Payload = jsonPayload, Status = success ? WebhookStatus.Success : WebhookStatus.Failed, ResponseCode = responseCode, ResponseBody = responseBody?.Length > 2000 ? responseBody[..2000] : responseBody, SentAt = DateTime.UtcNow }; await webhookLogRepo.AddAsync(log); await unitOfWork.SaveChangesAsync(); } private static bool IsEventSubscribed(Service service, string eventName) { if (string.IsNullOrEmpty(service.WebhookEvents)) return false; try { var events = JsonSerializer.Deserialize>(service.WebhookEvents); return events?.Contains(eventName) == true; } catch { return false; } } private static string EventTypeToString(WebhookEvent eventType) { return eventType switch { WebhookEvent.PushSent => "push_sent", WebhookEvent.PushFailed => "push_failed", WebhookEvent.PushClicked => "push_clicked", _ => eventType.ToString().ToLowerInvariant() }; } }