using System;
using System.Collections.Generic;
using System.Linq;
using AutoMapper;
using InventoryTraker.Web.Core;
using InventoryTraker.Web.Data;
using InventoryTraker.Web.Models;
using InventoryTraker.Web.Utilities;

namespace InventoryTraker.Web.Services
{
	public class ReportService
	{
		private readonly AppDbContext _context;
		private readonly IMapper _mapper;

		public ReportService(AppDbContext context, IMapper mapper)
		{
			_context = context;
			_mapper = mapper;
		}

		public DistributionReport[] GetDistributionReport(DateTime startDate, DateTime endDate)
		{
			var query =
				from t in _context.Transactions
				where
					t.TransactionType == TransactionType.Distributed
					&& t.TransactionDate >= startDate
					&& t.TransactionDate < endDate
				group t by new { t.TransactionDate, t.Destination }
				into g
				select new
				{
					Date = g.Key.TransactionDate,
					Destination = g.Key.Destination,
					Transactions = g.ToList()
				};

			var report =
				from item in query.ToArray()
				select new DistributionReport
				{
					Date = item.Date,
					Destination = item.Destination,
					Transactions =
						_mapper.Map<IList<Transaction>, IList<TransactionViewModel>>
							(item.Transactions).ToArray()
				};

			return report.ToArray();
		}

		public MovementReport GetMovementReport(DateTime month)
		{
			var startDate = month;
			var endDate = startDate.AddMonths(1);

			return
				new MovementReport
				{
					Items = GetMovementReportItems(startDate, endDate),
					Month = month
				};
		}

		private IEnumerable<MovementReportItem> GetMovementReportItems(DateTime startDate, DateTime endDate)
		{
			var transactionsMostRecentBefore =
				(from transaction in _context.Transactions
				 where
					 transaction.TransactionDate < startDate
				 group transaction by transaction.Inventory
					into g
				 let mostRecent =
					 g.OrderByDescending(t => t.TransactionDate)
						 .ThenBy(t => t.CurrentQuantity) // for days with multiple, assume it's the smallest
						 .FirstOrDefault()
				 where mostRecent.CurrentQuantity > 0
				 select mostRecent).ToList();

			var transactionSums =
				(from transaction in _context.Transactions
				 where
					 transaction.TransactionDate >= startDate
					 && transaction.TransactionDate < endDate
				 group transaction by transaction.Inventory
					into g
				 let addedQty = g.Sum(t => t.AddedQuantity)
				 let distributed = g.Where(t => t.TransactionType == TransactionType.Distributed)
				 let distributedQty = distributed.Any() ? distributed.Sum(t => t.RemovedQuantity) : 0
				 let adjustment = g.Where(t =>
					 t.TransactionType == TransactionType.Expired
					 || t.TransactionType == TransactionType.Loss)
				 let adjustmentQty = adjustment.Any() ? adjustment.Sum(t => t.RemovedQuantity) : 0
				 let endingQty =
					 g
						 .OrderByDescending(t => t.TransactionDate)
						 .ThenBy(t => t.CurrentQuantity)
						 .FirstOrDefault().CurrentQuantity
				 select new
				 {
					 Inventory = g.Key,
					 addedQty,
					 adjustmentQty,
					 distributedQty,
					 endingQty
				 }).ToList();

			var inventoryReportItems =
				transactionsMostRecentBefore.FullOuterJoin( // source
					transactionSums, // inner
					before => before.Inventory.Id, // fk
					sums => sums.Inventory.Id, // pk
					(before, sums, r) =>
					{
						var item = new MovementReportInventoryItem();

						if (before != null)
						{
							item.Inventory = before.Inventory;
							item.BeginningQuantity = before.CurrentQuantity;
							if (sums != null)
							{
								item.AddedQuantity = sums.addedQty;
								item.DistributedQuantity = sums.distributedQty;
								item.AdjustmentQuantity = sums.adjustmentQty;
								item.EndingQuantity = sums.endingQty;
							}
							else // no change
							{
								item.EndingQuantity = item.BeginningQuantity;
							}
						}
						else if (sums != null) // item was added in this time period
						{
							item.Inventory = sums.Inventory;
							item.AddedQuantity = sums.addedQty;
							item.DistributedQuantity = sums.distributedQty;
							item.AdjustmentQuantity = sums.adjustmentQty;
							item.EndingQuantity = sums.endingQty;
						}
						item.TotalAvailableQuantity = item.BeginningQuantity + item.AddedQuantity;
						return item;
					}).ToArray();

			// group by inventory type
			var inventoryTypeReportItems =
				from item in inventoryReportItems
				group item by item.Inventory.InventoryType
				into grp
				select new MovementReportItem
				{
					InventoryType = _mapper.Map<InventoryTypeViewModel>(grp.Key),
					BeginningQuantity = grp.Sum(g => g.BeginningQuantity),
					AddedQuantity = grp.Sum(g => g.AddedQuantity),
					TotalAvailableQuantity = grp.Sum(g => g.TotalAvailableQuantity),
					DistributedQuantity = grp.Sum(g => g.DistributedQuantity),
					AdjustmentQuantity = grp.Sum(g => g.AdjustmentQuantity),
					EndingQuantity = grp.Sum(g => g.EndingQuantity)
				};
			return inventoryTypeReportItems;
		}

		private class MovementReportInventoryItem
		{
			public Inventory Inventory { get; set; }
			public int BeginningQuantity { get; set; }
			public int AddedQuantity { get; set; }
			public int TotalAvailableQuantity { get; set; }
			public int DistributedQuantity { get; set; }
			public int AdjustmentQuantity { get; set; }
			public int EndingQuantity { get; set; }
		}
	}
}