using System.Collections;
using System.Collections.Concurrent;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Json;
using AyCode.Core.Helpers;
using AyCode.Core.Serializers.Expressions;
using static AyCode.Core.Helpers.JsonUtilities;

namespace AyCode.Core.Serializers.Jsons;

/// <summary>
/// High-performance custom JSON deserializer optimized for IId&lt;T&gt; reference handling.
/// Uses Utf8JsonReader for streaming deserialization when possible (STJ approach).
/// </summary>
public static partial class AcJsonDeserializer
{
    // Pre-computed JSON property names for fast lookup (UTF-8 bytes)
    private static readonly byte[] RefPropertyUtf8 = "$ref"u8.ToArray();
    private static readonly byte[] IdPropertyUtf8 = "$id"u8.ToArray();

    // Global metadata cache
    private static readonly ConcurrentDictionary<Type, JsonDeserializeTypeMetadata> MetadataCache = new();

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private static JsonDeserializeTypeMetadata GetTypeMetadata(Type type)
        => MetadataCache.GetOrAdd(type, static t => new JsonDeserializeTypeMetadata(t));

    #region Public API

    /// <summary>
    /// Deserialize JSON string to a new object of type T with default options.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static T? Deserialize<T>(string json) => Deserialize<T>(json, AcJsonSerializerOptions.Default);

    /// <summary>
    /// Deserialize UTF-8 encoded JSON bytes to a new object of type T with default options.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static T? Deserialize<T>(ReadOnlySpan<byte> utf8Json) => Deserialize<T>(utf8Json, AcJsonSerializerOptions.Default);

    /// <summary>
    /// Deserialize UTF-8 encoded JSON bytes to a new object of type T with specified options.
    /// </summary>
    public static T? Deserialize<T>(ReadOnlySpan<byte> utf8Json, in AcJsonSerializerOptions options)
    {
        if (utf8Json.IsEmpty) return default;
        if (utf8Json.Length == 4 && utf8Json.SequenceEqual("null"u8)) return default;
        
        var targetType = typeof(T);
        
        // Handle Expression types
        if (IsExpressionType(targetType))
        {
            return (T?)(object?)DeserializeExpression(utf8Json, targetType, options);
        }
        
        try
        {
            if (options.ReferenceHandling == ReferenceHandlingMode.None)
            {
                var reader = new Utf8JsonReader(utf8Json, new JsonReaderOptions { MaxDepth = options.MaxDepth });
                if (!reader.Read()) return default;
                return (T?)ReadValueFromReader(ref reader, targetType, options.MaxDepth, 0);
            }
            
            var jsonBytes = utf8Json.ToArray();
            using var doc = JsonDocument.Parse(jsonBytes);
            var context = JsonDeserializationContextPool.Get(options);
            try
            {
                var result = ReadValue(doc.RootElement, targetType, context);
                context.ResolveReferences();
                return (T?)result;
            }
            finally
            {
                JsonDeserializationContextPool.Return(context, options);
            }
        }
        catch (AcJsonDeserializationException) { throw; }
        catch (System.Text.Json.JsonException ex)
        {
            throw new AcJsonDeserializationException($"Failed to parse JSON for type '{targetType.Name}': {ex.Message}", null, targetType, ex);
        }
        catch (Exception ex) when (ex is FormatException or InvalidCastException or OverflowException)
        {
            throw new AcJsonDeserializationException($"Failed to convert JSON value for type '{targetType.Name}': {ex.Message}", null, targetType, ex);
        }
    }

    /// <summary>
    /// Deserialize JSON string to a new object of type T with specified options.
    /// </summary>
    public static T? Deserialize<T>(string json, in AcJsonSerializerOptions options)
    {
        if (string.IsNullOrEmpty(json) || json == "null") return default;
        
        var targetType = typeof(T);
        
        // Handle Expression types
        if (IsExpressionType(targetType))
        {
            var (buffer, length) = GetUtf8Bytes(json);
            try
            {
                return (T?)(object?)DeserializeExpression(buffer.AsSpan(0, length), targetType, options);
            }
            finally
            {
                ReturnUtf8Buffer(buffer);
            }
        }
        
        try
        {
            if (TryDeserializePrimitiveFast(json, targetType, out var primitiveResult))
                return (T?)primitiveResult;
            
            ValidateJson(json, targetType);
            
            if (options.ReferenceHandling == ReferenceHandlingMode.None)
            {
                return DeserializeWithUtf8Reader<T>(json, options.MaxDepth);
            }
            
            using var doc = JsonDocument.Parse(json);
            var context = JsonDeserializationContextPool.Get(options);
            try
            {
                var result = ReadValue(doc.RootElement, targetType, context);
                context.ResolveReferences();
                return (T?)result;
            }
            finally
            {
                JsonDeserializationContextPool.Return(context, options);
            }
        }
        catch (AcJsonDeserializationException) { throw; }
        catch (System.Text.Json.JsonException ex)
        {
            throw new AcJsonDeserializationException($"Failed to parse JSON for type '{targetType.Name}': {ex.Message}", json, targetType, ex);
        }
        catch (Exception ex) when (ex is FormatException or InvalidCastException or OverflowException)
        {
            throw new AcJsonDeserializationException($"Failed to convert JSON value for type '{targetType.Name}': {ex.Message}", json, targetType, ex);
        }
    }

    /// <summary>
    /// Deserialize JSON string to specified type with specified options.
    /// </summary>
    public static object? Deserialize(string json, in Type targetType, in AcJsonSerializerOptions options)
    {
        if (string.IsNullOrEmpty(json) || json == "null") return null;
        
        // Handle Expression types - deserialize as AcExpressionNode and rebuild
        if (AcSerializerCommon.IsExpressionType(targetType))
        {
            var (buffer, length) = GetUtf8Bytes(json);
            try
            {
                return DeserializeExpression(buffer.AsSpan(0, length), targetType, options);
            }
            finally
            {
                ReturnUtf8Buffer(buffer);
            }
        }
        
        try
        {
            var firstChar = json[0];
            var isArrayJson = firstChar == '[';
            var isObjectJson = firstChar == '{';
            
            if (!isArrayJson && !isObjectJson)
            {
                if (TryDeserializePrimitiveFast(json, targetType, out var primitiveResult))
                    return primitiveResult;
            }
            
            ValidateJson(json, targetType);
            
            // Fast path for no reference handling
            if (options.ReferenceHandling == ReferenceHandlingMode.None)
            {
                return DeserializeWithUtf8ReaderNonGeneric(json, targetType, options.MaxDepth);
            }
            
            // Reference handling requires DOM - copy to array for JsonDocument.Parse
            var jsonBytes = Encoding.UTF8.GetBytes(json);
            using var doc = JsonDocument.Parse(jsonBytes);
            var context = JsonDeserializationContextPool.Get(options);
            try
            {
                var result = ReadValue(doc.RootElement, targetType, context);
                context.ResolveReferences();
                return result;
            }
            finally
            {
                JsonDeserializationContextPool.Return(context, options);
            }
        }
        catch (AcJsonDeserializationException) { throw; }
        catch (System.Text.Json.JsonException ex)
        {
            throw new AcJsonDeserializationException($"Failed to parse JSON for type '{targetType.Name}': {ex.Message}", json, targetType, ex);
        }
        catch (Exception ex) when (ex is FormatException or InvalidCastException or OverflowException)
        {
            throw new AcJsonDeserializationException($"Failed to convert JSON value for type '{targetType.Name}': {ex.Message}", json, targetType, ex);
        }
    }

    /// <summary>
    /// Deserialize Expression from JSON.
    /// Uses AcSerializerCommon for entity type extraction.
    /// </summary>
    private static Expression? DeserializeExpression(ReadOnlySpan<byte> utf8Json, Type targetExpressionType, in AcJsonSerializerOptions options)
    {
        var reader = new Utf8JsonReader(utf8Json, new JsonReaderOptions { MaxDepth = options.MaxDepth });
        if (!reader.Read()) return null;
        
        var node = (AcExpressionNode?)ReadValueFromReader(ref reader, typeof(AcExpressionNode), options.MaxDepth, 0);
        if (node == null) return null;

        var entityType = AcSerializerCommon.GetExpressionEntityType(targetExpressionType);
        return AcExpressionRebuilder.FromNode(node, entityType);
    }

    /// <summary>
    /// Checks if a type is an Expression type.
    /// Uses AcSerializerCommon for consistency.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private static bool IsExpressionType(Type type)
    {
        return AcSerializerCommon.IsExpressionType(type);
    }

    /// <summary>
    /// Deserialize UTF-8 encoded JSON bytes to specified type with default options.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static object? Deserialize(ReadOnlySpan<byte> utf8Json, Type targetType) 
        => Deserialize(utf8Json, targetType, AcJsonSerializerOptions.Default);

    /// <summary>
    /// Deserialize UTF-8 encoded JSON bytes to specified type with specified options.
    /// </summary>
    public static object? Deserialize(ReadOnlySpan<byte> utf8Json, in Type targetType, in AcJsonSerializerOptions options)
    {
        if (utf8Json.IsEmpty) return null;
        
        // Check for "null" literal
        if (utf8Json.Length == 4 && utf8Json.SequenceEqual("null"u8)) return null;
        
        // Handle Expression types
        if (AcSerializerCommon.IsExpressionType(targetType))
        {
            return DeserializeExpression(utf8Json, targetType, options);
        }
        
        try
        {
            // Fast path for no reference handling
            if (options.ReferenceHandling == ReferenceHandlingMode.None)
            {
                var reader = new Utf8JsonReader(utf8Json, new JsonReaderOptions { MaxDepth = options.MaxDepth });
                if (!reader.Read()) return null;
                return ReadValueFromReader(ref reader, targetType, options.MaxDepth, 0);
            }
            
            // Reference handling requires DOM - copy to array for JsonDocument.Parse
            var jsonBytes = utf8Json.ToArray();
            using var doc = JsonDocument.Parse(jsonBytes);
            var context = JsonDeserializationContextPool.Get(options);
            try
            {
                var result = ReadValue(doc.RootElement, targetType, context);
                context.ResolveReferences();
                return result;
            }
            finally
            {
                JsonDeserializationContextPool.Return(context, options);
            }
        }
        catch (AcJsonDeserializationException) { throw; }
        catch (System.Text.Json.JsonException ex)
        {
            throw new AcJsonDeserializationException($"Failed to parse JSON for type '{targetType.Name}': {ex.Message}", null, targetType, ex);
        }
        catch (Exception ex) when (ex is FormatException or InvalidCastException or OverflowException)
        {
            throw new AcJsonDeserializationException($"Failed to convert JSON value for type '{targetType.Name}': {ex.Message}", null, targetType, ex);
        }
    }

    /// <summary>
    /// Deserialize JSON string to specified type with default options.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static object? Deserialize(string json, Type targetType) => Deserialize(json, targetType, AcJsonSerializerOptions.Default);

    /// <summary>
    /// Populate existing object with JSON data (merge mode) with default options.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void Populate<T>(string json, T target) where T : class 
        => Populate(json, target, AcJsonSerializerOptions.Default);

    /// <summary>
    /// Populate existing object with JSON data (merge mode) with specified options.
    /// </summary>
    public static void Populate<T>(string json, T target, in AcJsonSerializerOptions options) where T : class
    {
        ArgumentNullException.ThrowIfNull(target);
        if (string.IsNullOrEmpty(json) || json == "null") return;
        PopulateInternal(json, target, target.GetType(), options);
    }

    /// <summary>
    /// Populate existing object with JSON data with default options.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void Populate(string json, object target) 
        => Populate(json, target, target.GetType(), AcJsonSerializerOptions.Default);

    /// <summary>
    /// Populate existing object with JSON data with specified options.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void Populate(string json, object target, in AcJsonSerializerOptions options) 
        => Populate(json, target, target.GetType(), options);

    /// <summary>
    /// Populate existing object with JSON data with default options.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void Populate(string json, object target, Type targetType) 
        => Populate(json, target, targetType, AcJsonSerializerOptions.Default);

    /// <summary>
    /// Populate existing object with JSON data with specified options.
    /// </summary>
    public static void Populate(string json, object target, in Type targetType, in AcJsonSerializerOptions options)
    {
        ArgumentNullException.ThrowIfNull(target);
        if (string.IsNullOrEmpty(json) || json == "null") return;
        PopulateInternal(json, target, targetType, options);
    }

    private static void PopulateInternal(string json, object target, in Type targetType, in AcJsonSerializerOptions options)
    {
        try
        {
            ValidateJson(json, targetType);
            
            // Fast path for no reference handling - use Utf8JsonReader streaming
            if (options.ReferenceHandling == ReferenceHandlingMode.None)
            {
                var firstChar = json[0];
                
                if (firstChar == '[')
                {
                    if (target is IList targetList)
                        PopulateListWithUtf8Reader(json, targetList, targetType, options.MaxDepth);
                    else
                        throw new AcJsonDeserializationException($"Cannot populate non-list target '{targetType.Name}' with JSON array", json, targetType);
                    return;
                }
                
                if (firstChar == '{')
                {
                    var metadata = GetTypeMetadata(targetType);
                    PopulateObjectWithUtf8Reader(json, target, metadata, options.MaxDepth);
                    return;
                }
                
                throw new AcJsonDeserializationException($"Cannot populate object with JSON starting with '{firstChar}'", json, targetType);
            }
            
            // Reference handling requires DOM for forward references
            using var doc = JsonDocument.Parse(json);
            var rootElement = doc.RootElement;
            
            var context = JsonDeserializationContextPool.Get(options);
            context.IsMergeMode = true;
            
            try
            {
                if (rootElement.ValueKind == JsonValueKind.Array)
                {
                    if (target is IList targetList)
                        PopulateList(rootElement, targetList, targetType, context);
                    else
                        throw new AcJsonDeserializationException($"Cannot populate non-list target '{targetType.Name}' with JSON array", json, targetType);
                    
                    context.ResolveReferences();
                    return;
                }
                
                if (rootElement.ValueKind == JsonValueKind.Object)
                {
                    var metadata = GetTypeMetadata(targetType);
                    PopulateObjectInternalMerge(rootElement, target, metadata, context);
                }
                else
                    throw new AcJsonDeserializationException($"Cannot populate object with JSON value of kind '{rootElement.ValueKind}'", json, targetType);
                
                context.ResolveReferences();
            }
            finally
            {
                JsonDeserializationContextPool.Return(context, options);
            }
        }
        catch (AcJsonDeserializationException) { throw; }
        catch (System.Text.Json.JsonException ex)
        {
            throw new AcJsonDeserializationException($"Failed to parse JSON for population of type '{targetType.Name}': {ex.Message}", json, targetType, ex);
        }
        catch (Exception ex) when (ex is FormatException or InvalidCastException or OverflowException)
        {
            throw new AcJsonDeserializationException($"Failed to convert JSON value during population of type '{targetType.Name}': {ex.Message}", json, targetType, ex);
        }
    }

    #endregion

    #region Validation

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private static void ValidateJson(string json, in Type targetType)
    {
        if (json.Length < 2) return;

        var firstChar = json[0];
        
        // Quick check for double-serialized JSON
        if (firstChar == '"' && json[^1] == '"')
        {
            var inner = json.AsSpan(1, json.Length - 2);
            if (inner.Contains("\\\"", StringComparison.Ordinal) && 
                (inner.Contains("{", StringComparison.Ordinal) || inner.Contains("[", StringComparison.Ordinal)))
                throw new AcJsonDeserializationException(
                    $"Detected double-serialized JSON string. Target type: {targetType.Name}.",
                    json, targetType);
        }

        var isArrayJson = firstChar == '[';
        var isObjectJson = firstChar == '{';
        
        if (isArrayJson)
        {
            var isCollectionType = targetType.IsArray || IsGenericCollectionType(targetType);
            var isDictType = IsDictionaryType(targetType, out _, out _);
            if (!isCollectionType && !isDictType && targetType != typeof(object))
                throw new AcJsonDeserializationException($"JSON is an array but target type '{targetType.Name}' is not a collection type.", json, targetType);
        }
        else if (isObjectJson)
        {
            var isCollectionType = targetType.IsArray || IsGenericCollectionType(targetType);
            var isDictType = IsDictionaryType(targetType, out _, out _);
            if (isCollectionType && !isDictType)
                throw new AcJsonDeserializationException($"JSON is an object but target type '{targetType.Name}' is a collection type (not dictionary).", json, targetType);
        }
    }

    #endregion

    #region Chain API

    /// <summary>
    /// Create a deserialize chain that parses JSON once and allows multiple deserializations.
    /// Maintains reference identity for IId objects across chain operations.
    /// </summary>
    public static IDeserializeChain<T> CreateDeserializeChain<T>(string json)
        => CreateDeserializeChain<T>(json, AcJsonSerializerOptions.Default);

    /// <summary>
    /// Create a deserialize chain with options.
    /// </summary>
    public static IDeserializeChain<T> CreateDeserializeChain<T>(string json, in AcJsonSerializerOptions options)
    {
        if (string.IsNullOrEmpty(json) || json == "null")
            return EmptyDeserializeChain<T>.Instance;

        var targetType = typeof(T);
        ValidateJson(json, targetType);

        var doc = JsonDocument.Parse(json);
        var chainTracker = new AcSerializerCommon.ChainReferenceTracker();
        var context = JsonDeserializationContextPool.Get(options);
        context.ChainTracker = chainTracker;

        try
        {
            var result = ReadValue(doc.RootElement, targetType, context);
            context.ResolveReferences();
            return new JsonDeserializeChain<T>(doc, context, chainTracker, (T?)result);
        }
        catch
        {
            JsonDeserializationContextPool.Return(context, options);
            doc.Dispose();
            throw;
        }
    }

    /// <summary>
    /// Internal helper: populate object from parsed JsonDocument.
    /// Reuses existing populate logic without reparsing.
    /// </summary>
    private static void PopulateFromDocument(in JsonElement rootElement, object target, in Type targetType, DeserializationContext context)
    {
        if (rootElement.ValueKind == JsonValueKind.Array)
        {
            if (target is IList targetList)
                PopulateList(rootElement, targetList, targetType, context);
            else
                throw new AcJsonDeserializationException($"Cannot populate non-list target '{targetType.Name}' with JSON array", null, targetType);
        }
        else if (rootElement.ValueKind == JsonValueKind.Object)
        {
            var metadata = GetTypeMetadata(targetType);
            PopulateObjectInternalMerge(rootElement, target, metadata, context);
        }
        else
            throw new AcJsonDeserializationException($"Cannot populate object with JSON value of kind '{rootElement.ValueKind}'", null, targetType);
    }

    #endregion

    #region Chain Implementations (Nested Classes)

    /// <summary>
    /// JSON implementation of deserialize chain.
    /// Maintains reference identity for IId objects across chain operations.
    /// </summary>
    private sealed class JsonDeserializeChain<T> : IDeserializeChain<T>
    {
        private JsonDocument? _document;
        private DeserializationContext? _context;
        private readonly AcSerializerCommon.ChainReferenceTracker _chainTracker;
        private bool _isDisposed;

        public T? Value { get; }

        public JsonDeserializeChain(JsonDocument document, DeserializationContext context, AcSerializerCommon.ChainReferenceTracker chainTracker, T? value)
        {
            _document = document;
            _context = context;
            _chainTracker = chainTracker;
            Value = value;
        }

        public TResult? ThenDeserialize<TResult>()
        {
            ThrowIfDisposed();
            if (_document == null || _context == null)
                throw new ObjectDisposedException(nameof(JsonDeserializeChain<T>));

            var targetType = typeof(TResult);

            try
            {
                var result = ReadValue(_document.RootElement, targetType, _context);
                _context.ResolveReferences();
                return (TResult?)result;
            }
            catch (AcJsonDeserializationException) { throw; }
            catch (Exception ex)
            {
                throw new AcJsonDeserializationException(
                    $"Failed to deserialize to type '{targetType.Name}' in chain: {ex.Message}",
                    null, targetType, ex);
            }
        }

        public IDeserializeChain<T> ThenPopulate(object target)
        {
            ArgumentNullException.ThrowIfNull(target);
            ThrowIfDisposed();
            if (_document == null || _context == null)
                throw new ObjectDisposedException(nameof(JsonDeserializeChain<T>));

            var targetType = target.GetType();

            try
            {
                PopulateFromDocument(_document.RootElement, target, targetType, _context);
                _context.ResolveReferences();
                return this;
            }
            catch (AcJsonDeserializationException) { throw; }
            catch (Exception ex)
            {
                throw new AcJsonDeserializationException(
                    $"Failed to populate object of type '{targetType.Name}' in chain: {ex.Message}",
                    null, targetType, ex);
            }
        }

        private void ThrowIfDisposed()
        {
            ObjectDisposedException.ThrowIf(_isDisposed, this);
        }

        public void Dispose()
        {
            if (_isDisposed) return;
            _isDisposed = true;
            _chainTracker.Clear();
            
            if (_context != null)
            {
                JsonDeserializationContextPool.Return(_context, _context.Options);
                _context = null;
            }
            _document?.Dispose();
            _document = null;
        }
    }

    #endregion
}
