using System.Collections.Concurrent;
using System.Collections.Frozen;
using System.Reflection;
using AyCode.Services.SignalRs;

namespace AyCode.Models.Server.DynamicMethods;

public class AcDynamicMethodCallModel<TAttribute> where TAttribute : TagAttribute
{
    /// <summary>
    /// Statikus cache a típusok metódus metaadataihoz - a reflection csak egyszer fut le típusonként.
    /// Key: Instance típus -> Value: immutable frozen dictionary a metódusokkal messageTag szerint
    /// </summary>
    private static readonly ConcurrentDictionary<Type, FrozenDictionary<int, AcMethodInfoModel<TAttribute>>> _typeMethodCache = new();

    public object InstanceObject { get; init; }
    
    /// <summary>
    /// Immutable dictionary a metódusokkal messageTag szerint. Csak olvasásra használatos.
    /// </summary>
    public FrozenDictionary<int, AcMethodInfoModel<TAttribute>> MethodsByMessageTag { get; init; }

    public AcDynamicMethodCallModel(Type instanceObjectType) : this(instanceObjectType, null!)
    {
    }

    public AcDynamicMethodCallModel(Type instanceObjectType, params object[] constructorParams) : this(Activator.CreateInstance(instanceObjectType, constructorParams)!)
    {
    }

    public AcDynamicMethodCallModel(object instanceObject)
    {
        InstanceObject = instanceObject;
        MethodsByMessageTag = GetOrCreateMethodCache(instanceObject.GetType());
    }

    /// <summary>
    /// Visszaadja a cache-elt metódus metaadatokat, vagy létrehozza őket ha még nincsenek.
    /// A reflection csak egyszer fut le típusonként.
    /// </summary>
    private static FrozenDictionary<int, AcMethodInfoModel<TAttribute>> GetOrCreateMethodCache(Type instanceType)
    {
        return _typeMethodCache.GetOrAdd(instanceType, type =>
        {
            var methods = new Dictionary<int, AcMethodInfoModel<TAttribute>>();

            foreach (var methodInfo in type.GetMethods())
            {
                if (methodInfo.GetCustomAttribute(typeof(TAttribute)) is not TAttribute attribute) continue;

                if (methods.ContainsKey(attribute.MessageTag))
                    throw new Exception($"Multiple SignaRMessageTag! messageTag: {attribute.MessageTag}; methodName: {methodInfo.Name}");

                methods[attribute.MessageTag] = new AcMethodInfoModel<TAttribute>(attribute, methodInfo!);
            }

            return methods.ToFrozenDictionary();
        });
    }
}