using System.Collections;
using System.Reflection;

namespace Models.Helpers
{
    public static class EntityMapper
    {
        public static TDestination MapEntity<TSource, TDestination>(TSource source)
            where TDestination : new()
        {
            var destination = new TDestination();
            var sourceProps = typeof(TSource).GetProperties();
            var destProps = typeof(TDestination).GetProperties();

            foreach (var sourceProp in sourceProps)
            {
                var destProp = destProps.FirstOrDefault(p => p.Name == sourceProp.Name);
                if (destProp == null || !destProp.CanWrite) continue;

                var sourceValue = sourceProp.GetValue(source);

                if (sourceValue == null)
                {
                    destProp.SetValue(destination, null);
                    continue;
                }

                if (IsGenericCollection(destProp.PropertyType) &&
                    IsGenericCollection(sourceProp.PropertyType))
                {
                    var sourceElementType = sourceProp.PropertyType.GenericTypeArguments[0];
                    var destElementType = destProp.PropertyType.GenericTypeArguments[0];

                    var mappedList = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(destElementType))!;

                    foreach (var item in (IEnumerable)sourceValue)
                    {
                        var mapMethod = typeof(EntityMapper)
                            .GetMethod(nameof(MapEntity), BindingFlags.Public | BindingFlags.Static)!
                            .MakeGenericMethod(sourceElementType, destElementType);

                        var mappedItem = mapMethod.Invoke(null, new[] { item });
                        mappedList.Add(mappedItem);
                    }

                    destProp.SetValue(destination, mappedList);
                }
                else if (destProp.PropertyType.IsAssignableFrom(sourceProp.PropertyType))
                {
                    destProp.SetValue(destination, sourceValue);
                }
                else if (!destProp.PropertyType.IsPrimitive &&
                         destProp.PropertyType != typeof(string) &&
                         destProp.PropertyType.GetConstructor(Type.EmptyTypes) != null)
                {
                    // Mapeo de objetos anidados
                    var mapMethod = typeof(EntityMapper)
                        .GetMethod(nameof(MapEntity), BindingFlags.Public | BindingFlags.Static)!
                        .MakeGenericMethod(sourceProp.PropertyType, destProp.PropertyType);

                    var mappedValue = mapMethod.Invoke(null, new[] { sourceValue });
                    destProp.SetValue(destination, mappedValue);
                }
            }

            return destination;
        }
        public static void MapEntityToExisting<TSource, TTarget>(TSource source, TTarget target)
        {
            var sourceProps = typeof(TSource).GetProperties();
            var targetProps = typeof(TTarget).GetProperties();

            foreach (var sourceProp in sourceProps)
            {
                var targetProp = targetProps.FirstOrDefault(p => p.Name == sourceProp.Name && p.PropertyType == sourceProp.PropertyType);
                if (targetProp != null && targetProp.CanWrite)
                {
                    var value = sourceProp.GetValue(source);
                    targetProp.SetValue(target, value);
                }
            }
        }
        private static bool IsGenericCollection(Type type)
        {
            if (!type.IsGenericType) return false;
            var genericDef = type.GetGenericTypeDefinition();
            return typeof(IEnumerable<>).IsAssignableFrom(genericDef) ||
                    type.GetInterfaces().Any(i => i.IsGenericType &&
                        i.GetGenericTypeDefinition() == typeof(IEnumerable<>));
        }
    }
}
