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

namespace AyCode.Core.Serializers.Expressions;

/// <summary>
/// Converts Expression trees to AcExpressionNode DTO.
/// Format-agnostic - produces a DTO that can be serialized with any serializer.
/// </summary>
public class AcExpressionConverter : ExpressionVisitor
{
    private readonly Dictionary<ParameterExpression, int> _parameterIndexes = new();
    private int _nextParameterIndex;
    
    // Stack to collect converted nodes
    private readonly Stack<AcExpressionNode> _nodeStack = new();

    /// <summary>
    /// Converts an Expression to an AcExpressionNode DTO.
    /// </summary>
    public AcExpressionNode Convert(Expression expression)
    {
        _nodeStack.Clear();
        _parameterIndexes.Clear();
        _nextParameterIndex = 0;
        
        VisitAndConvert(expression);
        
        return _nodeStack.Count != 1 
            ? throw new InvalidOperationException($"Expected 1 node on stack, found {_nodeStack.Count}") 
            : _nodeStack.Pop();
    }

    /// <summary>
    /// Static convenience method to convert an Expression to AcExpressionNode.
    /// </summary>
    public static AcExpressionNode ToNode(Expression expression)
    {
        var converter = new AcExpressionConverter();
        return converter.Convert(expression);
    }

    /// <summary>
    /// Static convenience method to convert a typed Expression to AcExpressionNode.
    /// </summary>
    public static AcExpressionNode ToNode<TEntity, TResult>(Expression<Func<TEntity, TResult>> expression)
    {
        return ToNode((Expression)expression);
    }

    private void VisitAndConvert(Expression expression)
    {
        Visit(expression);
    }

    protected override Expression VisitBinary(BinaryExpression node)
    {
        VisitAndConvert(node.Left);
        var left = _nodeStack.Pop();
        
        VisitAndConvert(node.Right);
        var right = _nodeStack.Pop();
        
        _nodeStack.Push(new AcExpressionNode
        {
            NodeType = node.NodeType,
            TypeName = node.Type.FullName,
            Left = left,
            Right = right
        });
        
        return node;
    }

    protected override Expression VisitUnary(UnaryExpression node)
    {
        VisitAndConvert(node.Operand);
        var operand = _nodeStack.Pop();
        
        _nodeStack.Push(new AcExpressionNode
        {
            NodeType = node.NodeType,
            TypeName = node.Type.FullName,
            Operand = operand
        });
        
        return node;
    }

    protected override Expression VisitLambda<T>(Expression<T> node)
    {
        // Register parameters with indexes
        var parameters = new List<ParameterNode>();
        foreach (var param in node.Parameters)
        {
            var index = _nextParameterIndex++;
            _parameterIndexes[param] = index;
            parameters.Add(new ParameterNode
            {
                Name = param.Name ?? $"p{index}",
                TypeName = param.Type.FullName ?? param.Type.Name,
                Index = index
            });
        }

        VisitAndConvert(node.Body);
        var body = _nodeStack.Pop();

        _nodeStack.Push(new AcExpressionNode
        {
            NodeType = ExpressionType.Lambda,
            TypeName = node.Type.FullName,
            Body = body,
            Parameters = parameters
        });
        
        return node;
    }

    protected override Expression VisitParameter(ParameterExpression node)
    {
        var index = _parameterIndexes.GetValueOrDefault(node, -1);
        
        _nodeStack.Push(new AcExpressionNode
        {
            NodeType = ExpressionType.Parameter,
            TypeName = node.Type.FullName,
            ParameterName = node.Name,
            ParameterIndex = index
        });
        
        return node;
    }

    protected override Expression VisitMember(MemberExpression node)
    {
        // Check if this is a closure variable access (captured variable)
        if (IsClosureAccess(node))
        {
            var value = EvaluateClosureValue(node);
            _nodeStack.Push(CreateConstantNode(value, node.Type));
            return node;
        }

        AcExpressionNode? objectNode = null;
        if (node.Expression != null)
        {
            VisitAndConvert(node.Expression);
            objectNode = _nodeStack.Pop();
        }

        _nodeStack.Push(new AcExpressionNode
        {
            NodeType = ExpressionType.MemberAccess,
            TypeName = node.Type.FullName,
            MemberName = node.Member.Name,
            Object = objectNode,
            DeclaringType = node.Member.DeclaringType?.FullName
        });
        
        return node;
    }

    protected override Expression VisitConstant(ConstantExpression node)
    {
        _nodeStack.Push(CreateConstantNode(node.Value, node.Type));
        return node;
    }

    /// <summary>
    /// Creates a constant node with type-safe value storage.
    /// </summary>
    private static AcExpressionNode CreateConstantNode(object? value, Type type)
    {
        // IQueryable sources cannot be serialized
        if (value is IQueryable)
        {
            return new AcExpressionNode
            {
                NodeType = ExpressionType.Constant,
                TypeName = type.FullName,
                ValueType = ConstantValueType.Null
            };
        }

        var node = new AcExpressionNode
        {
            NodeType = ExpressionType.Constant,
            TypeName = type.FullName
        };

        node.SetValue(value);
        return node;
    }

    protected override Expression VisitMethodCall(MethodCallExpression node)
    {
        AcExpressionNode? objectNode = null;
        if (node.Object != null)
        {
            VisitAndConvert(node.Object);
            objectNode = _nodeStack.Pop();
        }

        var arguments = new List<AcExpressionNode>();
        foreach (var arg in node.Arguments)
        {
            VisitAndConvert(arg);
            arguments.Add(_nodeStack.Pop());
        }

        var genericArgs = node.Method.IsGenericMethod
            ? node.Method.GetGenericArguments().Select(t => t.AssemblyQualifiedName ?? t.FullName ?? t.Name).ToList()
            : null;

        _nodeStack.Push(new AcExpressionNode
        {
            NodeType = ExpressionType.Call,
            TypeName = node.Type.AssemblyQualifiedName ?? node.Type.FullName,
            MethodName = node.Method.Name,
            Object = objectNode,
            Arguments = arguments,
            // Use AssemblyQualifiedName for full type resolution (EF Core Include, linq2db LoadWith, etc.)
            DeclaringType = node.Method.DeclaringType?.AssemblyQualifiedName ?? node.Method.DeclaringType?.FullName,
            GenericArguments = genericArgs
        });
        
        return node;
    }

    protected override Expression VisitConditional(ConditionalExpression node)
    {
        VisitAndConvert(node.Test);
        var test = _nodeStack.Pop();
        
        VisitAndConvert(node.IfTrue);
        var ifTrue = _nodeStack.Pop();
        
        VisitAndConvert(node.IfFalse);
        var ifFalse = _nodeStack.Pop();

        _nodeStack.Push(new AcExpressionNode
        {
            NodeType = ExpressionType.Conditional,
            TypeName = node.Type.FullName,
            Test = test,
            IfTrue = ifTrue,
            IfFalse = ifFalse
        });
        
        return node;
    }

    protected override Expression VisitNew(NewExpression node)
    {
        var arguments = new List<AcExpressionNode>();
        foreach (var arg in node.Arguments)
        {
            VisitAndConvert(arg);
            arguments.Add(_nodeStack.Pop());
        }

        _nodeStack.Push(new AcExpressionNode
        {
            NodeType = ExpressionType.New,
            TypeName = node.Type.FullName,
            ConstructorArguments = arguments
        });
        
        return node;
    }

    protected override Expression VisitMemberInit(MemberInitExpression node)
    {
        var arguments = new List<AcExpressionNode>();
        foreach (var arg in node.NewExpression.Arguments)
        {
            VisitAndConvert(arg);
            arguments.Add(_nodeStack.Pop());
        }

        _nodeStack.Push(new AcExpressionNode
        {
            NodeType = ExpressionType.MemberInit,
            TypeName = node.Type.FullName,
            ConstructorArguments = arguments,
            MemberBindings = node.Bindings.Select(ConvertMemberBinding).ToList()
        });
        
        return node;
    }

    protected override Expression VisitNewArray(NewArrayExpression node)
    {
        var elements = new List<AcExpressionNode>();
        foreach (var expr in node.Expressions)
        {
            VisitAndConvert(expr);
            elements.Add(_nodeStack.Pop());
        }

        _nodeStack.Push(new AcExpressionNode
        {
            NodeType = node.NodeType,
            TypeName = node.Type.FullName,
            Elements = elements
        });
        
        return node;
    }

    protected override Expression VisitTypeBinary(TypeBinaryExpression node)
    {
        VisitAndConvert(node.Expression);
        var operand = _nodeStack.Pop();

        _nodeStack.Push(new AcExpressionNode
        {
            NodeType = node.NodeType,
            TypeName = node.TypeOperand.FullName,
            Operand = operand
        });
        
        return node;
    }

    protected override Expression VisitInvocation(InvocationExpression node)
    {
        VisitAndConvert(node.Expression);
        var objectNode = _nodeStack.Pop();

        var arguments = new List<AcExpressionNode>();
        foreach (var arg in node.Arguments)
        {
            VisitAndConvert(arg);
            arguments.Add(_nodeStack.Pop());
        }

        _nodeStack.Push(new AcExpressionNode
        {
            NodeType = ExpressionType.Invoke,
            TypeName = node.Type.FullName,
            Object = objectNode,
            Arguments = arguments
        });
        
        return node;
    }

    #region Helper Methods

    private MemberBindingNode ConvertMemberBinding(MemberBinding binding)
    {
        return binding switch
        {
            MemberAssignment assignment => ConvertMemberAssignment(assignment),
            MemberMemberBinding memberBinding => new MemberBindingNode
            {
                MemberName = memberBinding.Member.Name,
                BindingType = MemberBindingType.MemberBinding,
                Bindings = memberBinding.Bindings.Select(ConvertMemberBinding).ToList()
            },
            MemberListBinding listBinding => new MemberBindingNode
            {
                MemberName = listBinding.Member.Name,
                BindingType = MemberBindingType.ListBinding,
                Initializers = listBinding.Initializers
                    .Select(i => i.Arguments.Select(ConvertArgument).ToList())
                    .ToList()
            },
            _ => throw new NotSupportedException($"Member binding type '{binding.BindingType}' is not supported.")
        };
    }

    private MemberBindingNode ConvertMemberAssignment(MemberAssignment assignment)
    {
        VisitAndConvert(assignment.Expression);
        var expr = _nodeStack.Pop();
        
        return new MemberBindingNode
        {
            MemberName = assignment.Member.Name,
            BindingType = MemberBindingType.Assignment,
            Expression = expr
        };
    }

    private AcExpressionNode ConvertArgument(Expression expression)
    {
        VisitAndConvert(expression);
        return _nodeStack.Pop();
    }

    private static bool IsClosureAccess(MemberExpression node)
    {
        return node.Expression switch
        {
            ConstantExpression => true,
            MemberExpression nested => IsClosureAccess(nested),
            _ => false
        };
    }

    private static object? EvaluateClosureValue(MemberExpression node)
    {
        var objectStack = new Stack<MemberExpression>();
        Expression? current = node;

        while (current is MemberExpression me)
        {
            objectStack.Push(me);
            current = me.Expression;
        }

        if (current is not ConstantExpression constant)
            throw new InvalidOperationException("Expected constant at root of closure access.");

        object? value = constant.Value;

        while (objectStack.Count > 0)
        {
            var me = objectStack.Pop();
            value = me.Member switch
            {
                FieldInfo fi => fi.GetValue(value),
                PropertyInfo pi => pi.GetValue(value),
                _ => throw new InvalidOperationException($"Unsupported member type: {me.Member.GetType()}")
            };
        }

        return value;
    }

    #endregion
}
