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

namespace AyCode.Core.Serializers.Toons;

public static partial class AcToonSerializer
{
    /// <summary>
    /// Convention: Property named "{TypeName}Id" with a corresponding "{TypeName}" navigation property is a foreign key.
    /// Type-based validation: checks that navigation property is a complex type (not primitive/string).
    /// Example: "CompanyId" + "Company" property exists and is complex type -> foreign key to "Company".
    /// </summary>
    private static string? DetectConventionForeignKey(PropertyInfo property)
    {
        // Skip non-value types (we don't check specific FK type, could be int/long/Guid/etc)
        if (!property.PropertyType.IsValueType)
        {
            return null;
        }

        // Check if property name ends with "Id"
        if (!property.Name.EndsWith("Id", StringComparison.OrdinalIgnoreCase))
        {
            return null;
        }

        // Extract potential navigation property name
        var navigationPropertyName = property.Name.Substring(0, property.Name.Length - 2);

        // Check if corresponding navigation property exists
        var declaringType = property.DeclaringType;
        if (declaringType == null)
        {
            return null;
        }

        var navigationProperty = declaringType.GetProperty(navigationPropertyName, BindingFlags.Public | BindingFlags.Instance);

        // Type-based validation: navigation property must exist and be a complex type (not primitive/string)
        if (navigationProperty == null || IsPrimitiveOrStringFast(navigationProperty.PropertyType))
        {
            return null;
        }

        // Additional validation: navigation property type should have a primary key (IsPrimaryKey or Id property)
        if (!HasPrimaryKeyProperty(navigationProperty.PropertyType))
        {
            return null;
        }

        return navigationPropertyName;
    }

    /// <summary>
    /// Detect FK property name for a navigation property.
    /// Example: Customer navigation → CustomerId FK.
    /// Also tries target type name: ShippingDocumentFile (type: Files) → FilesId FK.
    /// </summary>
    private static string? DetectForeignKeyForNavigationProperty(PropertyInfo property)
    {
        var declaringType = property.DeclaringType;
        if (declaringType == null)
            return null;

        var allProperties = declaringType.GetProperties(BindingFlags.Public | BindingFlags.Instance);

        // Strategy 1: FK based on navigation property name (e.g., Customer → CustomerId)
        var fk = TryFindForeignKeyPropertyDirect(allProperties, property.Name);
        if (fk != null)
            return fk;

        // Strategy 2: FK based on target type name (e.g., ShippingDocumentFile: Files → FilesId)
        var targetTypeName = property.PropertyType.Name;
        fk = TryFindForeignKeyPropertyDirect(allProperties, targetTypeName);
        if (fk != null)
            return fk;

        return null;
    }

    /// <summary>
    /// Detect the foreign key property name on the other side of the relationship (OtherKey).
    /// For OneToMany: Order.OrderItems → OrderItem has "OrderId" FK.
    /// </summary>
    private static string? DetectOtherKey(PropertyInfo property, ToonRelationType? navigationType, string? inverseProperty)
    {
        if (navigationType != ToonRelationType.OneToMany)
            return null;

        var elementType = GetCollectionElementType(property.PropertyType);
        if (elementType == null)
            return null;

        // Use ReflectedType if available (the type we're reflecting on), otherwise DeclaringType
        var declaringType = property.ReflectedType ?? property.DeclaringType;
        if (declaringType == null)
            return null;

        var elementProperties = elementType.GetProperties(BindingFlags.Public | BindingFlags.Instance);

        // Strategy 1: FK based on inverse property name
        if (!string.IsNullOrEmpty(inverseProperty))
        {
            var fk = TryFindForeignKeyPropertyDirect(elementProperties, inverseProperty);
            if (fk != null) return fk;
        }

        // Strategy 2: FK based on declaring type name (with Dto suffix handling)
        var fkFromType = TryFindForeignKeyPropertyDirect(elementProperties, declaringType.Name);
        if (fkFromType != null) return fkFromType;

        // Strategy 3: FK based on element type name prefix
        // OrderNote.OrderId → "Order" is prefix of "OrderNote", so look for "{prefix}Id"
        var elementTypeName = elementType.Name;
        for (int i = 0; i < elementProperties.Length; i++)
        {
            var prop = elementProperties[i];
            if (!prop.Name.EndsWith("Id", StringComparison.Ordinal) || !prop.PropertyType.IsValueType)
                continue;

            // Extract the prefix: "OrderId" → "Order"
            var fkPrefix = prop.Name.Substring(0, prop.Name.Length - 2);

            // Check if element type name starts with this prefix: "OrderNote".StartsWith("Order")
            if (elementTypeName.StartsWith(fkPrefix, StringComparison.Ordinal) && fkPrefix.Length > 0)
            {
                return prop.Name;
            }
        }

        // Strategy 4: FK that has navigation property pointing back
        for (int i = 0; i < elementProperties.Length; i++)
        {
            var prop = elementProperties[i];
            if (!prop.Name.EndsWith("Id", StringComparison.Ordinal) || !prop.PropertyType.IsValueType)
                continue;

            var navName = prop.Name.Substring(0, prop.Name.Length - 2);
            for (int j = 0; j < elementProperties.Length; j++)
            {
                if (elementProperties[j].Name == navName && IsTypeMatch(elementProperties[j].PropertyType, declaringType))
                    return prop.Name;
            }
        }

        return null;
    }

    /// <summary>
    /// Find FK property by name using direct PropertyInfo array (not filtered by GetSerializableProperties).
    /// Handles "Dto" and "Model" suffix removal for naming conventions.
    /// Example: baseName="OrderDto" → tries "OrderDtoId", then "OrderId"
    /// Example: baseName="OrderModel" → tries "OrderModelId", then "OrderId"
    /// </summary>
    private static string? TryFindForeignKeyPropertyDirect(PropertyInfo[] properties, string baseName)
    {
        // Try "{baseName}Id"
        var fkName = baseName + "Id";
        for (int i = 0; i < properties.Length; i++)
        {
            if (properties[i].Name == fkName && properties[i].PropertyType.IsValueType)
                return fkName;
        }

        // Try without "Dto" suffix: "OrderDto" → "OrderId"
        if (baseName.Length > 3 && baseName.EndsWith("Dto", StringComparison.Ordinal))
        {
            fkName = baseName.Substring(0, baseName.Length - 3) + "Id";
            for (int i = 0; i < properties.Length; i++)
            {
                if (properties[i].Name == fkName && properties[i].PropertyType.IsValueType)
                    return fkName;
            }
        }

        // Try without "Model" suffix: "OrderModel" → "OrderId"
        if (baseName.Length > 5 && baseName.EndsWith("Model", StringComparison.Ordinal))
        {
            fkName = baseName.Substring(0, baseName.Length - 5) + "Id";
            for (int i = 0; i < properties.Length; i++)
            {
                if (properties[i].Name == fkName && properties[i].PropertyType.IsValueType)
                    return fkName;
            }
        }

        return null;
    }
}
