using System.Text.RegularExpressions;

namespace Inspectron.Epson.Templates.Interpreter;

public class ExpressionEvaluator
{
    private static readonly Regex ComparisonRegex = new(
        @"^(.+?)\s*(==|!=|>=|<=|>|<)\s*(.+)$",
        RegexOptions.Compiled);

    public bool Evaluate(string expression, DataContext context)
    {
        expression = expression.Trim();

        if (string.IsNullOrEmpty(expression))
        {
            return false;
        }

        // Handle negation: !property
        if (expression.StartsWith("!"))
        {
            var inner = expression[1..].Trim();
            return !Evaluate(inner, context);
        }

        // Handle comparison operators
        var match = ComparisonRegex.Match(expression);
        if (match.Success)
        {
            var left = match.Groups[1].Value.Trim();
            var op = match.Groups[2].Value;
            var right = match.Groups[3].Value.Trim();

            return EvaluateComparison(left, op, right, context);
        }

        // Handle logical AND: property1 && property2
        if (expression.Contains("&&"))
        {
            var parts = expression.Split("&&", 2);
            return Evaluate(parts[0], context) && Evaluate(parts[1], context);
        }

        // Handle logical OR: property1 || property2
        if (expression.Contains("||"))
        {
            var parts = expression.Split("||", 2);
            return Evaluate(parts[0], context) || Evaluate(parts[1], context);
        }

        // Simple truthy check
        return context.IsTruthy(expression);
    }

    private bool EvaluateComparison(string left, string op, string right, DataContext context)
    {
        var leftValue = ResolveValue(left, context);
        var rightValue = ResolveValue(right, context);

        // Handle null comparisons
        if (leftValue == null && rightValue == null)
        {
            return op == "==" || op == ">=" || op == "<=";
        }

        if (leftValue == null || rightValue == null)
        {
            return op switch
            {
                "==" => false,
                "!=" => true,
                _ => false
            };
        }

        // Try numeric comparison first
        if (TryGetNumeric(leftValue, out var leftNum) && TryGetNumeric(rightValue, out var rightNum))
        {
            return op switch
            {
                "==" => Math.Abs(leftNum - rightNum) < 0.0001,
                "!=" => Math.Abs(leftNum - rightNum) >= 0.0001,
                ">" => leftNum > rightNum,
                "<" => leftNum < rightNum,
                ">=" => leftNum >= rightNum,
                "<=" => leftNum <= rightNum,
                _ => false
            };
        }

        // Fall back to string comparison
        var leftStr = leftValue.ToString() ?? "";
        var rightStr = rightValue.ToString() ?? "";

        return op switch
        {
            "==" => leftStr.Equals(rightStr, StringComparison.OrdinalIgnoreCase),
            "!=" => !leftStr.Equals(rightStr, StringComparison.OrdinalIgnoreCase),
            ">" => string.Compare(leftStr, rightStr, StringComparison.OrdinalIgnoreCase) > 0,
            "<" => string.Compare(leftStr, rightStr, StringComparison.OrdinalIgnoreCase) < 0,
            ">=" => string.Compare(leftStr, rightStr, StringComparison.OrdinalIgnoreCase) >= 0,
            "<=" => string.Compare(leftStr, rightStr, StringComparison.OrdinalIgnoreCase) <= 0,
            _ => false
        };
    }

    private object? ResolveValue(string expression, DataContext context)
    {
        expression = expression.Trim();

        // Check for quoted string literal
        if ((expression.StartsWith("\"") && expression.EndsWith("\"")) ||
            (expression.StartsWith("'") && expression.EndsWith("'")))
        {
            return expression[1..^1];
        }

        // Check for numeric literal
        if (double.TryParse(expression, out var num))
        {
            return num;
        }

        // Check for boolean literal
        if (expression.Equals("true", StringComparison.OrdinalIgnoreCase))
        {
            return true;
        }
        if (expression.Equals("false", StringComparison.OrdinalIgnoreCase))
        {
            return false;
        }

        // Check for null literal
        if (expression.Equals("null", StringComparison.OrdinalIgnoreCase))
        {
            return null;
        }

        // Resolve as path
        return context.GetValue(expression);
    }

    private bool TryGetNumeric(object? value, out double result)
    {
        result = 0;

        if (value == null) return false;

        if (value is double d)
        {
            result = d;
            return true;
        }

        if (value is long l)
        {
            result = l;
            return true;
        }

        if (value is int i)
        {
            result = i;
            return true;
        }

        if (value is string s && double.TryParse(s, out result))
        {
            return true;
        }

        return false;
    }
}
