using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using AutoMapper.Configuration.Conventions;
using Gebhardt.Shared;
using Gebhardt.StoreWare.Wcs.Common.Application.TransportHandling.Interfaces;
using Gebhardt.StoreWare.Wcs.Common.DbAccess;
using Gebhardt.StoreWare.Wcs.Common.DbAccess.Factories;
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 Microsoft.EntityFrameworkCore;
using static Gebhardt.StoreWare.Wcs.Common.ConstantsCommon;

namespace Gebhardt.StoreWare.Wcs.HostBooking.InterfaceWcsWms.MessageImplementation
{
	public class TransportOrderHandler : IHandleRecord<ITransportOrder>
	{
		private readonly IDestinationService _destinationService;
		private readonly ITransportOrderService _transportOrderService;
		private ITransportOrder _transportOrder;
		private readonly LeFactory _leFactory = LeFactory.GetInstance();
		private readonly List<string> _allStorageAndEmptyLeBufferDestinations;

		public TransportOrderHandler(IDestinationService destinationService, ITransportOrderService transportOrderService)
		{
			_destinationService = destinationService;
			_transportOrderService = transportOrderService;
			_allStorageAndEmptyLeBufferDestinations = _destinationService.Where(d => d.IsStorage || d.IsStorageArea || d.BuffersEmptyLe).Select(d => d.Name).ToList();
		}

		public bool Handle(ITransportOrder message)
		{
			_transportOrder = message;


			using WcsDbContext db = new();
			string leNo = _transportOrder.LeNo;
			Le le = db.Le.ByLeNo(leNo);
			if (le == null)
			{
				Log.Write(LogLevel.Info, $"HU {leNo} is unknown. Create HU...");
				LeTypeName type = LeType.GetLeTypeFromLeNo(leNo);
				le = db.Add(_leFactory.RegularLe(leNo, type, preferredStorageArea: message.StorageArea)).Entity;
				db.SaveChanges();
			}
			CheckTransportOrder();

			IDestinationProperties destinationProperties = _destinationService.Get(_transportOrder.Destination);
			if (destinationProperties == null)
			{
				throw new TransportDestinationInvalidException($"Unknown destination in {message}");
			}

			if (destinationProperties.IsStorage && !destinationProperties.IsSequencer && le.IsInStorage())
			{
				throw new LeAlreadyInStorageException($"{le} is in storage.");
			}

			CheckLeIsReachable(le);
			//CheckActiveOrdersForLe();
			if (_transportOrder.MovementType == TransportOrderType.Storage.ToString())
			{
				le.SetStatus(LeStatus.Created);
			}
			else
			{
				SetTransportStatus(le);
			}
			SetAbcArea(le);
			// Le is in the old system, but no movement file has been created for it.
			bool leNeedsMomFile = le.IsHuInOldSystem == true && le.L8OrderCreated == null;
			CreateTransportOrder(leNeedsMomFile);
			db.SaveChanges();
			return true;
		}

		private void CheckTransportOrder()
		{
			if (_transportOrder.LeNo == null)
			{
				throw new LeNoMissingException("The HU number is is missing.");
			}
			if (_destinationService.Get(_transportOrder.Destination) == null)
			{
				throw new TransportDestinationInvalidException($"Destination {_transportOrder.Destination} is unknown. Order is confirmed negatively.");
			}

			if (string.IsNullOrEmpty(_transportOrder.MovementType))
			{
				throw new FromWmsException($"Field {nameof(_transportOrder.MovementType)} is not set or not known.");
			}
			// TODO: Solche Prüfungen sollten in einem Plausiblitätsmodul für LE-Nummern erfolgen, damit sie leicht kundenspezifisch angepasst werden können
			if (_transportOrder.LeNo.Length < ConstantsLE.LengthBarcodeWithType)
			{
				throw new LeNoTooShortException($"HU number '{_transportOrder.LeNo}' is too short. Should be {ConstantsLE.LengthBarcodeWithType} digits");
			}

			if (_transportOrder.LeNo.Length > ConstantsLE.LengthBarcodeWithType)
			{
				throw new LeNoTooLongException($"HU number '{_transportOrder.LeNo}' is too long. Should be {ConstantsLE.LengthBarcodeWithType} digits");
			}
		}

		private void SetAbcArea(Le le)
		{
			//le.AbcArea = _transportOrder.AbcArea;
		}

		private void CreateTransportOrder(bool oldSystemBoxRequiringMomFile = false)
		{
			using WcsDbContext db = new();
			string source = _transportOrder.Source;
			OrderTypeWms? typeWmsForOrderHost;
			if (!Enum.TryParse(_transportOrder.MovementType, true, out OrderTypeWms typeWms))
			{
				Log.Write(LogLevel.Error, $"could not parse '{_transportOrder.MovementType}'");
				typeWmsForOrderHost = null;
			}
			else
			{
				typeWmsForOrderHost = typeWms;
			}

			OrdersHost ordersHost = OrdersHostFactory.GetInstance().InitialForLe(_transportOrder.LeNo, TransportOrderType.TransportHost, source == TestToolConstants.TestToolSource ? Common.Constants.MfcAllDestinations.AKL01 : source,
				_transportOrder.Destination, idOrderWms: _transportOrder.IdOrderWms, idOrderWmsHead: _transportOrder.IdOrderWmsHead, idOrderWmsPos: _transportOrder.IdOrderWmsPos, typeWms: typeWmsForOrderHost);

			IDestinationProperties destination = _destinationService.Get(_transportOrder.Destination);
			if (destination != null && destination.IsAfterSequencer)
			{
				ordersHost.Destination = destination.ConnectedSequencer;
				ordersHost.HostDestination = _transportOrder.Destination;
			}

			// TODO 15585
			ordersHost.SequenceWms = _transportOrder.Sequence;
			ordersHost.IsDirectPicking = _transportOrder.IsDirectPicking;
			ordersHost.PriorityDate = _transportOrder.Priority;
			db.Add(ordersHost);

			if (source == TestToolConstants.TestToolSource)
			{
				ordersHost.Priority = TestToolConstants.TestToolPriority;
			}
			if (oldSystemBoxRequiringMomFile)
			{
				try
				{
					CreateMomFile(ordersHost.LeNo);
					ordersHost.Le.L8OrderCreated = DateTime.Now;
					Log.Write(LogLevel.Info, $"Mom file created and saved for HU {ordersHost.LeNo}.");
				}
				catch (Exception e)
				{
					Log.Write(LogLevel.Error, $"Error creating mom file for HU {ordersHost.LeNo}, Exception: {e}");
				}
			}			
			db.SaveChanges();
		}

		private void CreateMomFile(string huNumber)
		{
			// Hardcoded since very temporary
			string filePath = $@"\\10.101.71.32\\InterfaceFiles\\ProdGB\\OrdersGB\\{huNumber}_{DateTime.Now:yyyyMMddHHmmss00}.mom";
			File.WriteAllText(filePath, huNumber);
		}

		/// <summary>
		///   A Handling Unit that rests at a workplace is set to "On conveyer" when it receives a transport order
		/// </summary>
		private void SetTransportStatus(Le le)
		{
			if (le.Status != LeStatus.InStorage)
			{
				le.SetStatus(LeStatus.OnConveyor, le.LastWhere);
			}
		}

		private void CheckLeIsReachable(Le le)
		{
			if (le.IsInStorage())
			{
				bool? storageDeviceHasCriticalFault = new WcsDbContext().AisleForLe.GetAisle(le.AisleName, le.StorageArea)?.StorageDevices.Any(s => s.HasCriticalFault);
				if (storageDeviceHasCriticalFault == null)
				{
					Log.Write(LogLevel.Error, $"Unknown aisle {le.AisleName} LE: {le.LeNo}.");
				}
				else if (storageDeviceHasCriticalFault.Value)
				{
					// This throw causes, that the TransportOrder in FromWms will be frozen in status 'Failed'.
					// Maybe the order gets then forgotten.
					Log.Write(LogLevel.Error, $"Order for Le {le.LeNo} will be delayed: {le.AisleName} has a critical fault.");
				}
			}
		}

		private void CheckActiveOrdersForLe()
		{
			//is the an order to a storage location or EMB?
			if (_allStorageAndEmptyLeBufferDestinations.Contains(_transportOrder.Destination))
			{
				using WcsDbContext db = new();
				OrdersHost ordersHost = db.OrdersHost.AsNoTracking().
						ByLeNo(_transportOrder.LeNo).
						Open().
						ByDestination(_allStorageAndEmptyLeBufferDestinations).FirstOrDefault();
				if (ordersHost != null)
				{
					// Replace all open orders to storage or EMB with the new destination
					_transportOrderService.CancelOrdersHost(ordersHost.Id, $"OH on the way to storage or EMB was ordered to a commisioning destination. This order is cancelled and replaced by this new host order with IdOrderWms {_transportOrder.IdOrderWms}.");
					Log.Write(LogLevel.Info, $"HU {ordersHost.LeNo} with destination EMB or storage was ordered to a commisioning destination. The previous OH with IdOrderWms {ordersHost.IdOrderWms} was cancelled");
				}
			}
		}
	}
}