using FrameworkBLL.EIPConversation.EIPHandlers.EIPConversationEngine.EIPRoutingRepository; using FrameworkDAL.DTO.EIPConversation; using Microsoft.Extensions.Logging; using System; using System.Linq; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; namespace FrameworkBLL.EIPConversation.EIPHandlers.EIPEngine.RoutingEngine { public class EIPRoutingEngine : IEIPRoutingEngine { private readonly IEIPRoutingRepository _routingRepository; private readonly ILogger _logger; public EIPRoutingEngine( IEIPRoutingRepository routingRepository, ILogger logger) { _routingRepository = routingRepository ?? throw new ArgumentNullException(nameof(routingRepository)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public async Task ResolveRoutingAsync( EIPExecutionContextDTO context, GB5Shared.DTO.Framework.Login.LoginDTO loginDTO, CancellationToken cancellationToken) { if (context == null) throw new ArgumentNullException(nameof(context)); _logger.LogInformation( "PHASE2 ROUTING START | Tenant={Tenant} | Channel={Channel} | FlowBefore={Flow} | NormalizedMessage={Message}", context.TenantId, context.ChannelType, context.FlowCode, context.NormalizedMessage); try { var rules = await _routingRepository.GetActiveRoutingRulesAsync( context.TenantId, context.ChannelType.ToString().ToUpperInvariant(), loginDTO, cancellationToken); if (!rules.Any()) { _logger.LogWarning( "PHASE2 ROUTING | No routing rules configured | Tenant={Tenant} | Channel={Channel}", context.TenantId, context.ChannelType); return new EIPRoutingResultDTO { IsMatched = false }; } _logger.LogInformation( "PHASE2 ROUTING | Evaluating {Count} rule(s)", rules.Count); foreach (var rule in rules.OrderBy(r => r.PriorityOrder)) { var trimmedPattern = rule.Pattern?.Trim(); _logger.LogInformation( "PHASE2 ROUTING | Checking RuleId={RuleId} | MatchType={MatchType} | Pattern='{Pattern}' | Priority={Priority}", rule.RoutingRuleId, rule.MatchType, trimmedPattern, rule.PriorityOrder); var isMatched = IsMatch(rule, context.NormalizedMessage); _logger.LogInformation( "PHASE2 ROUTING | RuleId={RuleId} | Matched={Matched}", rule.RoutingRuleId, isMatched); if (isMatched) { _logger.LogInformation( "PHASE2 ROUTING MATCHED | RuleId={RuleId} | TargetFlow={Flow}", rule.RoutingRuleId, rule.TargetFlowCode); return new EIPRoutingResultDTO { IsMatched = true, TargetFlowCode = rule.TargetFlowCode, MatchedRuleCode = rule.RoutingRuleId.ToString(), MatchType = rule.MatchType.ToString() }; } } _logger.LogInformation( "PHASE2 ROUTING | No rule matched | Tenant={Tenant} | Message='{Message}'", context.TenantId, context.NormalizedMessage); return new EIPRoutingResultDTO { IsMatched = false }; } catch (Exception ex) { _logger.LogError(ex, "PHASE2 ROUTING ERROR | Tenant={Tenant} | Message='{Message}'", context.TenantId, context.NormalizedMessage); throw; } } private bool IsMatch(EIPRoutingRuleDTO rule, string message) { if (string.IsNullOrWhiteSpace(message) || rule == null) return false; // Null/empty MATCHVALUE = catch-all rule — matches any message. // These are always inserted with a high PRIORITY number so they // are checked last, after all specific rules. if (string.IsNullOrWhiteSpace(rule.Pattern)) return true; message = message.Trim(); var pattern = rule.Pattern.Trim(); try { if (string.IsNullOrWhiteSpace(pattern)) return false; return rule.MatchType switch { 1 => string.Equals( message, pattern, StringComparison.OrdinalIgnoreCase), 2 => message.StartsWith( pattern, StringComparison.OrdinalIgnoreCase), 3 => message.IndexOf( pattern, StringComparison.OrdinalIgnoreCase) >= 0, 4 => Regex.IsMatch( message, pattern, RegexOptions.IgnoreCase), _ => false }; } catch (Exception ex) { _logger.LogError(ex, "Pattern matching error | RuleId={RuleId} | Pattern='{Pattern}' | Message='{Message}'", rule.RoutingRuleId, pattern, message); return false; } } } }