using System.Linq.Expressions;
using System.Reflection;

namespace AyCode.Core.Serializers.Expressions;

/// <summary>
/// Rebuilds Expression trees from AcExpressionNode DTO.
/// Format-agnostic - works with any deserialized AcExpressionNode.
/// </summary>
public class AcExpressionRebuilder
{
    private readonly Dictionary<int, ParameterExpression> _parameters = new();

    /// <summary>
    /// Rebuilds an Expression from AcExpressionNode.
    /// </summary>
    public Expression Rebuild(AcExpressionNode node, Type? entityType = null)
    {
        return node.NodeType switch
        {
            ExpressionType.Lambda => RebuildLambda(node, entityType),
            ExpressionType.Parameter => RebuildParameter(node),
            ExpressionType.Constant => RebuildConstant(node),
            ExpressionType.MemberAccess => RebuildMemberAccess(node, entityType),
            ExpressionType.Call => RebuildMethodCall(node, entityType),
            ExpressionType.Conditional => RebuildConditional(node, entityType),
            ExpressionType.New => RebuildNew(node, entityType),
            ExpressionType.MemberInit => RebuildMemberInit(node, entityType),
            ExpressionType.NewArrayInit or ExpressionType.NewArrayBounds => RebuildNewArray(node, entityType),
            ExpressionType.Invoke => RebuildInvocation(node, entityType),
            ExpressionType.TypeIs or ExpressionType.TypeAs => RebuildTypeBinary(node, entityType),
            
            // Unary expressions
            ExpressionType.Not or ExpressionType.Negate or ExpressionType.NegateChecked or
            ExpressionType.Convert or ExpressionType.ConvertChecked or ExpressionType.ArrayLength or
            ExpressionType.Quote or ExpressionType.UnaryPlus
                => RebuildUnary(node, entityType),

            // Binary expressions
            ExpressionType.Add or ExpressionType.AddChecked or ExpressionType.Subtract or
            ExpressionType.SubtractChecked or ExpressionType.Multiply or ExpressionType.MultiplyChecked or
            ExpressionType.Divide or ExpressionType.Modulo or ExpressionType.Power or
            ExpressionType.And or ExpressionType.AndAlso or ExpressionType.Or or ExpressionType.OrElse or
            ExpressionType.ExclusiveOr or ExpressionType.Equal or ExpressionType.NotEqual or
            ExpressionType.LessThan or ExpressionType.LessThanOrEqual or
            ExpressionType.GreaterThan or ExpressionType.GreaterThanOrEqual or
            ExpressionType.Coalesce or ExpressionType.ArrayIndex or
            ExpressionType.LeftShift or ExpressionType.RightShift
                => RebuildBinary(node, entityType),

            _ => throw new NotSupportedException($"Expression type '{node.NodeType}' is not supported.")
        };
    }

    /// <summary>
    /// Static convenience method to rebuild an Expression from AcExpressionNode.
    /// </summary>
    public static Expression FromNode(AcExpressionNode node, Type? entityType = null)
    {
        var rebuilder = new AcExpressionRebuilder();
        return rebuilder.Rebuild(node, entityType);
    }

    /// <summary>
    /// Static convenience method to rebuild a typed Expression from AcExpressionNode.
    /// </summary>
    public static Expression<Func<TEntity, TResult>> FromNode<TEntity, TResult>(AcExpressionNode node)
    {
        var expression = FromNode(node, typeof(TEntity));
        return (Expression<Func<TEntity, TResult>>)expression;
    }

    #region Rebuild Methods

    private LambdaExpression RebuildLambda(AcExpressionNode node, Type? entityType)
    {
        // Create parameters
        var parameters = new List<ParameterExpression>();
        if (node.Parameters != null)
        {
            foreach (var paramNode in node.Parameters)
            {
                var paramType = entityType ?? ResolveType(paramNode.TypeName);
                var param = Expression.Parameter(paramType, paramNode.Name);
                _parameters[paramNode.Index] = param;
                parameters.Add(param);
                
                // Use entityType only for first parameter
                entityType = null;
            }
        }

        var body = Rebuild(node.Body!, null);
        return Expression.Lambda(body, parameters);
    }

    private ParameterExpression RebuildParameter(AcExpressionNode node)
    {
        // Try by index first
        if (node.ParameterIndex.HasValue && _parameters.TryGetValue(node.ParameterIndex.Value, out var param))
            return param;

        // Fallback: try by name (for cases where Index was not serialized, e.g., Index=0 skipped as default)
        if (!string.IsNullOrEmpty(node.ParameterName))
        {
            var byName = _parameters.Values.FirstOrDefault(p => p.Name == node.ParameterName);
            if (byName != null)
                return byName;
        }

        // Last resort: if there's only one parameter, use it
        if (_parameters.Count == 1)
            return _parameters.Values.First();

        throw new InvalidOperationException($"Parameter '{node.ParameterName}' not found. ParameterIndex: {node.ParameterIndex}");
    }

    private static ConstantExpression RebuildConstant(AcExpressionNode node)
    {
        var type = ResolveType(node.TypeName ?? "System.Object");
        
        // Use the type-safe GetDynamicValue method
        var value = node.GetValue();
        
        if (value == null)
            return Expression.Constant(null, type);

        return Expression.Constant(value, type);
    }

    private Expression RebuildMemberAccess(AcExpressionNode node, Type? entityType)
    {
        if (node.Object == null)
        {
            // Static member access
            var declaringType = ResolveType(node.DeclaringType!);
            var member = declaringType.GetMember(node.MemberName!, BindingFlags.Public | BindingFlags.Static).FirstOrDefault()
                ?? throw new InvalidOperationException($"Static member '{node.MemberName}' not found on type '{declaringType.Name}'.");
            return Expression.MakeMemberAccess(null, member);
        }

        var obj = Rebuild(node.Object, entityType);
        var memberInfo = obj.Type.GetMember(node.MemberName!, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase).FirstOrDefault()
            ?? throw new InvalidOperationException($"Member '{node.MemberName}' not found on type '{obj.Type.Name}'.");
        
        return Expression.MakeMemberAccess(obj, memberInfo);
    }

    private Expression RebuildMethodCall(AcExpressionNode node, Type? entityType)
    {
        var arguments = node.Arguments?.Select(a => Rebuild(a, entityType)).ToArray() ?? [];
        var argumentTypes = arguments.Select(a => a.Type).ToArray();

        var declaringType = node.DeclaringType != null ? ResolveType(node.DeclaringType) : null;
        var instance = node.Object != null ? Rebuild(node.Object, entityType) : null;

        MethodInfo? method = null;

        if (instance != null)
        {
            // Instance method
            method = FindMethod(instance.Type, node.MethodName!, argumentTypes, isStatic: false);
        }
        else if (declaringType != null)
        {
            // Static method (including extension methods)
            method = FindMethod(declaringType, node.MethodName!, argumentTypes, isStatic: true);
        }

        if (method == null)
            throw new InvalidOperationException($"Method '{node.MethodName}' not found.");

        // Handle generic methods
        if (method.IsGenericMethodDefinition && node.GenericArguments?.Count > 0)
        {
            var genericTypes = node.GenericArguments.Select(ResolveType).ToArray();
            method = method.MakeGenericMethod(genericTypes);
        }

        return instance != null
            ? Expression.Call(instance, method, arguments)
            : Expression.Call(method, arguments);
    }

    private Expression RebuildBinary(AcExpressionNode node, Type? entityType)
    {
        var left = Rebuild(node.Left!, entityType);
        var right = Rebuild(node.Right!, entityType);

        // Handle type mismatches (e.g., nullable comparisons)
        if (left.Type != right.Type)
        {
            if (Nullable.GetUnderlyingType(left.Type) == right.Type)
                right = Expression.Convert(right, left.Type);
            else if (Nullable.GetUnderlyingType(right.Type) == left.Type)
                left = Expression.Convert(left, right.Type);
        }

        return Expression.MakeBinary(node.NodeType, left, right);
    }

    private Expression RebuildUnary(AcExpressionNode node, Type? entityType)
    {
        var operand = Rebuild(node.Operand!, entityType);
        var type = node.TypeName != null ? ResolveType(node.TypeName) : null;

        return node.NodeType switch
        {
            ExpressionType.Convert or ExpressionType.ConvertChecked when type != null
                => Expression.Convert(operand, type),
            _ => Expression.MakeUnary(node.NodeType, operand, type)
        };
    }

    private Expression RebuildConditional(AcExpressionNode node, Type? entityType)
    {
        var test = Rebuild(node.Test!, entityType);
        var ifTrue = Rebuild(node.IfTrue!, entityType);
        var ifFalse = Rebuild(node.IfFalse!, entityType);
        return Expression.Condition(test, ifTrue, ifFalse);
    }

    private Expression RebuildNew(AcExpressionNode node, Type? entityType)
    {
        var type = ResolveType(node.TypeName!);
        var args = node.ConstructorArguments?.Select(a => Rebuild(a, entityType)).ToArray() ?? [];
        var argTypes = args.Select(a => a.Type).ToArray();
        var ctor = type.GetConstructor(argTypes) 
            ?? throw new InvalidOperationException($"Constructor not found for type '{type.Name}'.");
        return Expression.New(ctor, args);
    }

    private Expression RebuildMemberInit(AcExpressionNode node, Type? entityType)
    {
        var type = ResolveType(node.TypeName!);
        var args = node.ConstructorArguments?.Select(a => Rebuild(a, entityType)).ToArray() ?? [];
        var argTypes = args.Select(a => a.Type).ToArray();
        var ctor = type.GetConstructor(argTypes) ?? type.GetConstructor(Type.EmptyTypes)
            ?? throw new InvalidOperationException($"Constructor not found for type '{type.Name}'.");
        
        var newExpr = Expression.New(ctor, args);
        var bindings = node.MemberBindings?.Select(b => RebuildMemberBinding(b, type, entityType)).ToList() 
            ?? [];
        
        return Expression.MemberInit(newExpr, bindings);
    }

    private MemberBinding RebuildMemberBinding(MemberBindingNode node, Type declaringType, Type? entityType)
    {
        var member = declaringType.GetMember(node.MemberName, BindingFlags.Public | BindingFlags.Instance).FirstOrDefault()
            ?? throw new InvalidOperationException($"Member '{node.MemberName}' not found on type '{declaringType.Name}'.");

        return node.BindingType switch
        {
            MemberBindingType.Assignment => Expression.Bind(member, Rebuild(node.Expression!, entityType)),
            MemberBindingType.MemberBinding => Expression.MemberBind(member,
                node.Bindings?.Select(b => RebuildMemberBinding(b, GetMemberType(member), entityType)) ?? []),
            MemberBindingType.ListBinding => Expression.ListBind(member,
                node.Initializers?.Select(args => Expression.ElementInit(
                    GetAddMethod(GetMemberType(member)),
                    args.Select(a => Rebuild(a, entityType)))) ?? []),
            _ => throw new NotSupportedException($"Binding type '{node.BindingType}' is not supported.")
        };
    }

    private Expression RebuildNewArray(AcExpressionNode node, Type? entityType)
    {
        var elementType = ResolveType(node.TypeName!).GetElementType() 
            ?? throw new InvalidOperationException("Cannot determine array element type.");
        var elements = node.Elements?.Select(e => Rebuild(e, entityType)).ToArray() ?? [];
        return Expression.NewArrayInit(elementType, elements);
    }

    private Expression RebuildInvocation(AcExpressionNode node, Type? entityType)
    {
        var expression = Rebuild(node.Object!, entityType);
        var arguments = node.Arguments?.Select(a => Rebuild(a, entityType)).ToArray() ?? [];
        return Expression.Invoke(expression, arguments);
    }

    private Expression RebuildTypeBinary(AcExpressionNode node, Type? entityType)
    {
        var expression = Rebuild(node.Operand!, entityType);
        var type = ResolveType(node.TypeName!);
        return node.NodeType == ExpressionType.TypeIs
            ? Expression.TypeIs(expression, type)
            : Expression.TypeAs(expression, type);
    }

    #endregion

    #region Helper Methods

    private static MethodInfo? FindMethod(Type type, string methodName, Type[] argumentTypes, bool isStatic)
    {
        var bindingFlags = BindingFlags.Public | (isStatic ? BindingFlags.Static : BindingFlags.Instance);
        
        // Try exact match first
        var method = type.GetMethod(methodName, bindingFlags, null, argumentTypes, null);
        if (method != null) return method;

        // Try finding by name and parameter count
        var candidates = type.GetMethods(bindingFlags)
            .Where(m => m.Name == methodName && m.GetParameters().Length == argumentTypes.Length)
            .ToList();

        return candidates.FirstOrDefault();
    }

    private static Type GetMemberType(MemberInfo member) => member switch
    {
        PropertyInfo pi => pi.PropertyType,
        FieldInfo fi => fi.FieldType,
        _ => throw new InvalidOperationException($"Cannot get type for member '{member.Name}'.")
    };

    private static MethodInfo GetAddMethod(Type collectionType)
    {
        return collectionType.GetMethod("Add") 
            ?? throw new InvalidOperationException($"Add method not found on type '{collectionType.Name}'.");
    }

    private static Type ResolveType(string typeName)
    {
        var type = typeName switch
        {
            "System.String" or "string" => typeof(string),
            "System.Int32" or "int" => typeof(int),
            "System.Int64" or "long" => typeof(long),
            "System.Int16" or "short" => typeof(short),
            "System.Byte" or "byte" => typeof(byte),
            "System.Boolean" or "bool" => typeof(bool),
            "System.Double" or "double" => typeof(double),
            "System.Single" or "float" => typeof(float),
            "System.Decimal" or "decimal" => typeof(decimal),
            "System.DateTime" => typeof(DateTime),
            "System.DateTimeOffset" => typeof(DateTimeOffset),
            "System.DateOnly" => typeof(DateOnly),
            "System.TimeOnly" => typeof(TimeOnly),
            "System.TimeSpan" => typeof(TimeSpan),
            "System.Guid" => typeof(Guid),
            "System.Object" or "object" => typeof(object),
            _ => Type.GetType(typeName)
        };

        if (type == null && typeName.Contains("Nullable"))
        {
            var match = System.Text.RegularExpressions.Regex.Match(typeName, @"System\.Nullable`1\[\[(.+?),");
            if (match.Success)
            {
                var underlyingType = ResolveType(match.Groups[1].Value);
                type = typeof(Nullable<>).MakeGenericType(underlyingType);
            }
        }

        return type ?? throw new InvalidOperationException($"Cannot resolve type '{typeName}'.");
    }

    #endregion
}
