using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using DiplomovaPrace.Contracts.Customer;
using DiplomovaPrace.Contracts.Customer.Products;
using DiplomovaPrace.Contracts.Customer.Products.ViewModels;
using DiplomovaPrace.DataLayer.DataSources.Products;
using DiplomovaPrace.Model.Products;
using Havit.Collections;
using Havit.Data.EntityFrameworkCore.Patterns.QueryServices;
using Havit.Data.Patterns.DataLoaders;
using Havit.Diagnostics.Contracts;
using Havit.Extensions.DependencyInjection.Abstractions;
using Havit.Linq;

namespace DiplomovaPrace.DataLayer.Queries.Products
{
	[Service]
	public class ProductListQuery : QueryBase<ProductVM>, IProductListQuery
	{
		private readonly IProductCategoryDataSource productCategoryDataSource;

		private readonly IProductDataSource productDataSource;

		private readonly IDataLoader dataLoader;

		public ProductListQueryFilter Filter { get; set; }

		public SortItem[] Sorting { get; set; }

		public ProductListQuery(IProductCategoryDataSource productCategoryDataSource, IProductDataSource productDataSource, IDataLoader dataLoader)
		{
			this.productCategoryDataSource = productCategoryDataSource;
			this.productDataSource = productDataSource;
			this.dataLoader = dataLoader;
		}

		protected override IQueryable<ProductVM> Query()
		{
			Contract.Requires(Filter != null);
			Contract.Requires(Filter.ProductCategoryID != null);
			Contract.Requires(Sorting != null);
			List<ProductCategory> descendants = new();

			if (Filter.ProductCategoryID != 0)
			{
				IQueryable<ProductCategory> productCategories = productCategoryDataSource.Data;
				dataLoader.LoadAll(productCategories, i => i.Descendants);

				descendants = GetCategoryDescendants(productCategories.FirstOrDefault(i => i.ID == Filter.ProductCategoryID));
			}

			// Pokud existuje ProductCategoryID, tak vrací kolekci produktů obsahující filtrovanou kategorii i produkty podkategorie
			IQueryable<Product> data = productDataSource.Data
				//.WhereIf(Filter.ProductCategoryID != 0, i => i.ProductCategoryID == Filter.ProductCategoryID)
				.WhereIf(Filter.ProductCategoryID != 0, i => descendants.Contains(i.ProductCategory))
				.WhereIf(!string.IsNullOrWhiteSpace(Filter.SearchString),
				i => i.Name.Contains(Filter.SearchString) || i.ProductCategory.Name.Contains(Filter.SearchString) || i.Description.Contains(Filter.SearchString));

			foreach (SortItem sortingItem in Sorting)
			{
				switch (sortingItem.Expression)
				{
					case nameof(ProductVM.Name):
						data = data.OrderBy(i => i.Name, sortingItem.Direction);
						break;

					case nameof(ProductVM.Price):
						data = data.OrderBy(i => i.Price, sortingItem.Direction);
						break;

					case nameof(ProductVM.AvailableQuantity):
						data = data.OrderBy(i => i.AvailableQuantity, sortingItem.Direction);
						break;

					case nameof(ProductVM.ProductCategoryName):
						data = data.OrderBy(i => i.ProductCategory.Name, sortingItem.Direction);
						break;

					default:
						throw new InvalidOperationException($"Unknown SortingItem.Expression {sortingItem.Expression}.");
				}
			}

			return data.Select(i => new ProductVM
			{
				ID = i.ID,
				Name = i.Name,
				Price = i.Price,
				AvailableQuantity = i.AvailableQuantity,
				ReducedPrice = i.ReducedPrice,
				Description = i.Description,
				ProductCategoryID = i.ProductCategoryID,
				ProductImageIDs = i.ProductImages.Select(i => i.ID).ToList()
			});
		}

		public async Task<PagedQueryResult<ProductVM>> GetPageAsync(int pageIndex, int pageSize)
		{
			return new()
			{
				Data = await SelectPageAsync(pageIndex, pageSize),
				TotalCount = await CountAsync()
			};
		}

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

			foreach (var descendant in productCategory.Descendants)
			{
				result.AddRange(GetCategoryDescendants(descendant));
			}

			return result;
		}
	}
}
