using System.Linq.Expressions;

namespace AyCode.Core.Serializers.Expressions;

/// <summary>
/// Constant value type for serialization.
/// Determines which typed property contains the actual value.
/// </summary>
public enum ConstantValueType : byte
{
    Null = 0,
    Int32 = 1,
    Int64 = 2,
    Double = 3,
    Decimal = 4,
    Boolean = 5,
    String = 6,
    Guid = 7,
    DateTime = 8,
    DateTimeOffset = 9,
    TimeSpan = 10,
    Char = 11,
    Byte = 12,
    Int16 = 13,
    Single = 14,
    UInt32 = 15,
    UInt64 = 16,
    UInt16 = 17,
    SByte = 18,
    DateOnly = 19,
    TimeOnly = 20,
    Enum = 50,
}

/// <summary>
/// Universal DTO representing any Expression node.
/// Recursively represents the entire Expression tree.
/// Format-agnostic - can be serialized with any serializer (JSON, Binary, etc.)
/// </summary>
public sealed class AcExpressionNode
{
    /// <summary>
    /// The expression node type (Add, Equal, Call, MemberAccess, Lambda, etc.)
    /// </summary>
    public ExpressionType NodeType { get; set; }

    /// <summary>
    /// The CLR type name of this expression's result.
    /// </summary>
    public string? TypeName { get; set; }

    #region Binary Expressions (Add, Equal, AndAlso, OrElse, etc.)

    public AcExpressionNode? Left { get; set; }
    public AcExpressionNode? Right { get; set; }

    #endregion

    #region Unary Expressions (Not, Convert, Negate, etc.)

    public AcExpressionNode? Operand { get; set; }

    #endregion

    #region Lambda Expressions

    public AcExpressionNode? Body { get; set; }
    public List<ParameterNode>? Parameters { get; set; }

    #endregion

    #region Member Access

    public string? MemberName { get; set; }
    public AcExpressionNode? Object { get; set; }
    public string? DeclaringType { get; set; }

    #endregion

    #region Method Call

    public string? MethodName { get; set; }
    public List<AcExpressionNode>? Arguments { get; set; }
    public List<string>? GenericArguments { get; set; }

    #endregion

    #region Constant Values - Type-safe storage

    public ConstantValueType ValueType { get; set; }
    public int ValueInt32 { get; set; }
    public long ValueInt64 { get; set; }
    public double ValueDouble { get; set; }
    public decimal ValueDecimal { get; set; }
    public bool ValueBool { get; set; }
    public string? ValueString { get; set; }
    public Guid ValueGuid { get; set; }
    public DateTime ValueDateTime { get; set; }
    public DateTimeOffset ValueDateTimeOffset { get; set; }
    public TimeSpan ValueTimeSpan { get; set; }
    
    /// <summary>
    /// For enum values, stores the assembly-qualified enum type name.
    /// </summary>
    public string? EnumTypeName { get; set; }

    #endregion

    #region Parameter

    public string? ParameterName { get; set; }
    public int? ParameterIndex { get; set; }

    #endregion

    #region Conditional (Ternary)

    public AcExpressionNode? Test { get; set; }
    public AcExpressionNode? IfTrue { get; set; }
    public AcExpressionNode? IfFalse { get; set; }

    #endregion

    #region New Expression

    public List<AcExpressionNode>? ConstructorArguments { get; set; }
    public List<MemberBindingNode>? MemberBindings { get; set; }

    #endregion

    #region Array/Collection

    public List<AcExpressionNode>? Elements { get; set; }

    #endregion

    #region Value Helpers

    /// <summary>
    /// Gets the constant value based on ValueType.
    /// </summary>
    public object? GetValue()
    {
        return ValueType switch
        {
            ConstantValueType.Null => null,
            ConstantValueType.Int32 => ValueInt32,
            ConstantValueType.Int64 => ValueInt64,
            ConstantValueType.Double => ValueDouble,
            ConstantValueType.Decimal => ValueDecimal,
            ConstantValueType.Boolean => ValueBool,
            ConstantValueType.String => ValueString,
            ConstantValueType.Guid => ValueGuid,
            ConstantValueType.DateTime => ValueDateTime,
            ConstantValueType.DateTimeOffset => ValueDateTimeOffset,
            ConstantValueType.TimeSpan => ValueTimeSpan,
            ConstantValueType.Char => (char)ValueInt32,
            ConstantValueType.Byte => (byte)ValueInt32,
            ConstantValueType.Int16 => (short)ValueInt32,
            ConstantValueType.Single => (float)ValueDouble,
            ConstantValueType.UInt32 => (uint)ValueInt64,
            ConstantValueType.UInt64 => (ulong)ValueInt64,
            ConstantValueType.UInt16 => (ushort)ValueInt32,
            ConstantValueType.SByte => (sbyte)ValueInt32,
            ConstantValueType.DateOnly => DateOnly.FromDateTime(ValueDateTime),
            ConstantValueType.TimeOnly => TimeOnly.FromTimeSpan(ValueTimeSpan),
            ConstantValueType.Enum => GetEnumValue(),
            _ => null
        };
    }

    private object GetEnumValue()
    {
        if (string.IsNullOrEmpty(EnumTypeName))
            return ValueInt32;
        
        var enumType = Type.GetType(EnumTypeName);
        if (enumType == null)
        {
            // Fallback: try all loaded assemblies
            foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
            {
                enumType = assembly.GetType(EnumTypeName.Split(',')[0].Trim());
                if (enumType != null) break;
            }
        }
        
        return enumType != null ? Enum.ToObject(enumType, ValueInt32) : ValueInt32;
    }

    /// <summary>
    /// Sets the constant value with automatic type detection.
    /// </summary>
    public void SetValue(object? value)
    {
        if (value == null)
        {
            ValueType = ConstantValueType.Null;
            return;
        }

        var type = value.GetType();
        var underlyingType = Nullable.GetUnderlyingType(type) ?? type;

        if (underlyingType.IsEnum)
        {
            ValueType = ConstantValueType.Enum;
            ValueInt32 = Convert.ToInt32(value);
            // Store assembly-qualified name for cross-assembly resolution
            EnumTypeName = underlyingType.AssemblyQualifiedName;
            return;
        }

        switch (Type.GetTypeCode(underlyingType))
        {
            case TypeCode.Int32:
                ValueType = ConstantValueType.Int32;
                ValueInt32 = (int)value;
                break;
            case TypeCode.Int64:
                ValueType = ConstantValueType.Int64;
                ValueInt64 = (long)value;
                break;
            case TypeCode.Double:
                ValueType = ConstantValueType.Double;
                ValueDouble = (double)value;
                break;
            case TypeCode.Decimal:
                ValueType = ConstantValueType.Decimal;
                ValueDecimal = (decimal)value;
                break;
            case TypeCode.Boolean:
                ValueType = ConstantValueType.Boolean;
                ValueBool = (bool)value;
                break;
            case TypeCode.String:
                ValueType = ConstantValueType.String;
                ValueString = (string)value;
                break;
            case TypeCode.DateTime:
                ValueType = ConstantValueType.DateTime;
                ValueDateTime = (DateTime)value;
                break;
            case TypeCode.Char:
                ValueType = ConstantValueType.Char;
                ValueInt32 = (char)value;
                break;
            case TypeCode.Byte:
                ValueType = ConstantValueType.Byte;
                ValueInt32 = (byte)value;
                break;
            case TypeCode.Int16:
                ValueType = ConstantValueType.Int16;
                ValueInt32 = (short)value;
                break;
            case TypeCode.Single:
                ValueType = ConstantValueType.Single;
                ValueDouble = (float)value;
                break;
            case TypeCode.UInt32:
                ValueType = ConstantValueType.UInt32;
                ValueInt64 = (uint)value;
                break;
            case TypeCode.UInt64:
                ValueType = ConstantValueType.UInt64;
                ValueInt64 = (long)(ulong)value;
                break;
            case TypeCode.UInt16:
                ValueType = ConstantValueType.UInt16;
                ValueInt32 = (ushort)value;
                break;
            case TypeCode.SByte:
                ValueType = ConstantValueType.SByte;
                ValueInt32 = (sbyte)value;
                break;
            default:
                if (underlyingType == typeof(Guid))
                {
                    ValueType = ConstantValueType.Guid;
                    ValueGuid = (Guid)value;
                }
                else if (underlyingType == typeof(DateTimeOffset))
                {
                    ValueType = ConstantValueType.DateTimeOffset;
                    ValueDateTimeOffset = (DateTimeOffset)value;
                }
                else if (underlyingType == typeof(TimeSpan))
                {
                    ValueType = ConstantValueType.TimeSpan;
                    ValueTimeSpan = (TimeSpan)value;
                }
                else if (underlyingType == typeof(DateOnly))
                {
                    ValueType = ConstantValueType.DateOnly;
                    ValueDateTime = ((DateOnly)value).ToDateTime(TimeOnly.MinValue);
                }
                else if (underlyingType == typeof(TimeOnly))
                {
                    ValueType = ConstantValueType.TimeOnly;
                    ValueTimeSpan = ((TimeOnly)value).ToTimeSpan();
                }
                else
                {
                    throw new NotSupportedException(
                        $"Constant value type '{type.FullName}' is not supported for Expression serialization.");
                }
                break;
        }
    }

    #endregion
}

/// <summary>
/// Represents a parameter definition in a lambda expression.
/// </summary>
public sealed class ParameterNode
{
    public string Name { get; set; } = "";
    public string TypeName { get; set; } = "";
    public int Index { get; set; }
}

/// <summary>
/// Represents a member binding in MemberInit expressions.
/// </summary>
public sealed class MemberBindingNode
{
    public string MemberName { get; set; } = "";
    public MemberBindingType BindingType { get; set; }
    public AcExpressionNode? Expression { get; set; }
    public List<MemberBindingNode>? Bindings { get; set; }
    public List<List<AcExpressionNode>>? Initializers { get; set; }
}
