using ConfectioneryContracts.BindingModels;
using ConfectioneryContracts.BusinessLogicsContracts;
using ConfectioneryContracts.SearchModels;
using ConfectioneryContracts.ViewModels;
using ConfectioneryDataModels.Enums;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConfectioneryBusinessLogic
{
	public class WorkModeling : IWorkProcess
	{
		private readonly ILogger _logger;

		private readonly Random _rnd;

		private IOrderLogic? _orderLogic;

		public WorkModeling(ILogger<WorkModeling> logger)
		{
			_logger = logger;
			_rnd = new Random(1000);
		}

		public void DoWork(IImplementerLogic implementerLogic, IOrderLogic orderLogic)
		{
			_orderLogic = orderLogic;
			var implementers = implementerLogic.ReadList(null);
			if (implementers == null)
			{
				_logger.LogWarning("DoWork. Implementers is null");
				return;
			}
			// Поскольку у нас могут быть заказы в работе мы не дожны заканчивать работы, если нет Принятых заказов
			// Поэтому находим заказы в работе и продолжаем работу, если они есть
			var orders = _orderLogic.ReadList(new OrderSearchModel { Statusses = new() { OrderStatus.Принят, OrderStatus.Выполняется } });
			if (orders == null || orders.Count == 0)
			{
				_logger.LogWarning("DoWork. Orders is null or empty");
				return;
			}
			_logger.LogDebug("DoWork for {Count} orders", orders.Count);
			foreach (var implementer in implementers)
			{
				Task.Run(() => WorkerWorkAsync(implementer, orders));
			}
		}

		/// <summary>
		/// Иммитация работы исполнителя
		/// </summary>
		/// <param name="implementer"></param>
		/// <param name="orders"></param>
		private async Task WorkerWorkAsync(ImplementerViewModel implementer, List<OrderViewModel> orders)
		{
			if (_orderLogic == null || implementer == null)
			{
				return;
			}
			// Заказы выполняются по заданию в таком приоритете:
			// 1. Сначала заказы, которые в статусе ожидаются
			// 2. Потом заказы, которые исполнитель уже выполняет
			// 3. Новые заказы

			// Для заказов находящихся в статусе ожидания работник не должен делать никакую работу, а должен просто пытаться перевести их в статус готов
			await Task.Run(() =>
			{ 
				foreach (var order in orders.Where(x => x.Status == OrderStatus.Ожидается && x.ImplementerId == implementer.Id))
				{
					try
					{
						_orderLogic.FinishOrder(new OrderBindingModel
						{
							Id = order.Id
						});
					}
					// кто-то мог уже перехватить заказ, игнорируем ошибку
					catch (InvalidOperationException ex)
					{
						_logger.LogWarning(ex, "Error try get work");
					}
					// заканчиваем выполнение имитации в случае иной ошибки
					catch (Exception ex)
					{
						_logger.LogError(ex, "Error while do work");
						throw;
					}
					// отдыхаем
					Thread.Sleep(implementer.Qualification * _rnd.Next(10, 100));
				}
			});

			await RunOrderInWork(implementer, orders);

			await Task.Run(() =>
			{
				foreach (var order in orders.Where(x => x.Status == OrderStatus.Принят))
				{
					try
					{
						_logger.LogDebug("DoWork. Worker {Id} try get order {Order}", implementer.Id, order.Id);
						// пытаемся назначить заказ на исполнителя
						_orderLogic.TakeOrderInWork(new OrderBindingModel
						{
							Id = order.Id,
							ImplementerId = implementer.Id
						});
						// делаем работу
						Thread.Sleep(implementer.WorkExperience * _rnd.Next(100, 1000) * order.Count);
						_logger.LogDebug("DoWork. Worker {Id} finish order {Order}", implementer.Id, order.Id);
						_orderLogic.FinishOrder(new OrderBindingModel
						{
							Id = order.Id
						});
					}
					// кто-то мог уже перехватить заказ, игнорируем ошибку
					catch (InvalidOperationException ex)
					{
						_logger.LogWarning(ex, "Error try get work");
					}
					// заканчиваем выполнение имитации в случае иной ошибки
					catch (Exception ex)
					{
						_logger.LogError(ex, "Error while do work");
						throw;
					}
					// отдыхаем
					Thread.Sleep(implementer.Qualification * _rnd.Next(10, 100));
				}
			});

				
		}

		/// <summary>
		/// Ищем заказ, которые уже в работе (вдруг исполнителя прервали)
		/// </summary>
		/// <param name="implementer"></param>
		/// <returns></returns>
		private async Task RunOrderInWork(ImplementerViewModel implementer, List<OrderViewModel> allOrders)
		{
			if (_orderLogic == null || implementer == null || allOrders == null || allOrders.Count == 0)
			{
				return;
			}
			try
			{
				// Выбираем из всех заказов тот, который выполняется данным исполнителем
				var runOrder = await Task.Run(() => allOrders.FirstOrDefault(x => x.ImplementerId == implementer.Id && x.Status == OrderStatus.Выполняется));
				if (runOrder == null)
				{
					return;
				}

				_logger.LogDebug("DoWork. Worker {Id} back to order {Order}", implementer.Id, runOrder.Id);
				// доделываем работу
				Thread.Sleep(implementer.WorkExperience * _rnd.Next(100, 300) * runOrder.Count);
				_logger.LogDebug("DoWork. Worker {Id} finish order {Order}", implementer.Id, runOrder.Id);
				_orderLogic.FinishOrder(new OrderBindingModel
				{
					Id = runOrder.Id
				});
				// отдыхаем
				Thread.Sleep(implementer.Qualification * _rnd.Next(10, 100));
			}
			// заказа может не быть, просто игнорируем ошибку
			catch (InvalidOperationException ex)
			{
				_logger.LogWarning(ex, "Error try get work");
			}
			// а может возникнуть иная ошибка, тогда просто заканчиваем выполнение имитации
			catch (Exception ex)
			{
				_logger.LogError(ex, "Error while do work");
				throw;
			}
		}
	}
}