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

namespace AyCode.Core.Serializers.Toons;

public static partial class AcToonSerializer
{
    /// <summary>
    /// Detects navigation property metadata for a property with fallback chain.
    /// Priority: ToonDescription -> EF Core/Linq2Db attributes -> Convention-based detection.
    /// Returns cached AcNavigationPropertyInfo with all relationship details.
    /// </summary>
    internal static AcNavigationPropertyInfo DetectNavigationMetadata(PropertyInfo property, ToonDescriptionAttribute? customDescription)
    {
        // 1. IsPrimaryKey - Priority: ToonDescription -> EF Core [Key] -> Linq2Db [PrimaryKey] -> Convention
        bool isPrimaryKey;
        if (customDescription?.IsPrimaryKey.HasValue == true)
        {
            isPrimaryKey = customDescription.IsPrimaryKey.Value;
        }
        else if (TryGetEFCoreKey(property) || TryGetLinq2DbPrimaryKey(property))
        {
            isPrimaryKey = true;
        }
        else
        {
            isPrimaryKey = IsConventionPrimaryKey(property);
        }

        // 2. Navigation type detection (needed early for FK detection logic)
        ToonRelationType? navigationType = null;
        if (customDescription?.Navigation.HasValue == true)
        {
            navigationType = customDescription.Navigation.Value;
        }
        else
        {
            var linq2dbNav = TryGetLinq2DbNavigation(property);
            if (linq2dbNav.HasValue)
            {
                navigationType = linq2dbNav.Value;
            }
            else
            {
                navigationType = DetectConventionNavigation(property);
            }
        }

        // 3. ForeignKey - Priority: ToonDescription -> EF Core [ForeignKey] -> Linq2Db [Association] -> Convention
        string? foreignKey = null;
        if (!string.IsNullOrEmpty(customDescription?.ForeignKey))
        {
            foreignKey = customDescription.ForeignKey;
        }
        else
        {
            var efCoreFk = TryGetEFCoreForeignKey(property);
            if (!string.IsNullOrEmpty(efCoreFk))
            {
                foreignKey = efCoreFk;
            }
            else
            {
                // For FK properties (int/Guid/etc ending with Id)
                foreignKey = DetectConventionForeignKey(property);

                // For navigation properties (ManyToOne/OneToOne), find the FK property name
                if (string.IsNullOrEmpty(foreignKey) &&
                    (navigationType == ToonRelationType.ManyToOne || navigationType == ToonRelationType.OneToOne))
                {
                    foreignKey = DetectForeignKeyForNavigationProperty(property);
                }
            }
        }

        // 4. InverseProperty - Priority: ToonDescription -> EF Core [InverseProperty] -> Convention
        string? inverseProperty = null;
        if (!string.IsNullOrEmpty(customDescription?.InverseProperty))
        {
            inverseProperty = customDescription.InverseProperty;
        }
        else
        {
            var efCoreInverse = TryGetEFCoreInverseProperty(property);
            if (!string.IsNullOrEmpty(efCoreInverse))
            {
                inverseProperty = efCoreInverse;
            }
            else
            {
                // Fallback: convention-based detection
                inverseProperty = DetectConventionInverseProperty(property, navigationType);
            }
        }

        // 5. OtherKey - Detect FK on the other side (for OneToMany)
        string? otherKey = DetectOtherKey(property, navigationType, inverseProperty);

        // 6. TargetType - Extract target entity type
        Type? targetType = null;
        if (navigationType == ToonRelationType.OneToMany || navigationType == ToonRelationType.ManyToMany)
        {
            targetType = GetCollectionElementType(property.PropertyType);
        }
        else if (navigationType == ToonRelationType.ManyToOne || navigationType == ToonRelationType.OneToOne)
        {
            targetType = property.PropertyType;
        }

        return new AcNavigationPropertyInfo(
            isPrimaryKey: isPrimaryKey,
            navigationType: navigationType,
            foreignKey: foreignKey,
            otherKey: otherKey,
            inverseProperty: inverseProperty,
            targetType: targetType);
    }

    /// <summary>
    /// Convention: Detect navigation type based on property type.
    /// Type-based validation with FK lookup:
    /// - ICollection&lt;T&gt; or List&lt;T&gt; -> OneToMany (if T has primary key)
    /// - Complex object type -> ManyToOne (if has corresponding FK property and has primary key)
    /// </summary>
    private static ToonRelationType? DetectConventionNavigation(PropertyInfo property)
    {
        var propertyType = property.PropertyType;

        // Skip primitive types and strings
        if (IsPrimitiveOrStringFast(propertyType))
        {
            return null;
        }

        // Check for collection types (OneToMany)
        if (typeof(IEnumerable).IsAssignableFrom(propertyType) && propertyType != typeof(string))
        {
            // Extract element type from collection
            var elementType = GetCollectionElementType(propertyType);
            if (elementType != null && HasPrimaryKeyProperty(elementType))
            {
                // It's a collection of entities -> OneToMany
                return ToonRelationType.OneToMany;
            }
            return null;
        }

        // Complex object type -> check if it's a navigation property
        // Type-based validation: must have a primary key
        if (!HasPrimaryKeyProperty(propertyType))
        {
            return null;
        }

        // Look for a corresponding foreign key property to confirm it's a navigation
        var declaringType = property.DeclaringType;
        if (declaringType != null)
        {
            var foreignKeyPropertyName = property.Name + "Id";
            var foreignKeyProperty = declaringType.GetProperty(foreignKeyPropertyName, BindingFlags.Public | BindingFlags.Instance);

            // Type-based validation: FK must be a value type
            if (foreignKeyProperty != null && foreignKeyProperty.PropertyType.IsValueType)
            {
                // Found corresponding foreign key -> ManyToOne
                return ToonRelationType.ManyToOne;
            }
        }

        // No corresponding foreign key found
        // Return null to avoid false positives
        return null;
    }

    /// <summary>
    /// Convention: Detect inverse property by looking at the related type's properties.
    /// For OneToMany: Look for ManyToOne property in element type that points back.
    /// For ManyToOne: Look for OneToMany collection in related type that points back.
    /// </summary>
    private static string? DetectConventionInverseProperty(PropertyInfo property, ToonRelationType? navigationType)
    {
        if (!navigationType.HasValue)
            return null;

        var declaringType = property.DeclaringType;
        if (declaringType == null)
            return null;

        if (navigationType == ToonRelationType.OneToMany)
        {
            // OneToMany: look for ManyToOne in element type that points back to declaring type
            var elementType = GetCollectionElementType(property.PropertyType);
            if (elementType == null)
                return null;

            var elementMetadata = GetTypeMetadata(elementType);
            foreach (var elementProp in elementMetadata.Properties)
            {
                if (IsTypeMatch(elementProp.PropertyType, declaringType))
                    return elementProp.Name;
            }
        }
        else if (navigationType == ToonRelationType.ManyToOne)
        {
            // ManyToOne: look for OneToMany collection in related type that points back
            var relatedMetadata = GetTypeMetadata(property.PropertyType);
            foreach (var relatedProp in relatedMetadata.Properties)
            {
                if (typeof(IEnumerable).IsAssignableFrom(relatedProp.PropertyType) &&
                    relatedProp.PropertyType != typeof(string) &&
                    IsTypeMatch(GetCollectionElementType(relatedProp.PropertyType), declaringType))
                {
                    return relatedProp.Name;
                }
            }
        }

        return null;
    }

    /// <summary>
    /// Detect table name for a type with fallback chain.
    /// Priority: ToonDescription.TableName -> EF Core [Table] -> Linq2Db [Table] -> Convention (class name).
    /// </summary>
    internal static string? DetectTableName(Type type, ToonDescriptionAttribute? customDescription)
    {
        // 1. ToonDescription.TableName (explicit override)
        if (!string.IsNullOrEmpty(customDescription?.TableName))
        {
            return customDescription.TableName;
        }

        // 2. EF Core [Table("name")] attribute (primary)
        var efCoreTable = TryGetEFCoreTableName(type);
        if (!string.IsNullOrEmpty(efCoreTable))
        {
            return efCoreTable;
        }

        // 3. Linq2Db [Table(Name = "name")] attribute (if EF Core not found)
        var linq2dbTable = TryGetLinq2DbTableName(type);
        if (!string.IsNullOrEmpty(linq2dbTable))
        {
            return linq2dbTable;
        }

        // 4. Convention: class name (fallback)
        return type.Name;
    }
}
