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

namespace AyCode.Core.Serializers.Binaries;

public static partial class AcBinarySerializer
{
    internal sealed class BinarySerializeTypeMetadata : SerializeTypeMetadataBase
    {
        public BinaryPropertyAccessor[] Properties { get; }

        /// <summary>
        /// True if this type has a Source Generator generated serializer available.
        /// </summary>
        public bool HasGeneratedSerializer { get; }

        /// <summary>
        /// The generated serializer type, if available.
        /// </summary>
        public Type? GeneratedSerializerType { get; }

        public bool EnableInternString { get; }
        
        /// <summary>
        /// Lazy-computed FNV-1a hash of the type name (SourceType.Name).
        /// Only computed once per type, on first access when UseMetadata=true.
        /// </summary>
        private int _propNameHash;
        private bool _propNameHashComputed;

        public int PropNameHash
        {
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            get
            {
                if (!_propNameHashComputed)
                    ComputePropNameHash();
                return _propNameHash;
            }
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        private void ComputePropNameHash()
        {
            _propNameHash = FnvHash.ComputeString(SourceType.Name);
            _propNameHashComputed = true;
        }

        /// <summary>
        /// Lazy-computed property name hashes array for UseMetadata footer.
        /// Includes duplicate hash validation (computed once per type, not per session).
        /// </summary>
        private int[]? _metadataPropertyHashes;

        /// <summary>
        /// Lazy-computed array of reference properties (complex types + strings).
        /// Used by scan pass to quickly iterate only properties that need reference tracking.
        /// </summary>
        private BinaryPropertyAccessor[]? _referenceProperties;

        /// <summary>
        /// Gets the array of reference properties (complex types + strings).
        /// Computed lazily on first access.
        /// </summary>
        public BinaryPropertyAccessor[] ReferenceProperties
        {
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            get => _referenceProperties ??= ComputeReferenceProperties();
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        private BinaryPropertyAccessor[] ComputeReferenceProperties()
        {
            var list = new List<BinaryPropertyAccessor>();
            for (var i = 0; i < Properties.Length; i++)
            {
                var prop = Properties[i];
                if ((prop.IsComplexType && !(prop.IsStringCollectionProperty && !EnableInternString)) ||
                    (EnableInternString && prop.AccessorType == PropertyAccessorType.String))
                    list.Add(prop);
            }

            return list.ToArray();
        }

        public int[] MetadataPropertyHashes
        {
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            get => _metadataPropertyHashes ??= ComputeMetadataPropertyHashes();
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        private int[] ComputeMetadataPropertyHashes()
        {
            var hashes = new int[Properties.Length];
            HashSet<int>? seen = null;

            for (var i = 0; i < Properties.Length; i++)
            {
                var hash = Properties[i].PropertyNameHash;
                seen ??= new HashSet<int>(Properties.Length);
                if (!seen.Add(hash))
                {
                    throw new InvalidOperationException(
                        $"UseMetadata: Duplikált property name hash a '{SourceType.FullName}' típuson. " +
                        $"Property '{Properties[i].Name}' FNV-1a hash ütközés. " +
                        $"Használj [AcPropertyName] attribútumot a feloldáshoz.");
                }
                hashes[i] = hash;
            }

            return hashes;
        }


        /// <summary>
        /// Pre-computed minimum byte size for writing all properties of this type.
        /// Markerless properties contribute their max encoded size (e.g. VarInt=5, double=8).
        /// Markered properties contribute 1 byte (minimum marker: PropertySkip/Null/True/False).
        /// Used by EnsureCapacity before property writes to avoid mid-object buffer Grow().
        /// </summary>
        public int MinWriteSize { get; }

        public BinarySerializeTypeMetadata(
            [DynamicallyAccessedMembers(RequiredMembers)] Type type,
            Func<PropertyInfo, bool> ignorePropertyFilter)
            : base(type, ignorePropertyFilter)
        {
            // Use pre-computed WritableProperties directly - no method call overhead!
            var orderedProperties = WritableProperties;

            // Read [AcBinarySerializable] once per type — passed to property accessors
            var serializableAttr = type.GetCustomAttribute<AcBinarySerializableAttribute>(inherit: false);
            EnableInternString = serializableAttr == null || serializableAttr.EnableInternStringFeature;
            var enableRefHandling = serializableAttr == null || serializableAttr.EnableRefHandlingFeature;

            Properties = new BinaryPropertyAccessor[orderedProperties.Length];
            var complexCount = 0;
            var minWriteSize = 0;

            for (var i = 0; i < orderedProperties.Length; i++)
            {
                var accessor = new BinaryPropertyAccessor(orderedProperties[i], type, EnableInternString);
                accessor.PropertyIndex = i;
                Properties[i] = accessor;

                // Assign ComplexPropertyIndex for non-primitive, non-string properties
                if (accessor.IsComplexType)
                {
                    accessor.ComplexPropertyIndex = complexCount++;
                }

                // Accumulate min write size per property
                minWriteSize += ComputePropertyMaxWriteSize(accessor);
            }

            MinWriteSize = minWriteSize;

            // Set scan optimization flags
            ComplexPropertyCount = complexCount;
            HasComplexProperties = complexCount > 0;
            
            // Type needs reference tracking if:
            // 1. It's IId (can be deduplicated by Id) — already false if EnableIdTrackingFeature == false
            // 2. EnableRefHandlingFeature enabled AND (has complex properties or non-primitive)
            NeedsReferenceTracking = IsIId || (enableRefHandling && (HasComplexProperties || !IsPrimitiveType));

            // Fast check: only look for generated serializer if type has [AcBinarySerializable] attribute
            if (false && type.IsDefined(typeof(AcBinarySerializableAttribute), inherit: false))
            {
                GeneratedSerializerType = FindGeneratedSerializerType(type);
                HasGeneratedSerializer = GeneratedSerializerType != null;
            }
        }

        /// <summary>
        /// Returns the worst-case byte count for a markered property write (skip-or-write pattern).
        /// Most types: type_code(1) + markerless_max. Boolean/Enum are special.
        /// Used as the single source of truth for MinWriteSize — covers both UseMetadata=true
        /// (all properties markered) and UseMetadata=false (ExpectedTypeCode properties markerless,
        /// which is strictly smaller than markered).
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static int GetMarkeredMaxSize(PropertyAccessorType accessorType) => accessorType switch
        {
            PropertyAccessorType.Boolean => 1,    // PropertySkip or True (single byte)
            PropertyAccessorType.Enum => 7,       // Enum(1) + Int32(1) + VarInt(5)
            PropertyAccessorType.Int32 => 6,      // marker(1) + VarInt(5)
            PropertyAccessorType.Int64 => 11,     // marker(1) + VarLong(10)
            PropertyAccessorType.Double => 9,     // marker(1) + 8
            PropertyAccessorType.Single => 5,     // marker(1) + 4
            PropertyAccessorType.Decimal => 17,   // marker(1) + 16
            PropertyAccessorType.DateTime => 10,  // marker(1) + Ticks(8) + Kind(1)
            PropertyAccessorType.Guid => 17,      // marker(1) + 16
            PropertyAccessorType.Byte => 2,       // marker(1) + 1
            PropertyAccessorType.Int16 => 3,      // marker(1) + 2
            PropertyAccessorType.UInt16 => 3,     // marker(1) + 2
            PropertyAccessorType.UInt32 => 6,     // marker(1) + VarUInt(5)
            PropertyAccessorType.UInt64 => 11,    // marker(1) + VarULong(10)
            _ => 1                                // String/Complex/Collection: safe writes
        };

        /// <summary>
        /// Computes the maximum byte size a single property write can consume.
        /// Always uses markered max — covers both UseMetadata=true (all properties markered via
        /// WritePropertyOrSkipUnsafe) and UseMetadata=false (markerless, which is strictly ≤ markered).
        /// </summary>
        private static int ComputePropertyMaxWriteSize(BinaryPropertyAccessor accessor)
            => GetMarkeredMaxSize(accessor.AccessorType);

        private static Type? FindGeneratedSerializerType(Type type)
        {
            var generatedTypeName = $"{type.FullName}_AcBinarySerializer";
            
            var serializerType = type.Assembly.GetType(generatedTypeName);
            
            if (serializerType == null && type.IsNested)
            {
                var ns = type.Namespace ?? "";
                var simpleName = $"{(string.IsNullOrEmpty(ns) ? "" : ns + ".")}{type.Name}_AcBinarySerializer";
                serializerType = type.Assembly.GetType(simpleName);
            }

            return serializerType;
        }
    }

    /// <summary>
    /// Binary serialization property accessor with typed getters.
    /// </summary>
    internal sealed class BinaryPropertyAccessor : BinaryPropertyAccessorBase
    {
        public BinaryPropertyAccessor(
            PropertyInfo prop,
            [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type declaringType,
            bool enableInternString)
            : base(prop, declaringType, enableInternString)
        {
        }
    }
}
