using FrameworkDAL.CustomCode.EventRule; using FrameworkDAL.DTO.EventRule; using GB5Shared.DTO.Framework.Login; using GB5Shared.QueryExecutor; using GB5Shared.RuleEngine; using Microsoft.Extensions.Logging; using System; using System.Text.Json; using System.Threading; using System.Threading.Tasks; namespace FrameworkBLL.EventRule { public sealed class EventRuleBLL : IEventRuleBLL { private readonly IEventRuleDAL _dal; private readonly IQueryExecutor _qe; private readonly IConditionEvaluator _conditionEvaluator; private readonly ILogger _logger; public EventRuleBLL( IEventRuleDAL dal, IQueryExecutor qe, IConditionEvaluator conditionEvaluator, ILogger logger) { _dal = dal; _qe = qe; _conditionEvaluator = conditionEvaluator; _logger = logger; } public async Task GetEventRuleAsync( int actionId, LoginDTO login, CancellationToken ct = default) { var dto = await _dal.GetByIdAsync(actionId, login, ct).ConfigureAwait(false); return JsonSerializer.Serialize(dto); } public async Task GetListByEventTypeAsync( int eventTypeId, LoginDTO login, CancellationToken ct = default) { var list = await _dal.GetListByEventTypeAsync(eventTypeId, login, ct).ConfigureAwait(false); return JsonSerializer.Serialize(list); } public async Task GetSelectListAsync( LoginDTO login, CancellationToken ct = default) { var list = await _dal.GetSelectListAsync(login, ct).ConfigureAwait(false); return JsonSerializer.Serialize(list); } public async Task SaveEventRuleAsync( EventRuleDTO dto, LoginDTO login, CancellationToken ct = default) { ValidateConditionExpression(dto.ConditionExpression); dto.ActionId = await _dal.GetNextActionIdAsync(login, ct).ConfigureAwait(false); var etaId = await _dal.GetNextEtaIdAsync(login, ct).ConfigureAwait(false); var detailId = await _dal.GetNextDetailIdAsync(login, ct).ConfigureAwait(false); var tx = await _qe.BeginTransactionAsync(login).ConfigureAwait(false); try { await _dal.InsertActionAsync(dto, login, tx, ct).ConfigureAwait(false); await _dal.InsertEventTypeActionAsync(etaId, dto, login, tx, ct).ConfigureAwait(false); await _dal.InsertDetailAsync(detailId, etaId, dto, login, tx, ct).ConfigureAwait(false); await _qe.CommitAsync(tx).ConfigureAwait(false); _logger.LogInformation( "EventRuleBLL: saved ActionId={ActionId} EventTypeId={EventTypeId} User={UserId}", dto.ActionId, dto.EventTypeId, login.UserId); return JsonSerializer.Serialize(new { dto.ActionId }); } catch { await _qe.RollbackAsync(tx).ConfigureAwait(false); throw; } } public async Task UpdateEventRuleAsync( EventRuleDTO dto, LoginDTO login, CancellationToken ct = default) { ValidateConditionExpression(dto.ConditionExpression); var tx = await _qe.BeginTransactionAsync(login).ConfigureAwait(false); try { await _dal.UpdateActionAsync(dto, login, tx, ct).ConfigureAwait(false); await _dal.UpdateDetailSlNoAsync(dto.ActionId, dto.SlNo, login, tx, ct).ConfigureAwait(false); await _qe.CommitAsync(tx).ConfigureAwait(false); _logger.LogInformation( "EventRuleBLL: updated ActionId={ActionId} User={UserId}", dto.ActionId, login.UserId); return JsonSerializer.Serialize(new { dto.ActionId }); } catch { await _qe.RollbackAsync(tx).ConfigureAwait(false); throw; } } public async Task DeleteEventRuleAsync( int actionId, LoginDTO login, CancellationToken ct = default) { var tx = await _qe.BeginTransactionAsync(login).ConfigureAwait(false); try { await _dal.DeactivateDetailAsync(actionId, login, tx, ct).ConfigureAwait(false); await _dal.DeactivateActionAsync(actionId, login.UserId, login, tx, ct).ConfigureAwait(false); await _qe.CommitAsync(tx).ConfigureAwait(false); _logger.LogInformation( "EventRuleBLL: deactivated ActionId={ActionId} User={UserId}", actionId, login.UserId); return JsonSerializer.Serialize(new { ActionId = actionId, Deleted = true }); } catch { await _qe.RollbackAsync(tx).ConfigureAwait(false); throw; } } // ── Helpers ─────────────────────────────────────────────────────────── /// /// Validates ConditionExpression syntax by doing a dry-run evaluation against a dummy message. /// Throws ArgumentException on parse failure so the caller returns a 400. /// private void ValidateConditionExpression(string? expression) { if (string.IsNullOrWhiteSpace(expression)) return; // Dry-run: a parse error surfaces immediately // _conditionEvaluator returns true on unknown path (fail-open), but throws on invalid syntax var dummy = new GB5Shared.DTO.PubSub.OutboxEventMessage(); try { _conditionEvaluator.Evaluate(expression, dummy); } catch (ArgumentException ex) { throw new ArgumentException( $"Invalid ConditionExpression: {ex.Message}", nameof(expression), ex); } } } }