using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
using NLog;
using Quadarax.Foundation.Core.Attributes;
using Quadarax.Foundation.Core.Reflection;
using Quadarax.Foundation.Core.Reflection.Extensions;
using ILogger = NLog.ILogger;

namespace Quadarax.Foundation.Core.Business.Injection
{
    /// <summary>
    /// Register modules inside defined assemblies. Modules must be flagged by <see cref="DiModuleAttribute"/> and <see cref="DiImplementsOfAttribute"/>
    /// </summary>
    public static class DiManager
    {
        #region *** Private fields ***
        private static IList<Assembly> _registeredAssemblies = new List<Assembly>();
        private static ILogger _logger = LogManager.GetLogger(nameof(DiManager));
        #endregion

        #region *** Public properties ***
        public static string[] RegisteredAssembliesNames =>
            _registeredAssemblies.Select(x => x.GetName().ToString()).ToArray();
        #endregion 

        #region  *** Public Operations ***
        /// <summary>
        /// Register all assemblies from current appdomain.
        /// </summary>
        public static void RegisterAssemblyFromThisAppdomain()
        {
            RegisterAssemblyFromThisAppdomain(null, null);
        }
        /// <summary>
        /// Register all assemblies from current appdomain by filtering options.
        /// </summary>
        /// <param name="includedStartsWith"></param>
        /// <param name="excludedStartsWith"></param>
        public static void RegisterAssemblyFromThisAppdomain([AllowNull]IEnumerable<string> includedStartsWith,[AllowNull] IEnumerable<string> excludedStartsWith)
        {
            includedStartsWith ??= new List<string>();
            excludedStartsWith ??= new List<string>();

            foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
            {
                var isRegister = includedStartsWith.Any() && includedStartsWith.Any(x => assembly.GetName().ToString().StartsWith(x));
                isRegister = isRegister && !excludedStartsWith.Any(x => assembly.GetName().ToString().StartsWith(x));
                if (isRegister)
                    RegisterAssembly(assembly);
            }
        }


        /// <summary>
        /// Register an assembly to scan and find modules flagged by <see cref="DiModuleAttribute"/>
        /// </summary>
        /// <param name="assembly"></param>
        public static void RegisterAssembly([NotNull]Assembly assembly)
        {
            if (assembly == null)
                throw new ArgumentNullException(nameof(assembly));

            if (_registeredAssemblies.Contains(assembly)) 
                return;

            _registeredAssemblies.Add(assembly);
            var logMessage = $"DiManager register assembly: {assembly.GetName()}";
            _logger.Debug(logMessage);
            Trace.WriteLine(logMessage,"DI");
        }

        /// <summary>
        /// Scans registered assemblies and register them to <see cref="IServiceCollection"/>
        /// </summary>
        /// <param name="services">Service definition collection.</param>
        public static void ScanAndRegisterDependencyInjections([NotNull]IServiceCollection services)
        {
            int cntAssemblies = 0;
            int cntTypes = 0;
            int cntRegistrations = 0;
            foreach (var assembly in _registeredAssemblies)
            {
                cntAssemblies++;
                var logMessage = $"Scanning assembly '{assembly.GetName()}' ...";
                _logger.Debug(logMessage);
                Trace.WriteLine(logMessage,"DI");
                var types = assembly.GetTypesWithAttribute<DiModuleAttribute>(true);
                foreach (var type in types)
                {
                    cntTypes++;
                    var attributes = AttributeQuery<DiImplementsOfAttribute>.FindAll(type);
                    foreach (var attribute in attributes)
                    {
                        switch (attribute.LifeCycleType)
                        {
                            case LifeCycleTypeEnum.Scoped:
                                services.AddScoped(attribute.InterfaceTypeImplementing, type);
                                break;
                            case LifeCycleTypeEnum.Singleton:
                                services.AddSingleton(attribute.InterfaceTypeImplementing, type);
                                break;
                            case LifeCycleTypeEnum.Transient:
                                services.AddTransient(attribute.InterfaceTypeImplementing, type);
                                break;
                            default:
                                throw new NotSupportedException($"Not supported registration for LifeCycleType = {attribute.LifeCycleType}");
                        }

                        cntRegistrations++;
                        logMessage = $"DI registered: Type={attribute.InterfaceTypeImplementing.FullName} for Type={type.Name} as LifeCycle={attribute.LifeCycleType}";
                        _logger.Info(logMessage);
                        Trace.WriteLine(logMessage,"DI");

                    }
                }
            }
            var logMessage2 = $"DI registered. Assemblies={cntAssemblies}, Types={cntTypes}, Registrations={cntRegistrations}.";
            _logger.Info(logMessage2);
            Trace.WriteLine(logMessage2,"DI");

        }
        #endregion
    }
}
