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

namespace AyCode.Core.Serializers.Jsons;

/// <summary>
/// High-performance custom JSON serializer optimized for IId&lt;T&gt; reference handling.
/// Uses Utf8JsonWriter for high-performance UTF-8 output (STJ approach).
/// Supports Expression tree serialization.
/// </summary>
public static partial class AcJsonSerializer
{
    // Pre-encoded property names for $id/$ref (STJ optimization)
    private static readonly JsonEncodedText IdPropertyEncoded = JsonEncodedText.Encode("$id");
    private static readonly JsonEncodedText RefPropertyEncoded = JsonEncodedText.Encode("$ref");
    
    /// <summary>
    /// Serialize object to JSON string with default options.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static string Serialize<T>(T value) => Serialize(value, AcJsonSerializerOptions.Default);

    /// <summary>
    /// Serialize object to JSON string with specified options.
    /// </summary>
    public static string Serialize<T>(T value, in AcJsonSerializerOptions options)
    {
        if (value == null) return "null";
        
        var type = value.GetType();
        
        // Handle IQueryable types - convert to AcExpressionNode (serialize the Expression)
        object actualValue = value;
        if (value is IQueryable queryable)
        {
            actualValue = AcSerializerCommon.QueryableToNode(queryable);
            type = typeof(AcExpressionNode);
        }
        // Handle Expression types - convert to AcExpressionNode
        else if (AcSerializerCommon.IsExpressionType(type))
        {
            actualValue = AcExpressionConverter.ToNode((Expression)(object)value);
            type = typeof(AcExpressionNode);
        }
        
        if (TrySerializePrimitiveRuntime(actualValue, type, out var primitiveJson))
            return primitiveJson;
        
        var context = SerializationContextPool.Get(options);
        try
        {
            if (options.ReferenceHandling != ReferenceHandlingMode.None)
                ScanReferences(actualValue, context, 0);

            WriteValue(actualValue, context, 0);
            return context.GetResult();
        }
        finally
        {
            if (context.Options.UseAsync) SerializationContextPool.ReturnAsync(context);
            else SerializationContextPool.Return(context);
        }
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private static bool TrySerializePrimitiveRuntime(object value, in Type type, out string json)
    {
        var underlyingType = Nullable.GetUnderlyingType(type) ?? type;
        var typeCode = Type.GetTypeCode(underlyingType);
        
        switch (typeCode)
        {
            case TypeCode.String: json = SerializeString((string)value); return true;
            case TypeCode.Int32: json = ((int)value).ToString(CultureInfo.InvariantCulture); return true;
            case TypeCode.Int64: json = ((long)value).ToString(CultureInfo.InvariantCulture); return true;
            case TypeCode.Boolean: json = (bool)value ? "true" : "false"; return true;
            case TypeCode.Double:
                var d = (double)value;
                json = double.IsNaN(d) || double.IsInfinity(d) ? "null" : d.ToString("G17", CultureInfo.InvariantCulture);
                return true;
            case TypeCode.Decimal: json = ((decimal)value).ToString(CultureInfo.InvariantCulture); return true;
            case TypeCode.Single:
                var f = (float)value;
                json = float.IsNaN(f) || float.IsInfinity(f) ? "null" : f.ToString("G9", CultureInfo.InvariantCulture);
                return true;
            case TypeCode.DateTime: json = $"\"{((DateTime)value).ToString("O", CultureInfo.InvariantCulture)}\""; return true;
            case TypeCode.Byte: json = ((byte)value).ToString(CultureInfo.InvariantCulture); return true;
            case TypeCode.Int16: json = ((short)value).ToString(CultureInfo.InvariantCulture); return true;
            case TypeCode.UInt16: json = ((ushort)value).ToString(CultureInfo.InvariantCulture); return true;
            case TypeCode.UInt32: json = ((uint)value).ToString(CultureInfo.InvariantCulture); return true;
            case TypeCode.UInt64: json = ((ulong)value).ToString(CultureInfo.InvariantCulture); return true;
            case TypeCode.SByte: json = ((sbyte)value).ToString(CultureInfo.InvariantCulture); return true;
            case TypeCode.Char: json = SerializeString(value.ToString()!); return true;
        }
        
        if (ReferenceEquals(underlyingType, GuidType)) { json = $"\"{((Guid)value).ToString("D")}\""; return true; }
        if (ReferenceEquals(underlyingType, DateTimeOffsetType)) { json = $"\"{((DateTimeOffset)value).ToString("O", CultureInfo.InvariantCulture)}\""; return true; }
        if (ReferenceEquals(underlyingType, TimeSpanType)) { json = $"\"{((TimeSpan)value).ToString("c", CultureInfo.InvariantCulture)}\""; return true; }
        if (underlyingType.IsEnum) { json = Convert.ToInt32(value).ToString(CultureInfo.InvariantCulture); return true; }
        
        json = "";
        return false;
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private static string SerializeString(string value)
    {
        if (!NeedsEscaping(value)) return string.Concat("\"", value, "\"");
        
        var sb = new StringBuilder(value.Length + 8);
        sb.Append('"');
        WriteEscapedString(sb, value);
        sb.Append('"');
        return sb.ToString();
    }

    #region Reference Scanning

    private static void ScanReferences(object? value, JsonSerializationContext context, int depth)
    {
        if (value == null) return;
        
        var type = value.GetType();
        if (IsPrimitiveOrStringFast(type)) return;
        
        // Get wrapper early to use IId-aware tracking
        var wrapper = context.GetWrapper(type);
        var metadata = wrapper.Metadata;
        
        // Use IId-aware scanning if available
        if (!context.TrackForScanningWithIId(value, metadata)) return;
        
        if (value is IEnumerable enumerable && !ReferenceEquals(type, StringType))
        {
            foreach (var item in enumerable)
                if (item != null) ScanReferences(item, context, depth + 1);
            return;
        }
        
        var props = metadata.Properties;
        var propCount = props.Length;
        for (var i = 0; i < propCount; i++)
        {
            var propValue = props[i].GetValue(value);
            if (propValue != null) ScanReferences(propValue, context, depth + 1);
        }
    }

    #endregion

    #region Serialization

    private static void WriteValue(object? value, JsonSerializationContext context, int depth)
    {
        if (value == null) { context.Writer.WriteNullValue(); return; }
        
        var type = value.GetType();
        
        if (TryWritePrimitive(value, type, context.Writer)) return;
        
        if (value is IDictionary dictionary) { WriteDictionary(dictionary, context, depth); return; }
        if (value is IEnumerable enumerable && !ReferenceEquals(type, StringType)) { WriteArray(enumerable, context, depth); return; }
        
        WriteObject(value, type, context, depth);
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private static void WriteObject(object value, in Type type, JsonSerializationContext context, int depth)
    {
        var writer = context.Writer;
        var wrapper = context.GetWrapper(type);
        var metadata = wrapper.Metadata;
        var useReferenceHandling = context.UseTypeReferenceHandling(metadata);

        // Use IId-aware reference handling
        if (useReferenceHandling && context.TryGetExistingRefForIId(value, metadata, out var refId)) 
        { 
            writer.WriteStartObject();
            writer.WriteString(RefPropertyEncoded, refId.ToString(CultureInfo.InvariantCulture));
            writer.WriteEndObject();
            return; 
        }
        
        writer.WriteStartObject();
        
        if (useReferenceHandling && context.ShouldWriteIdForIId(value, metadata, out var id))
        {
            writer.WriteString(IdPropertyEncoded, id.ToString(CultureInfo.InvariantCulture));
            context.MarkAsWrittenForIId(value, metadata, id);
        }
        
        var props = metadata.Properties;
        var propCount = props.Length;
        var nextDepth = depth + 1;
        
        for (var i = 0; i < propCount; i++)
        {
            var prop = props[i];
            var propValue = prop.GetValue(value);
            if (propValue == null) continue;
            if (IsDefaultValueFast(propValue, prop.PropertyTypeCode, prop.PropertyType)) continue;
            
            writer.WritePropertyName(prop.JsonNameEncoded);
            WriteValue(propValue, context, nextDepth);
        }
        
        writer.WriteEndObject();
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private static void WriteArray(IEnumerable enumerable, JsonSerializationContext context, int depth)
    {
        var writer = context.Writer;
        writer.WriteStartArray();
        var nextDepth = depth + 1;
        
        foreach (var item in enumerable)
        {
            WriteValue(item, context, nextDepth);
        }
        
        writer.WriteEndArray();
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private static void WriteDictionary(IDictionary dictionary, JsonSerializationContext context, int depth)
    {
        var writer = context.Writer;
        writer.WriteStartObject();
        var nextDepth = depth + 1;
        
        foreach (DictionaryEntry entry in dictionary)
        {
            writer.WritePropertyName(entry.Key?.ToString() ?? "");
            WriteValue(entry.Value, context, nextDepth);
        }
        
        writer.WriteEndObject();
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private static bool TryWritePrimitive(object value, in Type type, Utf8JsonWriter writer)
    {
        var underlyingType = Nullable.GetUnderlyingType(type) ?? type;
        var typeCode = Type.GetTypeCode(underlyingType);
        
        switch (typeCode)
        {
            case TypeCode.String: writer.WriteStringValue((string)value); return true;
            case TypeCode.Int32: writer.WriteNumberValue((int)value); return true;
            case TypeCode.Int64: writer.WriteNumberValue((long)value); return true;
            case TypeCode.Boolean: writer.WriteBooleanValue((bool)value); return true;
            case TypeCode.Double: 
                var d = (double)value;
                if (double.IsNaN(d) || double.IsInfinity(d)) writer.WriteNullValue();
                else writer.WriteNumberValue(d);
                return true;
            case TypeCode.Decimal: writer.WriteNumberValue((decimal)value); return true;
            case TypeCode.Single:
                var f = (float)value;
                if (float.IsNaN(f) || float.IsInfinity(f)) writer.WriteNullValue();
                else writer.WriteNumberValue(f);
                return true;
            case TypeCode.DateTime: writer.WriteStringValue((DateTime)value); return true;
            case TypeCode.Byte: writer.WriteNumberValue((byte)value); return true;
            case TypeCode.Int16: writer.WriteNumberValue((short)value); return true;
            case TypeCode.UInt16: writer.WriteNumberValue((ushort)value); return true;
            case TypeCode.UInt32: writer.WriteNumberValue((uint)value); return true;
            case TypeCode.UInt64: writer.WriteNumberValue((ulong)value); return true;
            case TypeCode.SByte: writer.WriteNumberValue((sbyte)value); return true;
            case TypeCode.Char: writer.WriteStringValue(value.ToString()); return true;
        }
        
        if (ReferenceEquals(underlyingType, GuidType)) { writer.WriteStringValue((Guid)value); return true; }
        if (ReferenceEquals(underlyingType, DateTimeOffsetType)) { writer.WriteStringValue((DateTimeOffset)value); return true; }
        if (ReferenceEquals(underlyingType, TimeSpanType)) { writer.WriteStringValue(((TimeSpan)value).ToString("c", CultureInfo.InvariantCulture)); return true; }
        if (underlyingType.IsEnum) { writer.WriteNumberValue(Convert.ToInt32(value)); return true; }
        
        return false;
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private static bool IsDefaultValueFast(object value, TypeCode typeCode, in Type propertyType)
    {
        switch (typeCode)
        {
            case TypeCode.Int32: return (int)value == 0;
            case TypeCode.Int64: return (long)value == 0L;
            case TypeCode.Double: return (double)value == 0.0;
            case TypeCode.Decimal: return (decimal)value == 0m;
            case TypeCode.Single: return (float)value == 0f;
            case TypeCode.Byte: return (byte)value == 0;
            case TypeCode.Int16: return (short)value == 0;
            case TypeCode.UInt16: return (ushort)value == 0;
            case TypeCode.UInt32: return (uint)value == 0;
            case TypeCode.UInt64: return (ulong)value == 0;
            case TypeCode.SByte: return (sbyte)value == 0;
            case TypeCode.Boolean: return (bool)value == false;
            case TypeCode.String: return string.IsNullOrEmpty((string)value);
        }
        
        if (propertyType.IsEnum) return Convert.ToInt32(value) == 0;
        if (ReferenceEquals(propertyType, GuidType)) return (Guid)value == Guid.Empty;
        
        return false;
    }

    #endregion
}
