using System.Text.RegularExpressions; using Microsoft.Extensions.Logging; using OptimAI.BRE.RuleEngine.Domain; using OptimAI.BRE.Shared.Domain; namespace OptimAI.BRE.RuleEngine.Application; public sealed class ConditionEvaluator : IConditionEvaluator { private readonly IDynamicFieldResolver _fieldResolver; private readonly ILogger _logger; public ConditionEvaluator(IDynamicFieldResolver fieldResolver, ILogger logger) { _fieldResolver = fieldResolver; _logger = logger; } public bool Evaluate(ConditionNode condition, DynamicDataContext context) { try { var actualValue = ResolveValue(condition.Field!, context); var expectedValue = ResolveExpectedValue(condition, context); return condition.Operator switch { ComparisonOperator.Equals => AreEqual(actualValue, expectedValue), ComparisonOperator.NotEquals => !AreEqual(actualValue, expectedValue), ComparisonOperator.GreaterThan => Compare(actualValue, expectedValue) > 0, ComparisonOperator.GreaterThanOrEqual => Compare(actualValue, expectedValue) >= 0, ComparisonOperator.LessThan => Compare(actualValue, expectedValue) < 0, ComparisonOperator.LessThanOrEqual => Compare(actualValue, expectedValue) <= 0, ComparisonOperator.Between => EvaluateBetween(actualValue, condition, context), ComparisonOperator.NotBetween => !EvaluateBetween(actualValue, condition, context), ComparisonOperator.In => EvaluateIn(actualValue, condition.Value), ComparisonOperator.NotIn => !EvaluateIn(actualValue, condition.Value), ComparisonOperator.Contains => EvaluateContains(actualValue, expectedValue), ComparisonOperator.NotContains => !EvaluateContains(actualValue, expectedValue), ComparisonOperator.StartsWith => EvaluateStartsWith(actualValue, expectedValue), ComparisonOperator.EndsWith => EvaluateEndsWith(actualValue, expectedValue), ComparisonOperator.IsNull => actualValue == null, ComparisonOperator.IsNotNull => actualValue != null, ComparisonOperator.IsTrue => IsTruthy(actualValue), ComparisonOperator.IsFalse => !IsTruthy(actualValue), ComparisonOperator.Regex => EvaluateRegex(actualValue, expectedValue?.ToString()), _ => false }; } catch (Exception ex) { _logger.LogWarning(ex, "Condition evaluation failed for field {Field}", condition.Field); return false; } } private object? ResolveValue(string field, DynamicDataContext context) { return _fieldResolver.TryResolve(field, context, out var value) ? value : null; } private object? ResolveExpectedValue(ConditionNode condition, DynamicDataContext context) { return condition.ValueType switch { ValueType.Field when condition.ReferenceField != null => ResolveValue(condition.ReferenceField, context), ValueType.Literal => condition.Value, _ => condition.Value }; } private static bool AreEqual(object? left, object? right) { if (left == null && right == null) return true; if (left == null || right == null) return false; if (TryToDecimal(left, out var ld) && TryToDecimal(right, out var rd)) return ld == rd; return left.ToString()?.Equals(right.ToString(), StringComparison.OrdinalIgnoreCase) ?? false; } private static int Compare(object? left, object? right) { if (left == null && right == null) return 0; if (left == null) return -1; if (right == null) return 1; if (TryToDecimal(left, out var ld) && TryToDecimal(right, out var rd)) return ld.CompareTo(rd); if (left is DateTime dl && right is DateTime dr) return dl.CompareTo(dr); return string.Compare(left.ToString(), right.ToString(), StringComparison.OrdinalIgnoreCase); } private bool EvaluateBetween(object? actual, ConditionNode condition, DynamicDataContext context) { var value1 = ResolveExpectedValue(condition, context); var value2 = condition.Value2 != null ? (object)condition.Value2 : null; if (value1 == null || value2 == null) return false; return Compare(actual, value1) >= 0 && Compare(actual, value2) <= 0; } private static bool EvaluateIn(object? actual, object? values) { if (actual == null || values == null) return false; IEnumerable list = values switch { IEnumerable e => e, string s => s.Split(',').Select(x => (object)x.Trim()), _ => new[] { values } }; return list.Any(v => AreEqual(actual, v)); } private static bool EvaluateContains(object? actual, object? expected) { if (actual == null || expected == null) return false; if (actual is IEnumerable list) return list.Any(item => AreEqual(item, expected)); return actual.ToString()?.Contains(expected.ToString() ?? "", StringComparison.OrdinalIgnoreCase) ?? false; } private static bool EvaluateStartsWith(object? actual, object? expected) => actual?.ToString()?.StartsWith(expected?.ToString() ?? "", StringComparison.OrdinalIgnoreCase) ?? false; private static bool EvaluateEndsWith(object? actual, object? expected) => actual?.ToString()?.EndsWith(expected?.ToString() ?? "", StringComparison.OrdinalIgnoreCase) ?? false; private static bool EvaluateRegex(object? actual, string? pattern) { if (actual == null || pattern == null) return false; return Regex.IsMatch(actual.ToString()!, pattern, RegexOptions.IgnoreCase); } private static bool IsTruthy(object? value) => value switch { bool b => b, decimal d => d != 0, int i => i != 0, string s => !string.IsNullOrEmpty(s) && s != "0" && s.ToLower() != "false", null => false, _ => true }; private static bool TryToDecimal(object value, out decimal result) { result = 0; return value switch { decimal d => (result = d) == d, double d => (result = (decimal)d) == result, float f => (result = (decimal)f) == result, int i => (result = i) == i, long l => (result = l) == l, string s => decimal.TryParse(s, out result), _ => false }; } }