using FrameworkBLL.EIPConversation.EIPHandlers.UrlResolver;
using FrameworkDAL.DTO.EIPConversation;
using GB5Shared.DTO.Framework.Login;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
namespace FrameworkBLL.EIPConversation.EIPHandlers.EIPEngine.ActionHandler
{
///
/// Generic CRUD capability handler that calls any internal GB5 service endpoint.
///
/// context.Payload must contain:
/// "Method" — HTTP verb: GET | POST | PUT | DELETE (default POST)
/// "Endpoint" — relative path, e.g. "/Leave/SaveLeave"
/// "Body" — optional JSON body string (may use {ContextKey} substitutions already resolved upstream)
///
/// On success the raw response body is stored in context.Payload["CrudResult"].
///
/// ActionCode: "CRUD_OPERATION"
/// Example flow step:
/// {
/// "StepType": "CAPABILITY",
/// "CapabilityCode": "CRUD_OPERATION",
/// "MessageTemplate": "Leave submitted successfully.",
/// "NextStepCode": "END"
/// }
///
public class CrudOperationHandler : IEIPActionHandler
{
public string ActionCode => "CRUD_OPERATION";
private readonly IHttpClientFactory _httpClientFactory;
private readonly IConfiguration _config;
private readonly IEIPUrlResolver _urlResolver;
private readonly ILogger _logger;
public CrudOperationHandler(
IHttpClientFactory httpClientFactory,
IConfiguration config,
IEIPUrlResolver urlResolver,
ILogger logger)
{
_httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory));
_config = config ?? throw new ArgumentNullException(nameof(config));
_urlResolver = urlResolver ?? throw new ArgumentNullException(nameof(urlResolver));
_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)
{
if (context.Payload == null || context.Payload.Count == 0)
return new EIPActionResultDTO { IsSuccess = false, Message = "Payload is empty." };
if (!context.Payload.TryGetValue("Endpoint", out var endpointObj) ||
string.IsNullOrWhiteSpace(endpointObj?.ToString()))
{
return new EIPActionResultDTO { IsSuccess = false, Message = "Payload missing required key 'Endpoint'." };
}
var method = context.Payload.TryGetValue("Method", out var m) ? m?.ToString() ?? "POST" : "POST";
var body = context.Payload.TryGetValue("Body", out var b) ? b?.ToString() ?? "{}" : "{}";
string fullUrl;
// New-style: ServicePrefix + PathTemplate in payload → route through YARP gateway
if (context.Payload.TryGetValue("ServicePrefix", out var spObj) &&
!string.IsNullOrWhiteSpace(spObj?.ToString()))
{
var baseUri = loginDTO.BaseUri?.TrimEnd('/') ?? string.Empty;
fullUrl = _urlResolver.ResolveUrlFromPayload(context.Payload, new System.Collections.Generic.Dictionary(), baseUri);
}
else
{
// Legacy: Endpoint relative to SysJobSettings:ServiceBaseUrl
var relativeEndpoint = endpointObj.ToString()!.TrimStart('/');
var baseUrl = _config["SysJobSettings:ServiceBaseUrl"]?.TrimEnd('/');
if (string.IsNullOrWhiteSpace(baseUrl))
{
_logger.LogError("CrudOperationHandler: SysJobSettings:ServiceBaseUrl is not configured.");
return new EIPActionResultDTO { IsSuccess = false, Message = "Service base URL is not configured." };
}
fullUrl = $"{baseUrl}/{relativeEndpoint}";
}
_logger.LogInformation(
"CrudOperationHandler: {Method} {Url} | ConversationId={Id}", method, fullUrl, context.ConversationId);
try
{
var client = _httpClientFactory.CreateClient("gb5-internal");
HttpResponseMessage response;
if (method.Equals("GET", StringComparison.OrdinalIgnoreCase))
{
response = await client.GetAsync(fullUrl, ct).ConfigureAwait(false);
}
else if (method.Equals("DELETE", StringComparison.OrdinalIgnoreCase))
{
response = await client.DeleteAsync(fullUrl, ct).ConfigureAwait(false);
}
else
{
using var content = new StringContent(body, Encoding.UTF8, "application/json");
response = method.Equals("PUT", StringComparison.OrdinalIgnoreCase)
? await client.PutAsync(fullUrl, content, ct).ConfigureAwait(false)
: await client.PostAsync(fullUrl, content, ct).ConfigureAwait(false);
}
var responseBody = await response.Content.ReadAsStringAsync(ct).ConfigureAwait(false);
if (!response.IsSuccessStatusCode)
{
_logger.LogWarning(
"CrudOperationHandler: {Status} from {Url} | Body={Body}",
(int)response.StatusCode, fullUrl, responseBody);
return new EIPActionResultDTO
{
IsSuccess = false,
Message = $"Service returned {(int)response.StatusCode}.",
ErrorMessage = responseBody
};
}
context.Payload["CrudResult"] = responseBody;
_logger.LogInformation(
"CrudOperationHandler: OK | {Method} {Url}", method, fullUrl);
return new EIPActionResultDTO
{
IsSuccess = true,
Message = "Operation completed successfully.",
Data = responseBody
};
}
catch (OperationCanceledException)
{
_logger.LogWarning("CrudOperationHandler: cancelled | Url={Url}", fullUrl);
return new EIPActionResultDTO { IsSuccess = false, Message = "Operation cancelled." };
}
catch (Exception ex)
{
_logger.LogError(ex, "CrudOperationHandler: exception | Url={Url}", fullUrl);
return new EIPActionResultDTO { IsSuccess = false, Message = ex.Message, ErrorMessage = ex.ToString() };
}
}
}
}