using System.Collections;
using System.Reflection;
using static AyCode.Core.Helpers.JsonUtilities;

namespace AyCode.Core.Serializers.Toons;

public static partial class AcToonSerializer
{
    /// <summary>
    /// Write meta section only (for MetaOnly mode).
    /// </summary>
    private static void WriteMetaSectionOnly(Type type, ToonSerializationContext context, string domainContext)
    {
        WriteMetaSection(type, context, domainContext);
    }

    /// <summary>
    /// Write @meta and @types sections for a single root type.
    /// </summary>
    private static void WriteMetaSection(Type type, ToonSerializationContext context, string domainContext)
    {
        if (!context.Options.UseMeta) return;

        // Collect all types that need metadata
        var typesToDocument = new HashSet<Type>();
        CollectTypes(type, typesToDocument);

        WriteMetaSectionCore(typesToDocument, context, domainContext);
    }

    /// <summary>
    /// Write @meta and @types sections for a collection of types.
    /// </summary>
    private static void WriteMetaSection(IEnumerable<Type> types, ToonSerializationContext context, string domainContext)
    {
        if (!context.Options.UseMeta) return;

        // Collect all types that need metadata (including nested types)
        var typesToDocument = new HashSet<Type>();
        foreach (var type in types)
        {
            CollectTypes(type, typesToDocument);
        }

        WriteMetaSectionCore(typesToDocument, context, domainContext);
    }

    /// <summary>
    /// Core logic for writing @meta and @types sections.
    /// </summary>
    private static void WriteMetaSectionCore(HashSet<Type> typesToDocument, ToonSerializationContext context, string domainContext)
    {
        // @meta header
        context.WriteLine("@meta {");
        context.CurrentIndentLevel++;
        context.WriteProperty("version", $"\"{FormatVersion}\"");
        context.WriteProperty("format", "\"toon\"");
        context.WriteProperty("source-code-language", "\"C#\"");

        if(!string.IsNullOrEmpty(domainContext))
            context.WriteProperty("context", $"\"{domainContext}\"");

        // Write type list
        context.WriteIndent();
        context.Write("types");

        // Token optimization: no spaces around '=' when indentation is disabled
        if (context.Options.UseIndentation)
        {
            context.Write(" = ");
        }
        else
        {
            context.Write('=');
        }

        // Szűrés: csak komplex típusok (class/struct) és enum-ok, primitívek nélkül
        var filteredTypes = typesToDocument
            .Where(t => t.IsEnum || !IsPrimitiveOrStringFast(t)) // Enum-ok megtartása, primitívek és string kizárása
            .Where(t => !typeof(IEnumerable).IsAssignableFrom(t) || t == typeof(string))
            .Where(t => !t.IsGenericType || t.IsGenericTypeDefinition)
            .Where(t => !t.Name.StartsWith("List`", StringComparison.Ordinal) && !t.Name.StartsWith("ICollection`", StringComparison.Ordinal) && !t.Name.StartsWith("IEnumerable`", StringComparison.Ordinal) && !t.Name.StartsWith("Dictionary`", StringComparison.Ordinal))
            .ToList();

        // DAG topological sort: dependencies first
        var sortedTypes = TopologicalSortTypes(filteredTypes);

        context.Write("[");
        var first = true;
        foreach (var t in sortedTypes)
        {
            if (!first) context.Write(", ");
            context.Write($"\"{t.Name}\"");
            first = false;
        }
        context.WriteLine("]");

        context.CurrentIndentLevel--;
        context.WriteLine("}");

        // Token optimization: skip empty line when indentation is disabled
        if (context.Options.UseIndentation)
        {
            context.WriteLine();
        }

        // @types section with descriptions (same sorted order)
        context.WriteLine("@types {");
        context.CurrentIndentLevel++;

        foreach (var t in sortedTypes)
        {
            WriteTypeDefinition(t, context);
        }

        context.CurrentIndentLevel--;
        context.WriteLine("}");
    }

    /// <summary>
    /// Recursively collects all types that need metadata documentation.
    /// Uses the same logic as AcBinarySerializer.RegisterMetadataForType.
    /// </summary>
    private static void CollectTypes(Type type, HashSet<Type> types)
    {
        var underlyingType = Nullable.GetUnderlyingType(type) ?? type;

        // Enum-ok: hozzáadjuk és return (nem traversáljuk tovább)
        if (underlyingType.IsEnum)
        {
            types.Add(underlyingType);
            return;
        }

        // Primitívek és string: kihagyjuk
        if (IsPrimitiveOrStringFast(underlyingType)) return;

        if (!types.Add(underlyingType)) return; // Already processed

        // Handle dictionaries FIRST (before generic IEnumerable check)
        if (IsDictionaryType(underlyingType, out var keyType, out var valueType))
        {
            if (keyType != null) CollectTypes(keyType, types);
            if (valueType != null) CollectTypes(valueType, types);
            return;
        }

        // Handle collections/arrays (matches AcBinarySerializer logic)
        if (typeof(IEnumerable).IsAssignableFrom(underlyingType) && !ReferenceEquals(underlyingType, StringType))
        {
            var elementType = GetCollectionElementType(underlyingType);
            if (elementType != null)
            {
                CollectTypes(elementType, types);
            }
            return;
        }

        // Handle object properties (traverse type graph)
        var metadata = GetTypeMetadata(underlyingType);

        // Detektáljuk az enum backing field-eket (NotMapped enum property-k)
        DetectAndCollectEnumBackingFields(underlyingType, types);

        foreach (var prop in metadata.Properties)
        {
            CollectTypes(prop.PropertyType, types);
        }
    }

    /// <summary>
    /// Detektálja az enum backing field-eket és hozzáadja az enum típusokat a types hash set-hez.
    /// Logika: Ha van egy enum property (bármilyen) és van egy {PropertyName}Id int property,
    /// akkor az enum típust hozzáadjuk.
    /// </summary>
    private static void DetectAndCollectEnumBackingFields(Type type, HashSet<Type> types)
    {
        var allProperties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);

        foreach (var prop in allProperties)
        {
            // Csak enum property-ket vizsgálunk
            if (!prop.PropertyType.IsEnum) continue;

            // Keressük meg a {PropertyName}Id backing field-et
            var backingFieldName = $"{prop.Name}Id";
            var backingField = allProperties.FirstOrDefault(p =>
                p.Name == backingFieldName &&
                (p.PropertyType == typeof(int) || p.PropertyType == typeof(int?)));

            if (backingField != null)
            {
                // Megtaláltuk az enum backing field párt → hozzáadjuk az enum típust
                types.Add(prop.PropertyType);
            }
        }
    }

    /// <summary>
    /// Megkeresi a property-hez tartozó enum típust, ha az egy enum backing field.
    /// Visszaadja az enum típust CSAK akkor, ha:
    /// 1. Van megfelelő nevű enum property (naming convention)
    /// 2. Az enum property NotMapped/NotColumn/JsonIgnore attribútummal van ellátva
    /// 3. A backing field típusa megegyezik az enum underlying type-jával (int/int?, byte/byte?, stb.)
    /// </summary>
    private static Type? FindEnumTypeForBackingField(PropertyInfo backingFieldProperty, Type declaringType)
    {
        // Ha a property neve "...Id"-re végződik, keressük meg az enum property-t
        if (!backingFieldProperty.Name.EndsWith("Id", StringComparison.Ordinal))
            return null;

        var enumPropertyName = backingFieldProperty.Name.Substring(0, backingFieldProperty.Name.Length - 2);
        var allProperties = declaringType.GetProperties(BindingFlags.Public | BindingFlags.Instance);

        var enumProperty = allProperties.FirstOrDefault(p =>
            p.Name == enumPropertyName &&
            (p.PropertyType.IsEnum || (Nullable.GetUnderlyingType(p.PropertyType)?.IsEnum ?? false)));

        if (enumProperty == null) return null;

        // Ellenőrizzük, hogy NotMapped/NotColumn/JsonIgnore van-e rajta
        var isNotMapped = enumProperty.GetCustomAttributes(inherit: true).Any(attr =>
        {
            var attrName = attr.GetType().Name;
            return attrName == "NotMappedAttribute" ||
                   attrName == "NotColumnAttribute" ||
                   attrName == "JsonIgnoreAttribute";
        });

        // Csak akkor backing field, ha az enum property NotMapped!
        if (!isNotMapped) return null;

        // Ellenőrizzük, hogy a backing field típusa megegyezik-e az enum underlying type-jával
        var enumType = Nullable.GetUnderlyingType(enumProperty.PropertyType) ?? enumProperty.PropertyType;
        var enumUnderlyingType = Enum.GetUnderlyingType(enumType);

        // A backing field lehet nullable vagy non-nullable is
        var backingUnderlyingType = Nullable.GetUnderlyingType(backingFieldProperty.PropertyType)
                                    ?? backingFieldProperty.PropertyType;

        // Az underlying type-oknak meg kell egyezniük
        if (backingUnderlyingType != enumUnderlyingType)
            return null;

        // Visszaadjuk az enum típust (nullable nélkül)
        return enumType;
    }
}
