using System.Collections;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.CompilerServices;
using static AyCode.Core.Helpers.JsonUtilities;

namespace AyCode.Core.Serializers.Binaries;

/// <summary>
/// Binary-specific property setter base class.
/// Extends PropertySetterBase with binary-specific functionality.
/// Typed setters are inherited from PropertySetterBase.
/// </summary>
public abstract class BinaryPropertySetterBase : PropertySetterBase
{
    /// <summary>
    /// Index into the PropertyTypeWrappers array on TypeMetadataWrapper.
    /// Only set for complex (non-primitive, non-string) properties. -1 for primitives and strings.
    /// Used to pre-cache TypeMetadataWrapper per property type, eliminating GetWrapper dictionary lookups.
    /// </summary>
    public int ComplexPropertyIndex { get; internal set; } = -1;

    /// <summary>
    /// Whether this property is a complex type (not primitive, string, enum, or common value types).
    /// Note: Shadows PropertyAccessorBase.IsComplexType with Binary-specific check.
    /// </summary>
    public new bool IsComplexType { get; }
    
    /// <summary>
    /// Whether this property is a collection type.
    /// </summary>
    public bool IsCollection { get; }

    /// <summary>
    /// Pre-computed expected type code for markerless deserialization (UseMetadata=false).
    /// When set, the deserializer uses this value instead of reading a marker from the stream.
    /// null = marker must be read from stream (bool, enum, string, object, collection, nullable value types).
    /// Non-null = markerless (non-nullable value types).
    /// </summary>
    public byte? ExpectedTypeCode { get; }

    protected BinaryPropertySetterBase(
        PropertyInfo prop,
        [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type declaringType)
        : base(prop, declaringType)
    {
        IsCollection = IsCollectionTypeCheck(PropertyType);
        IsComplexType = IsComplex(PropertyType);
        ExpectedTypeCode = ComputeExpectedTypeCode(AccessorType);
    }

    public override void SetValue(object target, object? value)
    {
        try
        {
            base.SetValue(target, value);
        }
        catch (InvalidCastException ex)
        {
            var valueType = value?.GetType().FullName ?? "null";
            var propType = PropertyType.FullName;
            var targetTypeName = target.GetType().FullName;
            var underlyingType = Nullable.GetUnderlyingType(PropertyType);
            
            // Get actual value info for debugging
            var valueInfo = value switch
            {
                null => "null",
                int i => $"int:{i}",
                long l => $"long:{l}",
                DateTime dt => $"DateTime:{dt:O}",
                string s => $"string:'{s}'",
                _ => $"{value.GetType().Name}:{value}"
            };
            
            throw new InvalidCastException(
                $"Cannot set property '{Name}' on type '{targetTypeName}' - " +
                $"PropertyType: '{propType}', ValueType: '{valueType}', Value: {valueInfo}, " +
                $"IsNullable: {underlyingType != null}, UnderlyingType: {underlyingType?.FullName ?? "N/A"}, " +
                $"IsValueType: {PropertyType.IsValueType}, IsCollection: {IsCollection}, IsComplexType: {IsComplexType}",
                ex);
        }
    }

    private static bool IsCollectionTypeCheck(Type type)
    {
        if (ReferenceEquals(type, StringType)) return false;
        if (type.IsArray) return true;
        return typeof(IEnumerable).IsAssignableFrom(type);
    }

    private static bool IsComplex(Type type)
    {
        var actualType = Nullable.GetUnderlyingType(type) ?? type;
        if (actualType.IsPrimitive) return false;
        if (ReferenceEquals(actualType, StringType)) return false;
        if (actualType.IsEnum) return false;
        if (ReferenceEquals(actualType, GuidType)) return false;
        if (ReferenceEquals(actualType, DateTimeType)) return false;
        if (ReferenceEquals(actualType, DecimalType)) return false;
        if (ReferenceEquals(actualType, TimeSpanType)) return false;
        if (ReferenceEquals(actualType, DateTimeOffsetType)) return false;
        return true;
    }

    /// <summary>
    /// Maps AccessorType to the BinaryTypeCode that would normally be read as marker.
    /// Returns null for types that always need a stream marker (string, object/nullable).
    /// </summary>
    private static byte? ComputeExpectedTypeCode(PropertyAccessorType accessorType) => accessorType switch
    {
        PropertyAccessorType.Int32 => BinaryTypeCode.Int32,
        PropertyAccessorType.Int64 => BinaryTypeCode.Int64,
        PropertyAccessorType.Double => BinaryTypeCode.Float64,
        PropertyAccessorType.Single => BinaryTypeCode.Float32,
        PropertyAccessorType.Decimal => BinaryTypeCode.Decimal,
        PropertyAccessorType.DateTime => BinaryTypeCode.DateTime,
        PropertyAccessorType.Guid => BinaryTypeCode.Guid,
        PropertyAccessorType.Byte => BinaryTypeCode.UInt8,
        PropertyAccessorType.Int16 => BinaryTypeCode.Int16,
        PropertyAccessorType.UInt16 => BinaryTypeCode.UInt16,
        PropertyAccessorType.UInt32 => BinaryTypeCode.UInt32,
        PropertyAccessorType.UInt64 => BinaryTypeCode.UInt64,
        PropertyAccessorType.Boolean => BinaryTypeCode.True,
        PropertyAccessorType.Enum => BinaryTypeCode.Enum,
        _ => null // String, Object � always read marker from stream
    };
}
