using System;
using System.Collections.Generic;
using System.Linq;
using Gebhardt.Shared;
using Gebhardt.Shared.DbAccess;
using Gebhardt.Shared.Process;
using Gebhardt.StoreWare.Wcs.Common;
using Gebhardt.StoreWare.Wcs.Common.Application.TransportHandling.Interfaces;
using Gebhardt.StoreWare.Wcs.Common.Dao;
using Gebhardt.StoreWare.Wcs.Common.Dao.Interfaces;
using Gebhardt.StoreWare.Wcs.Common.DbAccess;
using Gebhardt.StoreWare.Wcs.Common.DbAccess.Model;
using Gebhardt.StoreWare.Wcs.Common.DbAccess.Model.Enums;
using Gebhardt.StoreWare.Wcs.Common.DbAccess.Queries;
using Microsoft.EntityFrameworkCore;
using static Gebhardt.StoreWare.Wcs.Common.Constants;

namespace Gebhardt.StoreWare.Wcs.ConveyorDispo
{
	public class StartInitialOrdersHost : ProcessWorker
	{
		private readonly IWcsDbContextFactory _dbContextFactory;
		private readonly ITransportOrderService _transportOrderService;

		public StartInitialOrdersHost(int workInterval, ITransportOrderService transportOrderService, IWcsDbContextFactory dbContextFactory) : base(nameof(StartInitialOrdersHost), workInterval, true)
		{
			_transportOrderService = transportOrderService;
			_dbContextFactory = dbContextFactory;
		}

		public override bool DoWork()
		{
			try
			{
				using IWcsDbContext db = _dbContextFactory.GetDbContext();
				OrderList initialOrders = new(db.OrdersHost
									.ByStatus(TransportOrderStatus.Initial)
					.ExcludeNextEmpty()
					.Where(o => o.Le.Status != LeStatus.Created)
					.ApplyWmsOrderingSequencerRetrievalTime(db)
					.Select(o => new OrderListItem(o.Id, o.Status, o.Le, o.Destination, o.Priority, o.IdOrderWmsHead, o.Created, o.HostDestination))
					.ToList());
				//This step is needed to only start orders with all LEs available
				initialOrders = OnlyFirstOrderPerLeNo(db, initialOrders);

				return StartOrders(initialOrders);
			}
			catch (Exception ex)
			{
				Log.WriteException(ex);
				return false;
			}
		}

		/// <summary>
		/// Returns only the first order per LE
		/// </summary>
		/// <param name="initialOrders"></param>
		/// <returns>Filtered List</returns>
		private OrderList OnlyFirstOrderPerLeNo(IWcsDbContext db, OrderList initialOrders)
		{
			List<OrderListItem> distinctOrderList = new ();
			List<OrderListItem> lockedOrders = new();

			foreach (OrderListItem order in initialOrders)
			{
				//Only take first order per LE
				if (!distinctOrderList.Any(d=>d.Le.LeNo == order.Le.LeNo))
				{
					distinctOrderList.Add(order);
				}
				//Others are marked as locked (for info message)
				else
				{
					lockedOrders.Add(order);
					Log.Write(LogLevel.Info, 60, $"OrdersHost ID {order.OrdersHostId} for LeNo {order.Le.LeNo} waits for OrdersHost: ID {distinctOrderList.Where(d => d.Le.LeNo == order.Le.LeNo).First().OrdersHostId} which is started first.");
				}
			}
			//Make visible in DB (and to user)
			string infoMessage = "Le has transports that are started first.";
			var markOrders = db.OrdersHost.Where(o => lockedOrders.Select(l => l.OrdersHostId).Contains(o.Id) && o.Info != infoMessage).ToList();
			foreach (var order in markOrders)
			{
				order.Info = infoMessage;
			}
			//Rest Info if order can be started
			var unMarkOrders = db.OrdersHost.Where(o => distinctOrderList.Select(l => l.OrdersHostId).Contains(o.Id) && o.Info == infoMessage).ToList();
			foreach (var order in unMarkOrders)
			{
				order.Info = null;
			}
			db.SaveChanges();

			return new OrderList(distinctOrderList);
		}

		private bool StartOrders(OrderList orderList)
		{
			bool workDone = false;

			using IWcsDbContext db = _dbContextFactory.GetDbContext();
			for (int i = 0; i < orderList.Count; i++)
			{
				OrderListItem orderToBeScheduled = orderList[i];

				try
				{
					// If there exists a cancelled sequencer orders for this LE this should always be started first
					var cancelledSequencerOrder = db.OrdersHost
						.ByLeNo(orderToBeScheduled.Le.LeNo)
						.ByStatus(TransportOrderStatus.Initial)
						.ByCancelledSequencerOrder()
						.Select(o => new OrderListItem(o.Id, o.Status, o.Le, o.Destination, o.Priority, o.IdOrderWmsHead, o.Created, o.HostDestination))
						.ToList();

					if (cancelledSequencerOrder.FirstOrDefault() != null)
					{
						var cancelledOrderToBeScheduled = cancelledSequencerOrder.FirstOrDefault();
						Log.Write(LogLevel.Debug, $"{nameof(OrdersHost)} with Id {cancelledOrderToBeScheduled.OrdersHostId} will be scheduled since this is a cancelled sequencer order.");
						_transportOrderService.ScheduleOrdersHost(cancelledOrderToBeScheduled.OrdersHostId);
						//Order has been scheduled. Do not consider orders for the same LE.
						orderList.RemoveSubsequentWithEqualLeNo(orderToBeScheduled);
						workDone = true;
						continue;
					}

					OrdersHost orderForSameLe = db.OrdersHost
						.ByLeNo(orderToBeScheduled.Le.LeNo)
						.ByStatus(TransportOrderStatus.Pending, TransportOrderStatus.InProgress, TransportOrderStatus.InDestinationZone, TransportOrderStatus.InSequencer)
						.OrderBy(o => o.Status != TransportOrderStatus.Pending ? 0 : 1) // get active order (if any), pending otherwise
						.FirstOrDefault();

					if (orderForSameLe != null && orderForSameLe.Id != orderToBeScheduled.OrdersHostId)
					{
						workDone = TryScheduleWithExistingOrder(orderForSameLe, db, orderToBeScheduled);

						//Check equal LEs with open order only once
						orderList.RemoveSubsequentWithEqualLeNo(orderToBeScheduled);
						continue;
					}

					if (TryScheduleWithoutExistingOrder(orderToBeScheduled, db))
					{
						//Order has been scheduled. Do not consider orders for the same LE.
						orderList.RemoveSubsequentWithEqualLeNo(orderToBeScheduled);
						workDone = true;
						continue;
					}
					else
					{
						Log.Write(LogLevel.Debug, $"Destination: {orderToBeScheduled.Destination} has no ressources, skip all orders to this destination");
						orderList.RemoveSubsequentWithEqualDestination(orderToBeScheduled);						
					}
				}
				catch(Exception ex)
				{
					Log.Write(LogLevel.Error, $"Can not set ordersHost {orderToBeScheduled.OrdersHostId} for LE {orderToBeScheduled.Le.LeNo} to pending. Due to Exception:");
					Log.WriteException(ex);
				}
				//We can remove all subsequent orders with same LE, because the list is already ordered in a way that priorities are taken into account.
				orderList.RemoveSubsequentWithEqualLeNo(orderToBeScheduled);
			}
			return workDone;
		}

		private bool TryScheduleWithoutExistingOrder(OrderListItem orderToBeScheduled, IWcsDbContext db)
		{
			if (CanScheduleOrder(orderToBeScheduled, db))
			{
				Log.Write(LogLevel.Debug, $"{nameof(OrdersHost)} with Id {orderToBeScheduled.OrdersHostId} will be scheduled, as there are no other {nameof(OrdersHost)} scheduled or active orders for the same LE.");
				_transportOrderService.ScheduleOrdersHost(orderToBeScheduled.OrdersHostId);
				return true;
			}
			return false;
		}

		private bool CanScheduleOrder(OrderListItem orderToBeScheduled, IWcsDbContext db)
		{
			// Orders with their associated LE not being in storage can always be scheduled...
			//TODO Check for OnConveyor/InDestinationZone? IsInStorage() does not cover OnInputLeLifter/OnLhd/OnOutputLeLifter...
			if (!orderToBeScheduled.Le.IsInStorage())
			{
				return true;
			}
			//...if LE is in storage, whether the order can be scheduled is determined by the amount of pending orders that may exist towards the destination and the destination's demand.
			if (!SettingsManager.GetParsedValue(ConstantsCommon.SettingNames.MaxPendingOrdersPerDestination, out int maxPendingOrdersPerDestination))
			{
				maxPendingOrdersPerDestination = 10;
			}

			Resource resourceDestination = db.ResourceSetting.GetResourceByName(orderToBeScheduled.Destination);
			// for unmanaged resources, a demand of 1 is used.
			int destinationDemand = resourceDestination?.Demand ?? 1;

			int countPendingOrdersToDestination = db.OrdersHost.ByStatus(TransportOrderStatus.Pending).ByDestination(orderToBeScheduled.Destination).Count();
			return destinationDemand - countPendingOrdersToDestination > 0 || countPendingOrdersToDestination - destinationDemand < maxPendingOrdersPerDestination;
		}

		private bool TryScheduleWithExistingOrder(OrdersHost orderForSameLe, IWcsDbContext db, OrderListItem orderToBeScheduled)
		{
			// We not want to overwrite existing orders. We wait until we are back in Storage. (This happened only about 40 times a day.
			return false;
		}
	}
}