using System;
using System.ComponentModel;
using System.Collections.Generic;
using Newtonsoft.Json;
using System.Linq;
using System.Reflection;
using System.Xml.Serialization;
using System.Text.RegularExpressions;
using Unity;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Interfaces;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Services;
using Gebhardt.StoreWare.Wcs.HostBooking.InterfaceWcsWms.Interfaces;
using Gebhardt.Shared.Process;
using Gebhardt.Shared.Process.ProducerConsumer;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Interfaces;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Models;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.EntityFramework;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.EntityFramework.Models;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Mapping;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Helpers;

using Gebhardt.Shared;
using Gebhardt.StoreWare.WcsWms.Constants;


namespace Gebhardt.StoreWare.Wcs.HostBooking.InterfaceWcsWms
{
    internal class FromWmsBookingProducer : Producer<IHostMessage>
    {
        #region Private Fields

        private bool _firstExecution = true;

        private int _consumerQueueLength;

        private bool _useLoadBalancing;

        private readonly IUnityContainer _unityContainer;

        private readonly IHostMessageFromWmsService _serviceFromWms;

        #endregion Private Fields

        #region Private Methods	


        /// <summary>
        /// Fügt im Dictionary dem angegebenen Consumer das Messagem hinzu
        /// </summary>
        /// <param name="dataForConsumers"></param>
        /// <param name="consumer"></param>
        /// <param name="hostMessage">FromWms - IHostMessage</param>
        private void AddDataForConsumer(ref Dictionary<string, List<IHostMessage>> dataForConsumers, string consumer, IHostMessage hostMessage)
        {
            if (dataForConsumers.ContainsKey(consumer))
            {
                dataForConsumers[consumer].Add(hostMessage);
            }
            else
            {
                dataForConsumers.Add(consumer, new List<IHostMessage> { hostMessage });
            }
        }

        /// <summary>
        /// Bestimmt den Consumer, der für den Datensatz verantwortlich ist
        /// </summary>
        /// <param name="consumersWithDemand"></param>
        /// <param name="criterion"></param>
        /// <returns>Consumer Name, wenn ein passender Consumer in der lsite ist, null, wenn kein passender Consumer in der Liste ist</returns>
        private string GetConsumerForBooking(List<string> consumersWithDemand, string criterion)
        {
            //Gibt es eine Letzte Stelle, sonst default
            if (!criterion.IsNullOrEmptyOrDbEmpty())
            {
                //Wir entscheiden mit der letzten Stelle der LE, welcher LE-Consumer verbucht oder ob der default Consumer das tun muss
                string endOfLe = criterion.Substring(criterion.Length - 1);
                //Der Name des Consumer endet mit dem gleichen Zeichen
                if (consumersWithDemand.Any(c => c.EndsWith(endOfLe)))
                {
                    return consumersWithDemand.First(c => c.EndsWith(endOfLe));
                }
                //Soll dieses Message von einem speziellen Consumer bearbeitet werden? Aber dieser ist beschäftigt
                else if (Regex.IsMatch(endOfLe, "[0-9]"))
                {
                    //Message auslassen - keinem Consumer zuordnen
                    return null;
                }
                //HU endet mit einem Zeichen, dass zu keinem Consumer passt also Default oder auslassen
                else
                {
                    //Default Consumer muss mit Default enden!
                    return consumersWithDemand.FirstOrDefault(c => c.EndsWith("Default"));
                }
            }
            else
            {
                //Default Consumer muss mit Default enden!
                return consumersWithDemand.FirstOrDefault(c => c.EndsWith("Default"));
            }
        }

        #endregion Private Methods

        #region Protected Methods

        protected override Dictionary<string, List<IHostMessage>> GetDataForConsumers(List<string> consumersWithDemand)
        {
            //TODO: jub evtl. die Messagem Stau Meldung aus KW übernehmen
            Dictionary<string, List<IHostMessage>> dataForConsumers = new Dictionary<string, List<IHostMessage>>();
            try
            {
                using HostEntities db = new HostEntitiesFactory(HostEntities.DefaultConnectionStringName).Create();
                if (consumersWithDemand.Count > 0)
                {
                    List<IHostMessage> hostMessageEntries;

                    //es werden maximal so viele Messages abgerufen, wie ein einzelner Consumer annehmen könnte, damit das TryAdd sicher klappt und der Status nicht fälschlich
                    //gesetzt wird
                    if (_firstExecution)
                    {
                        //beim ersten mal nach Neustart werden auch die InProgress Messages nochmal mit abgerufen
                        //Ref == null damit nur Kopfnachrichten gefunden werden
                        hostMessageEntries = _serviceFromWms.GetAllEntries(a => a.Status == "Pending" || a.Status == "InProgress", takeCount: _consumerQueueLength).OrderBy(a => a.Id).ToList();
                        _firstExecution = false;
                    }
                    else
                    {
                        //sonst nur Messages, die noch an keinen Consumer gegeben wurden
                        //Ref == null damit nur Kopfnachrichten gefunden werden
                        hostMessageEntries = _serviceFromWms.GetAllEntries(a => a.Status == "Pending", takeCount: _consumerQueueLength).OrderBy(a => a.Id).ToList();
                    }

                    if (hostMessageEntries.Any())
                    {
                        //ohne LoadBalancing erhält der erste Consumer alle Messages zum Verbuchen
                        if (!_useLoadBalancing)
                        {
                            //der Consumer muss also alle Messages verarbeiten und nicht nur loggen
                            dataForConsumers.Add(consumersWithDemand.FirstOrDefault() ?? string.Empty, hostMessageEntries);
                            //Status auf InProgress damit jede Message nur einmal abgerufen wird
                            hostMessageEntries.ForEach(message => message.Status = "InProgress");
                            Log.Write(LogLevel.Low, $"Kein LoadBalancing aktiv, {hostMessageEntries.Count} neue HostMessages für Consumer {consumersWithDemand.FirstOrDefault()} gefunden");
                        }
                        //mit LoadBalancing wird nach der letzen Ziffer der ersten HU der Message auf 10 Consumer verteilt,
                        //enthält die Message keine HU wird es an Consumer 11 übergeben.
                        else
                        {
                            Log.Write(LogLevel.Low, $"LoadBalancing aktiv, {hostMessageEntries.Count} neue HostMessages gefunden, verteile auf Consumer");

                            foreach (IHostMessage message in hostMessageEntries)
                            {
                                try
                                {
                                    FromWms entityFromWms = HostMessageFromWmsService.HostMapper.Map<FromWms>(message);
                                    Log.Write(LogLevel.Low, $"Producerschleife für {entityFromWms}");
                                    string consumer;
                                    //keine HU in der Message, dann dem Default Consumer zuordnen
                                    if (entityFromWms.LeNo.IsNullOrEmptyOrDbEmpty())
                                    {
                                        consumer = GetConsumerForBooking(consumersWithDemand, null);
                                        //wenn der Default Consumer nicht in der Liste war, Message auslassen
                                        if (consumer != null)
                                        {
                                            AddDataForConsumer(ref dataForConsumers, consumer, message);
                                            //Status auf InProgress damit die Message nur einmal abgerufen wird
                                            entityFromWms.Status = "InProgress";
                                        }
                                    }
                                    //Messages mit HU
                                    else
                                    {
                                        consumer = GetConsumerForBooking(consumersWithDemand, entityFromWms.LeNo);
                                        AddDataForConsumer(ref dataForConsumers, consumer, message);
                                        //Status auf InProgress damit die Message nur einmal abgerufen wird
                                        entityFromWms.Status = "InProgress";
                                    }
                                }
                                catch (Exception ex)
                                {
                                    Log.WriteException(ex);
                                }
                            }
                            Log.Write(LogLevel.Low, $"Producerschleife beendet");
                        }
                        //Status Updates für alle weitergereichten Messages
                        db.SaveChanges();
                    }
                    else
                    {
                        Log.Write(LogLevel.Debug, 60, "Keine Messages in FromWms");
                    }
                }
                else
                {
                    Log.Write(LogLevel.Debug, 60, "Kein Consumer hat demand");
                }

                return dataForConsumers;
            }
            catch (Exception e)
            {
                Log.WriteException(e);
                return dataForConsumers;
            }
        }

        #endregion Protected Methods

        #region Public Constructors

        public FromWmsBookingProducer(int workinterval, bool useLoadBalancing, int consumerQueueLength, IUnityContainer unityContainer) : base(typeof(FromWmsBookingProducer).Name, workinterval, true)
        {
            _unityContainer = unityContainer;
            _serviceFromWms = _unityContainer.Resolve<IHostMessageFromWmsService>();
            _consumerQueueLength = consumerQueueLength;
            _useLoadBalancing = useLoadBalancing;
        }

        #endregion Public Constructors

    }

}