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

namespace Workflow.Domain.Expressions;

/// <summary>
/// Evaluates workflow conditions expressed as JSON against a variables dictionary.
/// Supports simple comparisons, logical AND/OR combinations, nested expressions,
/// and simple text expressions like "amount > 1000".
/// </summary>
public class ConditionEvaluator
{
    /// <summary>
    /// Evaluates a condition expression against the given variables.
    /// </summary>
    /// <param name="condition">JSON condition expression or simple text expression</param>
    /// <param name="variables">Variables to evaluate against</param>
    /// <returns>True if condition passes, false otherwise. Returns true for empty/null conditions.</returns>
    public bool Evaluate(string? condition, Dictionary<string, object>? variables)
    {
        // Empty/null/whitespace condition = always pass
        if (string.IsNullOrWhiteSpace(condition))
            return true;

        // Null variables means nothing to evaluate against
        if (variables is null)
            return false;

        // Try simple text expression first (e.g., "amount > 1000")
        if (TryParseSimpleExpression(condition, out var field, out var op, out var value))
            return EvaluateSimpleExpression(field, op, value, variables);

        // Fall back to JSON evaluation
        try
        {
            using var doc = JsonDocument.Parse(condition);
            return EvaluateElement(doc.RootElement, variables);
        }
        catch (JsonException)
        {
            // Malformed JSON returns false
            return false;
        }
    }

    private static readonly Regex SimpleExpressionRegex =
        new(@"^\s*(\w+)\s*(==|!=|>=|<=|>|<|contains)\s*(.+?)\s*$", RegexOptions.Compiled);

    private bool TryParseSimpleExpression(string condition, out string field, out string op, out string value)
    {
        field = string.Empty;
        op = string.Empty;
        value = string.Empty;

        var match = SimpleExpressionRegex.Match(condition);
        if (!match.Success)
            return false;

        field = match.Groups[1].Value;
        op = match.Groups[2].Value;
        value = match.Groups[3].Value.Trim();
        return true;
    }

    private bool EvaluateSimpleExpression(string field, string op, string value, Dictionary<string, object> variables)
    {
        if (!variables.TryGetValue(field, out var rawFieldValue) || rawFieldValue is null)
            return false;

        var fieldStr = rawFieldValue.ToString() ?? string.Empty;

        return op switch
        {
            "==" => string.Equals(fieldStr, value, StringComparison.Ordinal) ||
                    TryNumericCompare(rawFieldValue, value, (a, b) => a == b),
            "!=" => !string.Equals(fieldStr, value, StringComparison.Ordinal) &&
                    !TryNumericCompare(rawFieldValue, value, (a, b) => a == b),
            ">" => TryNumericCompare(rawFieldValue, value, (a, b) => a > b),
            "<" => TryNumericCompare(rawFieldValue, value, (a, b) => a < b),
            ">=" => TryNumericCompare(rawFieldValue, value, (a, b) => a >= b),
            "<=" => TryNumericCompare(rawFieldValue, value, (a, b) => a <= b),
            "contains" => fieldStr.Contains(value, StringComparison.Ordinal),
            _ => false
        };
    }

    private static bool TryNumericCompare(object fieldValue, string conditionValue, Func<decimal, decimal, bool> comparator)
    {
        if (!TryGetDecimal(fieldValue, out var fieldNum))
            return false;

        if (!decimal.TryParse(conditionValue, NumberStyles.Number, CultureInfo.InvariantCulture, out var condNum))
            return false;

        return comparator(fieldNum, condNum);
    }

    private bool EvaluateElement(JsonElement element, Dictionary<string, object> variables)
    {
        if (element.ValueKind != JsonValueKind.Object)
            return false;

        // Check for "and" combinator
        if (element.TryGetProperty("and", out var andArray))
        {
            if (andArray.ValueKind != JsonValueKind.Array)
                return false;

            foreach (var child in andArray.EnumerateArray())
            {
                if (!EvaluateElement(child, variables))
                    return false;
            }

            return true;
        }

        // Check for "or" combinator
        if (element.TryGetProperty("or", out var orArray))
        {
            if (orArray.ValueKind != JsonValueKind.Array)
                return false;

            foreach (var child in orArray.EnumerateArray())
            {
                if (EvaluateElement(child, variables))
                    return true;
            }

            return false;
        }

        // Simple comparison: { "field": "...", "op": "...", "value": ... }
        return EvaluateComparison(element, variables);
    }

    private bool EvaluateComparison(JsonElement element, Dictionary<string, object> variables)
    {
        if (!element.TryGetProperty("field", out var fieldElement))
            return false;

        if (!element.TryGetProperty("op", out var opElement))
            return false;

        if (!element.TryGetProperty("value", out var valueElement))
            return false;

        var fieldName = fieldElement.GetString();
        var op = opElement.GetString();

        if (fieldName is null || op is null)
            return false;

        // Look up field value from variables
        if (!variables.TryGetValue(fieldName, out var rawFieldValue))
            return false;

        if (rawFieldValue is null)
            return false;

        // Apply the operator
        return ApplyOperator(op, rawFieldValue, valueElement);
    }

    private bool ApplyOperator(string op, object fieldValue, JsonElement conditionValue)
    {
        return op switch
        {
            "==" => EvaluateEquals(fieldValue, conditionValue),
            "!=" => !EvaluateEquals(fieldValue, conditionValue),
            ">" => EvaluateNumericComparison(fieldValue, conditionValue, (a, b) => a > b),
            "<" => EvaluateNumericComparison(fieldValue, conditionValue, (a, b) => a < b),
            ">=" => EvaluateNumericComparison(fieldValue, conditionValue, (a, b) => a >= b),
            "<=" => EvaluateNumericComparison(fieldValue, conditionValue, (a, b) => a <= b),
            "contains" => EvaluateContains(fieldValue, conditionValue),
            "in" => EvaluateIn(fieldValue, conditionValue),
            _ => false
        };
    }

    private bool EvaluateEquals(object fieldValue, JsonElement conditionValue)
    {
        // Try numeric comparison first
        if (TryGetDecimal(fieldValue, out var fieldNum) &&
            TryGetDecimalFromElement(conditionValue, out var condNum))
        {
            return fieldNum == condNum;
        }

        // Fall back to string comparison
        var fieldStr = fieldValue.ToString();
        var condStr = conditionValue.ValueKind == JsonValueKind.String
            ? conditionValue.GetString()
            : conditionValue.GetRawText();

        return string.Equals(fieldStr, condStr, StringComparison.Ordinal);
    }

    private bool EvaluateNumericComparison(
        object fieldValue,
        JsonElement conditionValue,
        Func<decimal, decimal, bool> comparator)
    {
        if (!TryGetDecimal(fieldValue, out var fieldNum))
            return false;

        if (!TryGetDecimalFromElement(conditionValue, out var condNum))
            return false;

        return comparator(fieldNum, condNum);
    }

    private bool EvaluateContains(object fieldValue, JsonElement conditionValue)
    {
        if (conditionValue.ValueKind != JsonValueKind.String)
            return false;

        var searchStr = conditionValue.GetString();
        if (searchStr is null)
            return false;

        var fieldStr = fieldValue.ToString() ?? string.Empty;
        return fieldStr.Contains(searchStr, StringComparison.Ordinal);
    }

    private bool EvaluateIn(object fieldValue, JsonElement conditionValue)
    {
        if (conditionValue.ValueKind != JsonValueKind.Array)
            return false;

        foreach (var item in conditionValue.EnumerateArray())
        {
            if (EvaluateEquals(fieldValue, item))
                return true;
        }

        return false;
    }

    private static bool TryGetDecimal(object value, out decimal result)
    {
        result = 0m;

        return value switch
        {
            decimal d => SetResult(d, out result),
            double db => SetResult((decimal)db, out result),
            float f => SetResult((decimal)f, out result),
            int i => SetResult((decimal)i, out result),
            long l => SetResult((decimal)l, out result),
            short s => SetResult((decimal)s, out result),
            byte b => SetResult((decimal)b, out result),
            uint ui => SetResult((decimal)ui, out result),
            ulong ul => SetResult((decimal)ul, out result),
            string str => decimal.TryParse(str, out result),
            JsonElement e => TryGetDecimalFromElement(e, out result),
            _ => false
        };
    }

    private static bool TryGetDecimalFromElement(JsonElement element, out decimal result)
    {
        result = 0m;

        if (element.ValueKind == JsonValueKind.Number)
            return element.TryGetDecimal(out result);

        if (element.ValueKind == JsonValueKind.String)
        {
            var str = element.GetString();
            return str is not null && decimal.TryParse(str, out result);
        }

        return false;
    }

    private static bool SetResult(decimal value, out decimal result)
    {
        result = value;
        return true;
    }
}
