using Gebhardt.Shared;
using Gebhardt.Shared.Process;
using Gebhardt.StoreWare.Wcs.Common;
using Gebhardt.StoreWare.Wcs.Common.Application.LeHandling.Interfaces;
using Gebhardt.StoreWare.Wcs.Common.Application.StorageHandling.Interfaces;
using Gebhardt.StoreWare.Wcs.Common.Application.TransportHandling.Interfaces;
using Gebhardt.StoreWare.Wcs.Common.Dao;
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 System;
using System.Collections.Generic;
using System.Linq;
using static Gebhardt.StoreWare.Wcs.Common.Constants;

namespace Gebhardt.StoreWare.Wcs.ConveyorDispo;

public class OrderManager : ProcessWorker
{
	private readonly IAisleService _aisleService;
	private readonly IWcsDbContextFactory _dbContextFactory;
	private readonly IDestinationService _destinationService;
	private readonly ILeService _leService;
	private readonly ITransportOrderService _transportOrderService;

	public OrderManager(IDestinationService destinationService, ITransportOrderService transportOrderService, ILeService leService, IAisleService aisleService, IWcsDbContextFactory dbContextFactory, int workInterval)
		: base(nameof(OrderManager), workInterval, true)
	{
		_destinationService = destinationService;
		_transportOrderService = transportOrderService;
		_leService = leService;
		_aisleService = aisleService;
		_dbContextFactory = dbContextFactory;
	}

	public override bool DoWork()
	{
		bool workDone = false;
		try
		{
			using IWcsDbContext db = _dbContextFactory.GetDbContext();

			IQueryable<OrdersHost> pendingOrders = db.OrdersHost.ByStatus(TransportOrderStatus.Pending);
			OrderList onConveyor = new(pendingOrders
				.Where(o => o.Le.LocationId == null
										&& (!o.Le.IsEmpty || o.Type == TransportOrderType.TransportHost)
										&& (o.Le.Status == LeStatus.OnConveyor || o.Le.Status == LeStatus.InDestinationZone))
				.OrderBy(o => o.StartTime)
				.Select(o => new OrderListItem(o.Id, o.Status, o.Le, o.Destination, o.Priority, o.IdOrderWmsHead, o.Created, o.HostDestination))
				.AsNoTracking()
				.ToList());
			workDone |= StartNextOrders(onConveyor);

			var pendingOrdersWithDemand = pendingOrders
				.Where(x => x.Le.Status != LeStatus.Created)
				//Do not start pending orders for LEs inside a sequencer
				.ExcludeOrdersInSequencer()
				//Consider all destinations that are commissioning workstations or a storage area
				.ByDestination(_destinationService.GetCommissioningWorkstations()
					.Union(_destinationService.Where(d => d.IsStorageArea).Select(d => d.Name))
					.Union(_destinationService.Where(d => d.IsSequencer).Select(d => d.Name)));

			//// Always prioritize cancelled sequencer orders
			//var cancelledSeqOrders = pendingOrdersWithDemand.ByCancelledSequencerOrder();
			//OrderList forResourcesWithDemand = new(cancelledSeqOrders
			//	.Select(o => new OrderListItem(o.Id, o.Status, o.Le, o.Destination, o.Priority, o.IdOrderWmsHead, o.Created, o.HostDestination))
			//	.AsNoTracking()
			//	.ToList());
			//workDone |= StartNextOrders(forResourcesWithDemand);

			//Then do the normal orders with demand.
			 pendingOrdersWithDemand = pendingOrdersWithDemand.ApplyWmsOrderingSequencerRetrievalTime(db);

			OrderList forResourcesWithDemand = new(pendingOrdersWithDemand
				.Select(o => new OrderListItem(o.Id, o.Status, o.Le, o.Destination, o.Priority, o.IdOrderWmsHead, o.Created, o.HostDestination))
				.AsNoTracking()
				.ToList());
			workDone |= StartNextOrders(forResourcesWithDemand);

			OrderList forOtherDestinations = new(pendingOrders
				.Where(x => x.Le.Status != LeStatus.Created)
				.ExcludeDestination(db.ResourceSetting.Select(r => r.Name).ToList())
				.ApplyWmsOrderingSequencerRetrievalTime(db)
				.Select(o => new OrderListItem(o.Id, o.Status, o.Le, o.Destination, o.Priority, o.IdOrderWmsHead, o.Created, o.HostDestination))
				.AsNoTracking()
				.ToList());
			workDone |= StartNextOrders(forOtherDestinations);
		}
		catch (Exception ex)
		{
			Log.WriteException(ex);
		}
		return workDone;
	}

	private bool StartNextOrders(OrderList orders)
	{
		bool workDone = false;

		using IWcsDbContext db = _dbContextFactory.GetDbContext();

		List<Le> les = db.Le.Where(l => orders.Select(o => o.Le.LeNo).Contains(l.LeNo)).Distinct().ToList();

		for (int i = 0; i < orders.Count; i++)
		{
			OrderListItem order = orders[i];
			try
			{				
				Le le = les.Single(l => l.LeNo == order.Le.LeNo);
				if (LeIsExcludedAsAisleNotReady(orders, le, order)
				    || LeIsExcludedAsOrderMiniloadIsActive(orders, db, le, order)
				    || LeIsExcludedAsLeIsOnItsWayToNOK(orders, db, order))
				{
					workDone = true;
					continue;
				}
				
				// Special case for sequencer orders: reserve half the space for each workstation
				if (order.Destination.StartsWith("SEQ"))
				{
					Resource sequencerResource = db.ResourceSetting.GetResourceByName(order.Destination);
					if (sequencerResource != null)
					{
						SettingsManager.GetParsedValue(ConstantsCommon.SettingNames.MaximumUsableCapacityPercentPerWorkstation, out int maximumUsableCapacityPercentPerWorkstation, 50, true);
						maximumUsableCapacityPercentPerWorkstation = maximumUsableCapacityPercentPerWorkstation > 100 ? 100 : maximumUsableCapacityPercentPerWorkstation;
						maximumUsableCapacityPercentPerWorkstation = maximumUsableCapacityPercentPerWorkstation < 50 ? 50 : maximumUsableCapacityPercentPerWorkstation;

						int maximumUsableCapacityPerWorkstation = (sequencerResource.Capacity + sequencerResource.Overload) * maximumUsableCapacityPercentPerWorkstation / 100;
						
						// Count active orders for this specific HostDestination going to the same sequencer
						int activeOrdersForHostDestination = db.OrdersHost
							.Count(o => o.HostDestination == order.HostDestination
							            && o.Destination == order.Destination
							            && (o.Status == TransportOrderStatus.InProgress 
							                || o.Status == TransportOrderStatus.InDestinationZone 
							                || o.Status == TransportOrderStatus.InSequencer));
						
						if (activeOrdersForHostDestination >= maximumUsableCapacityPerWorkstation)
						{
							Log.Write(LogLevel.Info, 30, $"Sequencer capacity limit reached for {order.HostDestination} at {order.Destination}. Active: {activeOrdersForHostDestination}, Reserved: {maximumUsableCapacityPerWorkstation}");
							continue;
						}
					}
				}
				
				Resource resource = db.ResourceSetting.GetResourceByName(order.Destination);

				if (resource is null or { Demand: > 0 })
				{
					// Only cancel active transports not transportHost. Only WMS is allowed to finish this orders
					var activeOrdersNotOnTheWayToWorkstation = db.OrdersHost
						.ByLeNo(order.Le.LeNo)
						.Active()
						.Where(o => o.Type != TransportOrderType.TransportHost);
					if (activeOrdersNotOnTheWayToWorkstation.Any())
					{
						_leService.CancelActiveTransports(order.Le.LeNo, $"Another active order to a commissioning area exists");
					} else if (!db.OrdersHost.ByLeNo(order.Le.LeNo).Active().Any())
					{
						_transportOrderService.StartNextTransport(order.OrdersHostId);
					}
					orders.RemoveSubsequentWithEqualLeNo(order);
					workDone = true;
				}
				else if (resource is { Demand: <= 0 })
				{
					if (!le.IsInStorage())
					{
						List<string> destinationAisles = _destinationService.Where(d => d.IsStorage).Select(d => d.Name).ToList();
						if (!db.OrdersHost.OpenByLeNo(order.Le.LeNo).ByDestination(destinationAisles).Any())
						{
							_transportOrderService.PostponeOrdersHost(order.OrdersHostId, $"Destination {order.Destination} has no demand.");
							orders.RemoveSubsequentWithEqualLeNo(order);
						}
						continue;
					}
					orders.RemoveSubsequentWithEqualDestination(order);
				}
			}
			catch (Exception ex)
			{
				Log.Write(LogLevel.Error, $"Can not start OrdersHost: {order.OrdersHostId} for LE: {order.Le.LeNo}");
				Log.WriteException(ex);
			}
		}
		return workDone;
	}

	private static bool LeIsExcludedAsOrderMiniloadIsActive(OrderList orders, IWcsDbContext db, Le le, OrderListItem order)
	{
		if (db.OrdersMiniload.Underway().ByLeNo(le.LeNo).Any())
		{
			orders.RemoveSubsequentWithEqualLeNo(order);
			Log.Write(LogLevel.Info, $"{nameof(OrdersHost)} with Id '{order.OrdersHostId}' for LE {le.LeNo} cannot be started due to an open OrdersMiniload.");
			return true;
		}
		return false;
	}

	private bool LeIsExcludedAsAisleNotReady(OrderList orders, Le le, OrderListItem order)
	{
		if (le.IsInStorage())
		{
			if (!_aisleService.IsAisleReadyForRetrievalOrder(order.Le.StorageArea, order.Le.AisleName))
			{
				orders.RemoveSubsequentWithEqualAisle(order);
				Log.Write(LogLevel.Info, $"{nameof(OrdersHost)} with Id '{order.OrdersHostId}' for LE {le.LeNo} cannot be started as not all participating aisles / devices are available for {le.AisleName}/{le.StorageArea}.");
				return true;
			}
		}
		return false;
	}

	private bool LeIsExcludedAsLeIsOnItsWayToNOK(OrderList orders, IWcsDbContext db, OrderListItem order)
	{
		OrdersHost orderForSameLe = db.OrdersHost
					.ByLeNo(order.Le.LeNo)
					.ByStatus(TransportOrderStatus.Pending, TransportOrderStatus.InProgress, TransportOrderStatus.InDestinationZone)
					.OrderBy(o => o.Status != TransportOrderStatus.Pending ? 0 : 1) // get active order (if any), pending otherwise
					.FirstOrDefault();
		if (orderForSameLe == null)
		{
			return false;
		}

		if (orderForSameLe.Le.HasError() && orderForSameLe.Destination.IsInList(MfcAllDestinations.ERR12, MfcAllDestinations.ERR11, MfcAllDestinationsOldSystem.ERR01)
			|| order.Le.HasError() && order.Destination.IsInList(MfcAllDestinations.ERR12, MfcAllDestinations.ERR11, MfcAllDestinationsOldSystem.ERR01))
		{
			orders.RemoveSubsequentWithEqualLeNo(order);
			return true;
		}
		return false;
	}

}