using System;
using System.Linq;
using System.Linq.Expressions;
using System.Net;
using System.Reflection;
using System.Threading;
using ZeroLevel.Network;

namespace ZeroLevel.Services.Applications
{
    public abstract class BaseZeroService
        : IZeroService
    {
        private readonly ZeroServiceInfo _serviceInfo = new ZeroServiceInfo();
        private readonly IExchange _exhange;
        protected IExchange Exchange => _exhange;
        public ZeroServiceInfo ServiceInfo => _serviceInfo;

        public string Name { get { return _serviceInfo.Name; } private set { _serviceInfo.Name = value; } }
        public string Key { get { return _serviceInfo.ServiceKey; } private set { _serviceInfo.ServiceKey = value; } }
        public string Version { get { return _serviceInfo.Version; } private set { _serviceInfo.Version = value; } }
        public string Group { get { return _serviceInfo.ServiceGroup; } private set { _serviceInfo.ServiceGroup = value; } }
        public string Type { get { return _serviceInfo.ServiceType; } private set { _serviceInfo.ServiceType = value; } }

        public ZeroServiceStatus Status => _state;
        private ZeroServiceStatus _state;

        protected BaseZeroService()
        {
            Name = GetType().Name;
            _exhange = new Exchange(this);
        }

        protected BaseZeroService(string name)
        {
            Name = name;
            _exhange = new Exchange(this);
        }

        protected abstract void StartAction();
        protected abstract void StopAction();

        #region Config
        private const string DEFAULT_GROUP_NAME = "__default_group__";
        private const string DEFAULT_TYPE_NAME = "__default_type__";

        public void ReadServiceInfo()
        {
            if (string.IsNullOrWhiteSpace(this.Name))
                this.Name = ReadName();
            if (string.IsNullOrWhiteSpace(this.Key))
                this.Key = ReadKey();
            if (string.IsNullOrWhiteSpace(this.Version))
                this.Version = ReadVersion();
            if (string.IsNullOrWhiteSpace(this.Group))
                this.Group = ReadServiceGroup();
            if (string.IsNullOrWhiteSpace(this.Type))
                this.Type = ReadServiceType();
        }

        public void ReadServiceInfo(IConfigurationSet set)
        {
            if (string.IsNullOrWhiteSpace(this.Name))
                this.Name = ReadName(set);
            if (string.IsNullOrWhiteSpace(this.Key))
                this.Key = ReadKey(set);
            if (string.IsNullOrWhiteSpace(this.Version))
                this.Version = ReadVersion(set);
            if (string.IsNullOrWhiteSpace(this.Group))
                this.Group = ReadServiceGroup(set);
            if (string.IsNullOrWhiteSpace(this.Type))
                this.Type = ReadServiceType(set);
        }

        private string ReadName(IConfigurationSet set = null!)
        {
            return FindInConfig<string>(set, new[] { "ServiceName", "AppName" }, string.Empty, "service")
                ?? this.GetType().Name;
        }

        private string ReadKey(IConfigurationSet set = null!)
        {
            return FindInConfig<string>(set, new[] { "ServiceKey", "AppKey" }, string.Empty, "service");
        }

        private string ReadVersion(IConfigurationSet set = null!)
        {
            return FindInConfig<string>(set, new[] { "Version", "AppVersion" }, string.Empty, "service")
                ?? "1.0";
        }

        private string ReadServiceGroup(IConfigurationSet set = null!)
        {
            return FindInConfig<string>(set, new[] { "DiscoveryGroup", "ServiceGroup" }, string.Empty, "service")
                ?? DEFAULT_GROUP_NAME;
        }

        private string ReadServiceType(IConfigurationSet set = null!)
        {
            return FindInConfig<string>(set, new[] { "DiscoveryType", "ServiceType" }, string.Empty, "service")
                ?? DEFAULT_TYPE_NAME;
        }

        protected T FindInConfig<T>(IConfigurationSet set, string[] keys, params string[] sections)
        {
            var configSet = set ?? Configuration.DefaultSet;
            foreach (var section in sections)
            {
                if (string.IsNullOrWhiteSpace(section))
                {
                    foreach (var key in keys)
                    {
                        if (configSet.Default.Contains(key))
                        {
                            return configSet.Default.First<T>(key);
                        }
                    }
                }
                else if (configSet.ContainsSection(section))
                {
                    foreach (var key in keys)
                    {
                        if (configSet[section].Contains(key))
                        {
                            return configSet[section].First<T>(key);
                        }
                    }
                }
            }
            return default(T)!;
        }
        #endregion Config

        #region Network       

        public bool UseDiscovery()
        {
            if (_state == ZeroServiceStatus.Running
               || _state == ZeroServiceStatus.Initialized)
            {
                ReadServiceInfo();
                return _exhange.UseDiscovery();
            }
            return false;
        }

        public bool UseDiscovery(string endpoint)
        {
            if (_state == ZeroServiceStatus.Running
               || _state == ZeroServiceStatus.Initialized)
            {
                ReadServiceInfo();
                return _exhange.UseDiscovery(endpoint);
            }
            return false;
        }

        public bool UseDiscovery(IPEndPoint endpoint)
        {
            if (_state == ZeroServiceStatus.Running
               || _state == ZeroServiceStatus.Initialized)
            {
                ReadServiceInfo();
                return _exhange.UseDiscovery(endpoint);
            }
            return false;
        }

        public IRouter UseHost()
        {
            if (_state == ZeroServiceStatus.Running
                || _state == ZeroServiceStatus.Initialized)
            {
                return _exhange.UseHost();
            }
            return BaseSocket.NullRouter;
        }

        public IRouter UseHost(int port)
        {
            if (_state == ZeroServiceStatus.Running
                || _state == ZeroServiceStatus.Initialized)
            {
                return _exhange.UseHost(port);
            }
            return BaseSocket.NullRouter;
        }

        public IRouter UseHost(IPEndPoint endpoint)
        {
            if (_state == ZeroServiceStatus.Running
                || _state == ZeroServiceStatus.Initialized)
            {
                return _exhange.UseHost(endpoint);
            }
            return BaseSocket.NullRouter;
        }

        #region Autoregistration inboxes
        private static Delegate CreateDelegate(Type delegateType, MethodInfo methodInfo, object target)
        {
            Func<Type[], Type> getType;
            var isAction = methodInfo.ReturnType.Equals((typeof(void)));
            if (isAction)
            {
                getType = Expression.GetActionType;
            }
            else
            {
                getType = Expression.GetFuncType;
            }
            if (methodInfo.IsStatic)
            {
                return Delegate.CreateDelegate(delegateType, methodInfo);
            }
            return Delegate.CreateDelegate(delegateType, target, methodInfo.Name);
        }

        public void AutoregisterInboxes(IServer server)
        {
            var type = server.GetType();
            // Search router registerinbox methods with inbox name
            var register_methods = type.GetMethods(BindingFlags.Instance
                | BindingFlags.Public
                | BindingFlags.NonPublic
                | BindingFlags.FlattenHierarchy)?
                .Where(mi => mi.Name.Equals("RegisterInbox", StringComparison.Ordinal) &&
                mi.GetParameters().First().ParameterType == typeof(string));

            var register_message_handler = register_methods.First(mi => mi.IsGenericMethod == false);
            var register_message_handler_with_msg = register_methods.First(mi =>
            {
                if (mi.IsGenericMethod)
                {
                    var paremeters = mi.GetParameters().ToArray();
                    if (paremeters.Length == 2 && paremeters[1].ParameterType.IsAssignableToGenericType(typeof(MessageHandler<>)))
                    {
                        return true;
                    }
                }
                return false;
            });
            var register_request_handler_without_msg = register_methods.First(mi =>
            {
                if (mi.IsGenericMethod)
                {
                    var paremeters = mi.GetParameters().ToArray();
                    if (paremeters.Length == 2 && paremeters[1].ParameterType.IsAssignableToGenericType(typeof(RequestHandler<>)))
                    {
                        return true;
                    }
                }
                return false;
            });
            var register_request_handler = register_methods.First(mi =>
            {
                if (mi.IsGenericMethod)
                {
                    var paremeters = mi.GetParameters().ToArray();
                    if (paremeters.Length == 2 && paremeters[1].ParameterType.IsAssignableToGenericType(typeof(RequestHandler<,>)))
                    {
                        return true;
                    }
                }
                return false;
            });

            MethodInfo[] methods = this.
                GetType().
                GetMethods(BindingFlags.NonPublic
                | BindingFlags.Public
                | BindingFlags.Instance
                | BindingFlags.FlattenHierarchy
                | BindingFlags.Instance);

            foreach (MethodInfo mi in methods)
            {
                try
                {
                    foreach (Attribute attr in Attribute.GetCustomAttributes(mi, typeof(ExchangeAttribute)))
                    {
                        var args = mi.GetParameters().ToArray();
                        if (attr.GetType() == typeof(ExchangeMainHandlerAttribute))
                        {
                            if (args.Length == 1)
                            {
                                var handler = CreateDelegate(typeof(MessageHandler), mi, this);
                                register_message_handler.Invoke(server, new object[] { BaseSocket.DEFAULT_MESSAGE_INBOX, handler });
                            }
                            else
                            {
                                var handler = CreateDelegate(typeof(MessageHandler<>).MakeGenericType(args[1].ParameterType), mi, this);
                                MethodInfo genericMethod = register_message_handler_with_msg.MakeGenericMethod(args[1].ParameterType);
                                genericMethod.Invoke(server, new object[] { BaseSocket.DEFAULT_MESSAGE_INBOX, handler });
                            }
                        }
                        else if (attr.GetType() == typeof(ExchangeHandlerAttribute))
                        {
                            if (args.Length == 1)
                            {
                                var handler = CreateDelegate(typeof(MessageHandler), mi, this);
                                register_message_handler.Invoke(server, new object[] { (attr as ExchangeHandlerAttribute)!.Inbox, handler });
                            }
                            else
                            {
                                var handler = CreateDelegate(typeof(MessageHandler<>).MakeGenericType(args[1].ParameterType), mi, this);
                                MethodInfo genericMethod = register_message_handler_with_msg.MakeGenericMethod(args[1].ParameterType);
                                genericMethod.Invoke(server, new object[] { (attr as ExchangeHandlerAttribute)!.Inbox, handler });
                            }
                        }

                        else if (attr.GetType() == typeof(ExchangeMainReplierAttribute))
                        {
                            var returnType = mi.ReturnType;
                            var genArgType = args[1].ParameterType;
                            MethodInfo genericMethod = register_request_handler.MakeGenericMethod(genArgType, returnType);
                            var requestHandler = CreateDelegate(typeof(RequestHandler<,>).MakeGenericType(args[1].ParameterType, returnType), mi, this);
                            genericMethod.Invoke(server, new object[] { BaseSocket.DEFAULT_REQUEST_INBOX, requestHandler });
                        }
                        else if (attr.GetType() == typeof(ExchangeReplierAttribute))
                        {
                            var returnType = mi.ReturnType;
                            var genArgType = args[1].ParameterType;
                            MethodInfo genericMethod = register_request_handler.MakeGenericMethod(genArgType, returnType);
                            var requestHandler = CreateDelegate(typeof(RequestHandler<,>).MakeGenericType(args[1].ParameterType, returnType), mi, this);
                            genericMethod.Invoke(server, new object[] { (attr as ExchangeReplierAttribute)!.Inbox, requestHandler });
                        }

                        else if (attr.GetType() == typeof(ExchangeMainReplierWithoutArgAttribute))
                        {
                            var returnType = mi.ReturnType;
                            MethodInfo genericMethod = register_request_handler_without_msg.MakeGenericMethod(returnType);
                            var requestHandler = CreateDelegate(typeof(RequestHandler<>).MakeGenericType(returnType), mi, this);
                            genericMethod.Invoke(server, new object[] { BaseSocket.DEFAULT_REQUEST_WITHOUT_ARGS_INBOX, requestHandler });
                        }
                        else if (attr.GetType() == typeof(ExchangeReplierWithoutArgAttribute))
                        {
                            var returnType = mi.ReturnType;
                            MethodInfo genericMethod = register_request_handler_without_msg.MakeGenericMethod(returnType);
                            var requestHandler = CreateDelegate(typeof(RequestHandler<>).MakeGenericType(returnType), mi, this);
                            genericMethod.Invoke(server, new object[] { (attr as ExchangeReplierWithoutArgAttribute)!.Inbox, requestHandler });
                        }
                    }
                }
                catch (Exception ex)
                {
                    Log.Debug($"[ZExchange] Can't register method {mi.Name} as inbox handler. {ex}");
                }
            }
        }
        #endregion

        #endregion

        #region Service control
        public void Start()
        {
            if (_state == ZeroServiceStatus.Initialized)
            {
                try
                {
                    _state = ZeroServiceStatus.Running;
                    StartAction();
                    Log.Debug($"[{Name}] Service started");
                }
                catch (Exception ex)
                {
                    Log.Fatal(ex, $"[{Name}] Failed to start service");
                    Stop();
                }
            }
        }

        public void Stop()
        {
            if (_state == ZeroServiceStatus.Running)
            {
                try
                {
                    StopAction();
                    Log.Debug($"[{Name}] Service stopped");
                }
                catch (Exception ex)
                {
                    Log.Fatal(ex, $"[{Name}] Failed to stop service");
                }
            }
            _state = ZeroServiceStatus.Stopped;
        }

        public void WaitForStatus(ZeroServiceStatus status)
        {
            var start = DateTime.UtcNow;
            while (this.Status != status)
            {
                Thread.Sleep(1500);
            }
        }

        public void WaitForStatus(ZeroServiceStatus status, TimeSpan period)
        {
            var start = DateTime.UtcNow;
            while (this.Status != status && (DateTime.UtcNow - start) < period)
            {
                Thread.Sleep(1500);
            }
        }

        public void WaitWhileStatus(ZeroServiceStatus status)
        {
            var start = DateTime.UtcNow;
            while (this.Status == status)
            {
                Thread.Sleep(1500);
            }
        }

        public void WaitWhileStatus(ZeroServiceStatus status, TimeSpan period)
        {
            var start = DateTime.UtcNow;
            while (this.Status == status && (DateTime.UtcNow - start) < period)
            {
                Thread.Sleep(1500);
            }
        }
        #endregion

        public void Dispose()
        {
            if (_state != ZeroServiceStatus.Disposed)
            {
                _state = ZeroServiceStatus.Disposed;
                _exhange.Dispose();
            }
        }
    }
}