using System.Globalization;
using System.Text.Json;
using System.Text.RegularExpressions;

namespace Inspectron.Epson.TemplateEngine.DataBinding;

public class ExpressionEvaluator
{
    private static readonly Regex ExpressionPattern = new(@"\{\{(.+?)\}\}", RegexOptions.Compiled);

    private readonly DataContext _context;

    public ExpressionEvaluator(DataContext context)
    {
        _context = context;
    }

    public string Evaluate(string template)
    {
        if (string.IsNullOrEmpty(template))
            return "";

        return ExpressionPattern.Replace(template, match =>
        {
            var expression = match.Groups[1].Value.Trim();
            return ResolveExpression(expression);
        });
    }

    private string ResolveExpression(string expression)
    {
        // Loop variables: $index, $first, $last
        if (expression.StartsWith("$"))
        {
            var loopVar = _context.GetLoopVariable(expression);
            return loopVar?.ToString() ?? "";
        }

        // Format string: PropertyName:format
        string? format = null;
        var colonIndex = expression.IndexOf(':');
        if (colonIndex > 0)
        {
            format = expression[(colonIndex + 1)..];
            expression = expression[..colonIndex];
        }

        var element = _context.Resolve(expression);
        if (element == null)
            return "";

        if (format != null)
            return FormatValue(element.Value, format);

        return DataContext.GetStringValue(element.Value);
    }

    private static string FormatValue(JsonElement element, string format)
    {
        if (element.ValueKind == JsonValueKind.String)
        {
            var str = element.GetString();
            if (str != null && DateTime.TryParse(str, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var dt))
            {
                return dt.ToString(format, CultureInfo.InvariantCulture);
            }
            return str ?? "";
        }

        if (element.ValueKind == JsonValueKind.Number)
        {
            if (element.TryGetDecimal(out var d))
                return d.ToString(format, CultureInfo.InvariantCulture);
        }

        return DataContext.GetStringValue(element);
    }

    public bool EvaluateCondition(string test)
    {
        if (string.IsNullOrWhiteSpace(test))
            return false;

        // Negation
        bool negate = false;
        var expr = test.Trim();
        if (expr.StartsWith("!"))
        {
            negate = true;
            expr = expr[1..].Trim();
        }

        // Loop variables: $first, $last
        if (expr.StartsWith("$"))
        {
            var loopVar = _context.GetLoopVariable(expr);
            bool loopResult = loopVar is bool b ? b : loopVar != null;
            return negate ? !loopResult : loopResult;
        }

        // Comparison operators
        var comparisonOps = new[] { "==", "!=", ">=", "<=", ">", "<" };
        foreach (var op in comparisonOps)
        {
            var parts = SplitComparison(expr, op);
            if (parts != null)
            {
                bool compResult = EvaluateComparison(parts.Value.left, op, parts.Value.right);
                return negate ? !compResult : compResult;
            }
        }

        // Truthiness check - property exists and has a value
        var result = IsTruthy(expr);
        return negate ? !result : result;
    }

    private (string left, string right)? SplitComparison(string expr, string op)
    {
        var idx = expr.IndexOf(op, StringComparison.Ordinal);
        if (idx < 0) return null;

        // Make sure we don't confuse == with = or != with !
        if (op == "=" && idx > 0 && expr[idx - 1] == '!') return null;
        if (op == ">" && idx > 0 && (expr[idx - 1] == '>' || expr[idx - 1] == '<')) return null;

        var left = expr[..idx].Trim();
        var right = expr[(idx + op.Length)..].Trim();

        if (string.IsNullOrEmpty(left) || string.IsNullOrEmpty(right))
            return null;

        return (left, right);
    }

    private bool EvaluateComparison(string left, string op, string right)
    {
        var leftVal = ResolveComparisonValue(left);
        var rightVal = ResolveComparisonValue(right);

        // Try numeric comparison
        if (decimal.TryParse(leftVal, CultureInfo.InvariantCulture, out var leftNum)
            && decimal.TryParse(rightVal, CultureInfo.InvariantCulture, out var rightNum))
        {
            return op switch
            {
                "==" => leftNum == rightNum,
                "!=" => leftNum != rightNum,
                ">" => leftNum > rightNum,
                "<" => leftNum < rightNum,
                ">=" => leftNum >= rightNum,
                "<=" => leftNum <= rightNum,
                _ => false
            };
        }

        // String comparison
        int cmp = string.Compare(leftVal, rightVal, StringComparison.OrdinalIgnoreCase);
        return op switch
        {
            "==" => cmp == 0,
            "!=" => cmp != 0,
            ">" => cmp > 0,
            "<" => cmp < 0,
            ">=" => cmp >= 0,
            "<=" => cmp <= 0,
            _ => false
        };
    }

    private string ResolveComparisonValue(string value)
    {
        // Quoted string literal
        if ((value.StartsWith("'") && value.EndsWith("'")) ||
            (value.StartsWith("\"") && value.EndsWith("\"")))
        {
            return value[1..^1];
        }

        // Numeric literal
        if (decimal.TryParse(value, CultureInfo.InvariantCulture, out _))
        {
            return value;
        }

        // Loop variable
        if (value.StartsWith("$"))
        {
            var loopVar = _context.GetLoopVariable(value);
            return loopVar?.ToString() ?? "";
        }

        // Property resolution
        var element = _context.Resolve(value);
        if (element == null)
            return "";

        return DataContext.GetStringValue(element.Value);
    }

    private bool IsTruthy(string expression)
    {
        var element = _context.Resolve(expression);
        if (element == null)
            return false;

        return element.Value.ValueKind switch
        {
            JsonValueKind.Null => false,
            JsonValueKind.Undefined => false,
            JsonValueKind.False => false,
            JsonValueKind.String => !string.IsNullOrEmpty(element.Value.GetString()),
            JsonValueKind.Number => element.Value.GetDouble() != 0,
            JsonValueKind.Array => element.Value.GetArrayLength() > 0,
            _ => true
        };
    }
}
