using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using DiplomovaPrace.Contracts.ProductCategories;
using DiplomovaPrace.Contracts.ProductCategories.InputModels;
using DiplomovaPrace.Contracts.ProductCategories.ViewModels;
using DiplomovaPrace.DataLayer.Repositories.Products;
using DiplomovaPrace.Model.Products;
using Havit.Data.Patterns.UnitOfWorks;

namespace DiplomovaPrace.Facades.ProductCategories
{
	public class ProductCategoryFacade : IProductCategoryFacadeContract
	{
		private readonly IUnitOfWork unitOfWork;
		private readonly IProductCategoryRepository productCategoryRepository;

		public ProductCategoryFacade(IUnitOfWork unitOfWork, IProductCategoryRepository productCategoryRepository)
		{
			this.unitOfWork = unitOfWork;
			this.productCategoryRepository = productCategoryRepository;
		}

		// TODO: validovat data
		public async Task<ProductCategoryDto> CreateProductCategoryAsync(ProductCategoryIM inputModel)
		{
			ProductCategory newProductCategory = new ProductCategory
			{
				Name = inputModel.Name,
				Description = inputModel.Description
			};

			if (inputModel.ParentID is not null)
			{
				ProductCategory parent = await productCategoryRepository.GetObjectAsync(inputModel.ParentID.Value).ConfigureAwait(false);

				if (parent is not null)
				{
					newProductCategory.Parent = parent;
				}
				else
				{
					return new ProductCategoryDto();
				}
			}
			else
			{
				newProductCategory.ParentID = (int)ProductCategory.Entry.Root;
			}

			unitOfWork.AddForInsert(newProductCategory);
			await unitOfWork.CommitAsync().ConfigureAwait(false);

			return new ProductCategoryDto
			{
				ID = newProductCategory.ID,
				Name = newProductCategory.Name,
				Description = newProductCategory.Description ?? "",
				ParentID = newProductCategory.ParentID
			};
		}

		public async Task<List<ProductCategoryDto>> GetAllProductCategoriesAsync()
		{
			List<ProductCategory> productCategories = await productCategoryRepository.GetAllAsync().ConfigureAwait(false);

			if (productCategories.Count > 0)
			{
				return productCategories
					.Where(i => i.ID != (int)ProductCategory.Entry.Root)
					.OrderBy(i => i.Name)
					.Select(i => new ProductCategoryDto
					{
						ID = i.ID,
						Name = i.Name,
						Description = i.Description,
						ParentID = i.ParentID
					}).ToList();
			}

			return new List<ProductCategoryDto>();
		}

		public async Task<GetCategoryDescendantsResult> GetCategoryDescendantsAsync(GetCategoryDescendantsRequest request)
		{
			await productCategoryRepository.GetAllAsync().ConfigureAwait(false);
			ProductCategory productCategory = await productCategoryRepository.GetObjectAsync(request.ID).ConfigureAwait(false);

			if (productCategory is not null)
			{
				List<ProductCategory> descendants = GetDescendants(productCategory).Where(i => i.ID != request.ID).ToList();

				return new GetCategoryDescendantsResult
				{
					IsSuccessful = true,
					Descendants = descendants.Select(i => new ProductCategoryDto
					{
						ID = i.ID,
						Name = i.Name,
						Description = i.Description,
						ParentID = i.ParentID
					}).ToList()
				};
			}

			return new GetCategoryDescendantsResult();
		}

		// TODO: validovat data
		public async Task UpdateProductCategoryAsync(ProductCategoryDto inputModel)
		{
			ProductCategory productCategory = await productCategoryRepository.GetObjectAsync(inputModel.ID).ConfigureAwait(false);
			ProductCategory parent = await productCategoryRepository.GetObjectAsync((int)inputModel.ParentID).ConfigureAwait(false);

			if (productCategory is not null)
			{
				productCategory.Name = inputModel.Name;
				productCategory.Description = inputModel.Description ?? "";

				if (parent is not null)
				{
					productCategory.Parent = parent;
				}

				unitOfWork.AddForUpdate(productCategory);
				await unitOfWork.CommitAsync().ConfigureAwait(false);
			}
		}

		public async Task<DeleteProductCategoryResult> DeleteProductCategoryAsync(DeleteProductCategoryRequest request)
		{
			// pre-load of all categories
			await productCategoryRepository.GetAllAsync().ConfigureAwait(false);
			ProductCategory productCategory = await productCategoryRepository.GetObjectAsync(request.ID).ConfigureAwait(false);

			if (productCategory is not null)
			{
				List<ProductCategory> descendants = GetDescendants(productCategory);

				if (descendants.Count > 0)
					unitOfWork.AddRangeForDelete(descendants);

				await unitOfWork.CommitAsync().ConfigureAwait(false);

				return new DeleteProductCategoryResult { IsSuccessful = true, DeletedCategoryIDs = new List<int>(descendants.Select(i => i.ID)) };
			}

			return new DeleteProductCategoryResult { IsSuccessful = false };
		}

		private List<ProductCategory> GetDescendants(ProductCategory productCategory)
		{
			List<ProductCategory> result = new List<ProductCategory>
			{
				productCategory
			};

			foreach (ProductCategory item in productCategory.Descendants)
			{
				result.AddRange(GetDescendants(item));
			}

			return result;
		}
	}
}
