using System;
using System.Collections.Generic;
using System.Linq;
using Gebhardt.Shared;
using Gebhardt.StoreWare.Wcs.Common.Application.LeHandling.Interfaces;
using Gebhardt.StoreWare.Wcs.Common.Application.LoopStrategy;
using Gebhardt.StoreWare.Wcs.Common.Application.StorageStrategies.Interfaces;
using Gebhardt.StoreWare.Wcs.Common.Application.TransportHandling.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 Gebhardt.StoreWare.Wcs.HostBooking.InterfaceWcsWms.Interfaces;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Interfaces;
using static Gebhardt.StoreWare.Wcs.Common.Constants;

namespace Gebhardt.StoreWare.Wcs.HostBooking.InterfaceWcsWms.MessageImplementation
{
	public class AcknowledgeTransportCompletedHandler : IHandleRecord<IAcknowledgeTransportCompleted>
	{
		private readonly IWcsDbContextFactory _dbContextFactory;
		private readonly ITransportOrderService _transportOrderService;
		private readonly ILeService _leService;
		private readonly IDestinationService _destinationService;
		private readonly ILoopSelectionService _loopSelectionService;

		public AcknowledgeTransportCompletedHandler(IWcsDbContextFactory dbContextFactory, ITransportOrderService transportOrderService, ILeService leService, IDestinationService destinationService, ILoopSelectionService loopSelectionService)
		{
			_dbContextFactory = dbContextFactory;
			_transportOrderService = transportOrderService;
			_destinationService = destinationService;
			_leService = leService;
			_loopSelectionService = loopSelectionService;
		}

		public bool Handle(IAcknowledgeTransportCompleted message)
		{
			using (IWcsDbContext db = _dbContextFactory.GetDbContext())
			{
				List<int> ordersHostIds = db.OrdersHost.Where(o => o.IdOrderWms == message.IdOrderWms).Select(o => o.Id).ToList();

				if (ordersHostIds.Any())
				{
					if (ordersHostIds.Count() > 1)
					{
						throw new InvalidOperationException($"Multiple OrdersHost found for same WMS id {message.IdOrderWms}");
					}

					_transportOrderService.FinishOrdersHost(ordersHostIds.Single(), "AcknowledgeTransportCompleted");
					Log.Write(LogLevel.Info, $"{nameof(OrdersHost)} with Id {ordersHostIds.Single()} and WMS id {message.IdOrderWms} finished.");

					//Other open OH for HU that must be cancelled?
					List<OrdersHost> others = db.OrdersHost
						.OpenByLeNo(message.LeNo).Where(oh => oh.Type == TransportOrderType.Transport).ToList();
					foreach (var other in others)
					{
						_transportOrderService.CancelOrdersHost(other.Id, $"New destination {message.Destination}", $"OrderHost {ordersHostIds.Single()} was finished");
					}
				}
				else
				{
					Log.Write(LogLevel.Error, $"{nameof(AcknowledgeTransportCompletedHandler)}: Cannot find OH for WMS id {message.IdOrderWms}");
				}

				db.SaveChanges();
			}

			// New DB Context since OrdersConveyorService changes data that we otherwise wouldn't notice
			using (IWcsDbContext db = _dbContextFactory.GetDbContext())
			{
				// le empty?
				Le le = db.Le.ByLeNo(message.LeNo);
				// If there is no other order for this le, send it back to the storage
				if (!db.OrdersHost.OpenByLeNo(message.LeNo).Any() && le != null)
				{
					DestinationProperty workStation = db.DestinationProperty.FirstOrDefault(d => d.Name == message.Destination && d.Property == nameof(IDestinationProperties.Level));
					var workStationDest = _destinationService.Get(message.Destination);

					List<VLoopOverview> availableLoops = _loopSelectionService.GetSuitableStorageLoops(message.LeNo);

					// Choose a desired destination based on which is the closest to the workstation
					string destination = MfcAllDestinations.StorageLoop2;
					if (workStationDest != null)
					{
						destination = workStationDest.StorageArea == MfcAllDestinations.AKL02 ? MfcAllDestinationsOldSystem.LOOP3 : workStationDest.Level == ConveyorLevel.EtraBox.ToString() ? MfcAllDestinations.StorageLoop1 : MfcAllDestinations.StorageLoop2;

						// If only one of the loops is available, choose this, no matter where the LE is located
						if (availableLoops.Count == 1)
						{
							destination = availableLoops.First().PlcName;
						}
						else
						{
							// Prefer the destination closest connected to the pick station but select the other if this is full
							string fallbackDestination = workStationDest.StorageArea == MfcAllDestinations.AKL02 ? MfcAllDestinationsOldSystem.LOOP3 : workStationDest.Level == ConveyorLevel.EtraBox.ToString() ? MfcAllDestinations.StorageLoop1 : MfcAllDestinations.StorageLoop2;
							if (!_loopSelectionService.hasEnoughSpace(destination) && _loopSelectionService.hasEnoughSpace(fallbackDestination))
							{
								destination = fallbackDestination;
							}
						}
					}
					string source = message.Destination == MfcAllDestinationsOldSystem.TOPUP ? MfcAllDestinationsOldSystem.TOPUP : message.Source ?? "?";
					int ordersHostId = _leService.CreateTransport(message.LeNo, source, destination);
					Log.Write(LogLevel.Info, $"Loop selection in acknowledge transport handler for {le.LeNo}, set to {destination}");
					LoopStrategy.ReserveForLoop(le.LeNo, ordersHostId, availableLoops.FirstOrDefault(l => l.PlcName == destination)?.Name);
				}
			}
			return true;
		}
	}
}