using System.Diagnostics; using System.Text; using GB5Shared.DTO.Framework.Login; using GB5Shared.DTO.GOP; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Polly; using Polly.Retry; namespace GB5Shared.GOP.ExecuteAsync { public class ExecutionEngine : IExecutionEngine { private static readonly ActivitySource ActivitySource = new("GOP.Execution"); private readonly IHttpClientFactory _httpFactory; private readonly ILogger _logger; private readonly AsyncRetryPolicy _retryPolicy; public ExecutionEngine( IHttpClientFactory httpFactory, ILogger logger) { _httpFactory = httpFactory; _logger = logger; _retryPolicy = Policy .Handle() .OrResult(r => !r.IsSuccessStatusCode) .WaitAndRetryAsync( 3, retry => TimeSpan.FromSeconds(Math.Pow(2, retry)), (result, delay, retryCount, ctx) => { logger.LogWarning( "Retry {Retry} after {Delay}s", retryCount, delay.TotalSeconds); }); } public async Task ExecuteAsync( ExecutionDTO execution, LoginDTO loginDTO, CancellationToken cancellationToken = default) { using var activity = ActivitySource.StartActivity("Execution"); try { execution.Status = ExecutionStatus.Running; execution.StartedAt = DateTime.UtcNow; var client = _httpFactory.CreateClient("GOP"); var responseContent = await ExecuteNodeAsync( client, execution, loginDTO, cancellationToken); execution.Status = ExecutionStatus.Completed; execution.CompletedAt = DateTime.UtcNow; _logger.LogInformation( "Execution completed {ExecutionId}", execution.ExecutionId); return new ExecutionResultDTO { ExecutionId = execution.ExecutionId, Status = execution.Status.ToString(), Response = responseContent }; } catch (Exception ex) { execution.Status = ExecutionStatus.Failed; execution.ErrorMessage = ex.Message; execution.CompletedAt = DateTime.UtcNow; activity?.SetStatus(ActivityStatusCode.Error, ex.Message); _logger.LogError( ex, "Execution failed {ExecutionId}", execution.ExecutionId); throw; } } private async Task ExecuteNodeAsync( HttpClient client, ExecutionDTO execution, LoginDTO loginDTO, CancellationToken cancellationToken) { var method = new HttpMethod(execution.Method ?? "POST"); var response = await _retryPolicy.ExecuteAsync(async ct => { using var request = new HttpRequestMessage(method, execution.Url); // ---------- Headers ---------- if (execution.Headers != null) { foreach (var header in execution.Headers) { request.Headers.TryAddWithoutValidation( header.Key, header.Value); } } // ---------- Login Header ---------- if (loginDTO != null) { request.Headers.TryAddWithoutValidation( "Login", JsonConvert.SerializeObject(loginDTO)); } // ---------- Body ---------- if (!string.IsNullOrWhiteSpace(execution.PayloadJson) && method != HttpMethod.Get) { request.Content = new StringContent( execution.PayloadJson, Encoding.UTF8, "application/json"); } return await client.SendAsync(request, ct); }, cancellationToken); var responseContent = await response.Content.ReadAsStringAsync(cancellationToken); _logger.LogInformation( "Response {StatusCode} → {Url}", response.StatusCode, execution.Url); response.EnsureSuccessStatusCode(); return responseContent; } } }