using System;
using System.Collections.Generic;
using System.Linq;

using BeWo.Data.Access;
using BeWo.Data.Entities;
using BeWo.Service.DCEntityMapper;

using BS.Shared;
using BS.Shared.Core;
using BS.Shared.DataContracts;
using BS.Shared.DataContracts.Compact;
using BS.Shared.Extensions;
using BS.Shared.Services;
using BeWo.Service.Configuration;

namespace BeWo.Service.Plugins
{
	public class ServiceRecordValidator : AbstractIDSpecificDefaultClass<ServiceRecordValidator>
	{
		public virtual Dictionary<ServiceRecordDC, List<ServiceRecordValidationResult>> ValidateGroupServiceRecordEntry(ServiceRecordDC pServiceRecord, List<ServiceRecordDC> groupServiceRecords, List<CompactEmployeeDC> employees, DateTime? date, int maxDaysEditServiceRecordsAllowed, decimal flsTotalRounded, out Dictionary<long, string> overlappingRecords, out Dictionary<long, string> overlappingEmployees)
		{
			var result = new Dictionary<ServiceRecordDC, List<ServiceRecordValidationResult>>();
			overlappingRecords = new Dictionary<long, string>();
			overlappingEmployees = new Dictionary<long, string>();
			long gid = pServiceRecord.GroupOid ?? -1;


			foreach (ServiceRecordDC groupRecord in groupServiceRecords)
			{
				DateTime start = groupRecord.Start.Value;
				bool noTime = start.Hour == 0 && start.Minute == 0 && start.Second == 1; //Keine Zeit angegeben

				if (!noTime)
				{
					IList<ServiceRecord> records =
						DAOFactory.SearchDAO.FindServiceRecordsDetailsForLastDays(
							groupRecord.CostBearer2SupportConceptOid.Value, null);

					// 1. ServiceRecordOverlapping
					try
					{
						records = records.Where(r =>
						{
							bool valid = true;

							if (r.GroupOid.HasValue)
								valid = r.GroupOid.Value != gid;

							return valid;
						}).ToList();

						List<ServiceRecord> belongsToSupportConcept = records.ToList();

						bool overlapping =
							belongsToSupportConcept.Any(
								rec =>
									TimeSpanOverlapsWithOtherTimeSpan(groupRecord.Start, rec.Start,
										groupRecord.GroupRoundedDuration ?? groupRecord.RoundedDuration,
										rec.GroupRoundedDuration ?? rec.RoundedDuration));

						if (overlapping)
						{
							result.AddOrUpdateValueInDictionary(groupRecord,
								new List<ServiceRecordValidationResult> { ServiceRecordValidationResult.ServiceRecordOverlapping });

							ServiceRecord rec =
								belongsToSupportConcept.First(
									re =>
										TimeSpanOverlapsWithOtherTimeSpan(groupRecord.Start, re.Start,
											groupRecord.GroupRoundedDuration ?? groupRecord.RoundedDuration,
											re.GroupRoundedDuration ?? re.RoundedDuration));
							overlappingRecords.Add(groupRecord.ServiceRecordOid.Value,
								rec.Start.Value.ToShortTimeString() + ", Dauer " +
								string.Format("{0:0}", rec.GroupRoundedDuration ?? rec.RoundedDuration) + ", " +
								groupRecord.Customer.FullName);
						}
					}
					catch (Exception)
					{
						result.AddOrUpdateValueInDictionary(groupRecord,
							new List<ServiceRecordValidationResult> { ServiceRecordValidationResult.Error });
					}

					// 2. EmployeeOverlapping
					try
					{
						foreach (CompactEmployeeDC employee in employees)
						{
							var span = new DateTimeSpan();
							span.StartDate = start.Date.AddDays(-1);
							span.EndDate = groupRecord.End.Value.Date.AddDays(1);

							IList<ServiceRecord> empRecords = DAOFactory.SearchDAO.FindEmployeeServiceRecords(employee.EmployeeOid, span, null, null);
							if (groupRecord.ServiceRecordOid.HasValue)
							{
								empRecords = empRecords.Where(r => r.Oid != groupRecord.ServiceRecordOid.Value).ToList();
							}
							empRecords = empRecords.Where(r => r.Start.HasValue && r.Start.Value.Second == 0).ToList();


							bool overlapping =
								empRecords.Any(
									record =>
										TimeSpanOverlapsWithOtherTimeSpan(pServiceRecord.Start, record.Start, pServiceRecord.RoundedDuration,
											record.RoundedDuration));

							if (overlapping)
							{
								result.AddOrUpdateValueInDictionary(groupRecord,
									new List<ServiceRecordValidationResult> { ServiceRecordValidationResult.EmployeeOverlapping });
								overlappingEmployees.Add(groupRecord.ServiceRecordOid.Value,
									string.Format("{1}, {0}", employee.FirstName, employee.LastName));
							}
						}
					}
					catch (Exception)
					{
						result.AddOrUpdateValueInDictionary(groupRecord,
							new List<ServiceRecordValidationResult> { ServiceRecordValidationResult.Error });
					}
				}
				// 4. ApprovedFLSOverspending
				try
				{
					SupportConceptCostBearerRelDC reldc = MapperFactory.SupportConceptCostBearerRelDC_CostBearer2SupportConcept.MapToNewDC(DAOFactory.GenericDAO.GetByID<CostBearer2SupportConcept>(groupRecord.CostBearer2SupportConceptOid.Value));
					Calculations calc = PluginLoader.FindClass<Calculations>(reldc.CostBearer.CostBearerID) ?? Calculations.GetInstance(reldc.CostBearer.CostBearerID);
					decimal totalFLSApproved = calc.GetApprovedHoursTotal(reldc, true) ?? 0m;

					if (totalFLSApproved > 0)
					{
						decimal totalFLSNew = flsTotalRounded + (groupRecord.RoundedDuration / 60);
						if (pServiceRecord.ServiceDescription.Category.IsBillable && totalFLSNew > totalFLSApproved)
							result.AddOrUpdateValueInDictionary(groupRecord, new List<ServiceRecordValidationResult> { ServiceRecordValidationResult.OutsideOfSupportConcept });
					}

					// 3. OutsideOfSupportConcept
					if (date.HasValue && reldc.StartDate.HasValue && reldc.EndDate.HasValue)
						if (!date.Value.InBetween(reldc.StartDate.Value, reldc.EndDate.Value, true))
							result.AddOrUpdateValueInDictionary(groupRecord, new List<ServiceRecordValidationResult> { ServiceRecordValidationResult.OutsideOfSupportConcept });
				}
				catch (Exception)
				{
					result.AddOrUpdateValueInDictionary(groupRecord, new List<ServiceRecordValidationResult> { ServiceRecordValidationResult.Error });
				}

				// 6. SettlementInvoiceAlreadyExisting
				try
				{
					IList<SettlementInvoice> allSettlementInvoices = DAOFactory.GenericDAO.GetAll<SettlementInvoice>();

					if (allSettlementInvoices.Where(rechnung => rechnung.InvoiceBase.CostBearer2SupportConcept.Oid.HasValue).Any(rechnung => rechnung.InvoiceBase.CostBearer2SupportConcept.Oid.Value.Equals(groupRecord.CostBearer2SupportConceptOid.Value) && rechnung.IsActive.Equals(ActivationTypeId.Active)))
						result.AddOrUpdateValueInDictionary(groupRecord, new List<ServiceRecordValidationResult> { ServiceRecordValidationResult.SettlementInvoiceAlreadyExisting });
				}
				catch (Exception)
				{
					result.AddOrUpdateValueInDictionary(groupRecord, new List<ServiceRecordValidationResult> { ServiceRecordValidationResult.Error });
				}

				try
				{
					// 5. Frist
					var settings = AppSettings.CreateSettings();

					if (settings.AnzTageZeiterfassErfolgt > 0)
					{
						var dt = new DateTime(date.Value.Year, date.Value.Month, 1);
						dt = dt.AddDays(settings.AnzTageZeiterfassErfolgt);
						if (DateTime.Now >= dt)
							result.AddOrUpdateValueInDictionary(groupRecord, new List<ServiceRecordValidationResult> { ServiceRecordValidationResult.FristAbgelaufen });
					}
				}
				catch (Exception)
				{
					result.AddOrUpdateValueInDictionary(groupRecord, new List<ServiceRecordValidationResult> { ServiceRecordValidationResult.Error });
				}

				if (maxDaysEditServiceRecordsAllowed <= 0)
					return result;

				
				try
				{
					// 6. OutOfEditLimit
					var dt = new DateTime(date.Value.Year, date.Value.Month, 1);
					dt = dt.AddMonths(1);
					dt = dt.AddDays(maxDaysEditServiceRecordsAllowed + 1);
					if (DateTime.Now >= dt)
						result.AddOrUpdateValueInDictionary(groupRecord, new List<ServiceRecordValidationResult> { ServiceRecordValidationResult.OutOfEditLimit });

				}
				catch (Exception)
				{
					result.AddOrUpdateValueInDictionary(groupRecord, new List<ServiceRecordValidationResult> { ServiceRecordValidationResult.Error });
				}
			}

			return result;
		}

		public virtual List<ServiceRecordValidationResultDC> ValidateServiceRecord(ServiceRecordDC newServiceRecord,
			SupportConceptStatisticsDC statistics, int maxDaysEditServiceRecordsAllowed,
			IList<long> employeeOids, IList<long> cb2scOids)
		{
			var result = new List<ServiceRecordValidationResultDC>();


			if (!newServiceRecord.Start.HasValue)
				return result;


			bool isGroupRecord = (cb2scOids != null && cb2scOids.Count > 1) ||
								 (employeeOids != null && employeeOids.Count > 1);


			DateTime start = newServiceRecord.Start.Value;
			bool noTime = start.Hour == 0 && start.Minute == 0 && start.Second == 1; //Keine Zeit angegeben
			if (!noTime)
			{
				noTime = newServiceRecord.ServiceDescription.DoNotCheckOverlapping;
			}

			var duration = (decimal)newServiceRecord.End.Value.Subtract(newServiceRecord.Start.Value).TotalMinutes;
			decimal roundedDuration = duration;
			if (newServiceRecord.ServiceDescription.Category != null &&
					newServiceRecord.ServiceDescription.Category.IsBillable && newServiceRecord.CostBearer != null)
			{
				Calculations calc = Calculations.GetInstance(newServiceRecord.CostBearer.CostBearerID);
				roundedDuration =
					calc.GetRoundedDuration(newServiceRecord.CostBearer, roundedDuration);
			}


			if (isGroupRecord)
			{
				var groupcalc = PluginLoader.FindClass<GroupDurationCalculator>();

				if (groupcalc != null)
				{
					var gd = groupcalc.CalculateGroupDuration(cb2scOids.Count, employeeOids.Count, duration, cb2scOids.ToArray());
					if (gd == null)
					{
						roundedDuration = Math.Round(roundedDuration / cb2scOids.Count, 0, MidpointRounding.AwayFromZero);
					}
					else
					{
						roundedDuration = gd.SingleDuration;
					}

				}
			}


			//var minuteInterval = cbDC.ActualMinuteIntervall;
			//if (minuteInterval > 0)
			newServiceRecord.RoundedDuration = roundedDuration;


			decimal rd = newServiceRecord.RoundedDuration;
			decimal? grd = newServiceRecord.GroupRoundedDuration;

			long? cb2ScOid = null;
			if (newServiceRecord.CostBearer2SupportConceptOid.HasValue)
			{
				cb2ScOid = newServiceRecord.CostBearer2SupportConceptOid.Value;
			}

			if (!noTime)
			{
				bool overlapsCustomer = false;
				if (cb2ScOid.HasValue)
				{
					// 1. Customer ServiceRecordOverlapping
					try
					{
						//Nur für abrechenbare Leistungen prüfen. 
						if (newServiceRecord.ServiceDescription.Category.IsBillable)
						{
							IList<ServiceRecord> records = DAOFactory.SearchDAO.FindServiceRecordsDetailsForLastDays(cb2ScOid.Value,
								null);

							if (newServiceRecord.ServiceRecordOid.HasValue)
							{
								records = records.Where(r => r.Oid != newServiceRecord.ServiceRecordOid.Value).ToList();
							}
							records = records.Where(r => r.Start.HasValue && r.Start.Value.Second == 0).ToList();

							if (isGroupRecord && newServiceRecord.GroupOid.HasValue)
							{
								records =
									records.Where(r => !r.GroupOid.HasValue || !r.GroupOid.Equals(newServiceRecord.GroupOid.Value)).ToList();
							}


							ServiceRecord overlappingRecord =
								records.FirstOrDefault(
									record => record.ServiceDescription.ServiceCategory.IsBillable &&
											  TimeSpanOverlapsWithOtherTimeSpan(start, record.Start,
												  grd ?? rd,
												  record.GroupRoundedDuration ?? record.RoundedDuration));

							if (overlappingRecord != null)
							{
								overlapsCustomer = true;
								var rdc = new ServiceRecordValidationResultDC
								{
									ResultType = ServiceRecordValidationResult.ServiceRecordOverlapping,
									Message = String.Format("{0}, Dauer {1:0} Minuten", overlappingRecord.Start.Value.ToShortTimeString(),
										overlappingRecord.GroupRoundedDuration ?? overlappingRecord.RoundedDuration)
								};

								Customer c = overlappingRecord.Customer;
								if (c != null)
								{
									rdc.CustomerName = String.Format("{0}, {1}", c.Person.LastName, c.Person.FirstName);
								}
								Employee e = overlappingRecord.Employee;
								if (e != null)
								{
									rdc.EmployeeName = String.Format("{0} {1}", e.Person.FirstName, e.Person.LastName);
								}


								result.Add(rdc);
							}
						}
					}
					catch (Exception)
					{
						result.Add(new ServiceRecordValidationResultDC { ResultType = ServiceRecordValidationResult.Error });
					}
				}


				// 2. EmployeeOverlapping
				if (!overlapsCustomer)
				{
					if (!isGroupRecord)
					{
						ServiceRecordValidationResultDC resultdc = CheckOverlappingEmployeeServiceRecords(newServiceRecord, start, newServiceRecord.Employee.EmployeeOid, rd);
						if (resultdc != null)
							result.Add(resultdc);
					}
					else if (employeeOids != null)
					{
						foreach (long employeeOid in employeeOids)
						{
							ServiceRecordValidationResultDC resultdc = null;
							if (resultdc == null)
							{
								resultdc = CheckOverlappingEmployeeServiceRecords(newServiceRecord, start, employeeOid, rd);
								if (resultdc != null)
									result.Add(resultdc);
							}
						}
					}
				}
			}

			try
			{
				// 5. Frist
				var settings = AppSettings.CreateSettings();

				if (settings.AnzTageZeiterfassErfolgt > 0)
				{
					var dt = new DateTime(start.Year, start.Month, start.Day);
					dt = dt.AddDays(settings.AnzTageZeiterfassErfolgt + 1);
					if (DateTime.Now >= dt)
						result.Add(new ServiceRecordValidationResultDC { ResultType = ServiceRecordValidationResult.FristAbgelaufen });
				}
			}
			catch (Exception)
			{
				result.Add(new ServiceRecordValidationResultDC { ResultType = ServiceRecordValidationResult.Error });
			}


			if (cb2ScOid.HasValue)
			{
				// 4. ApprovedFLSOverspending
				try
				{
					SupportConceptCostBearerRelDC reldc =
						MapperFactory.SupportConceptCostBearerRelDC_CostBearer2SupportConcept.MapToNewDC(
							DAOFactory.GenericDAO.GetByID<CostBearer2SupportConcept>(cb2ScOid.Value));
					Calculations calc = PluginLoader.FindClass<Calculations>(reldc.CostBearer.CostBearerID) ?? Calculations.GetInstance(reldc.CostBearer.CostBearerID);
					decimal totalFLSApproved = calc.GetApprovedHoursTotal(reldc, true) ?? 0m;

					if (totalFLSApproved > 0)
					{
						//SupportConceptStatisticsDC stats = statistics;
						//if (stats == null)
						//{
						var factory = PluginLoader.FindClass<ServiceRecordStatisticFactory>();


						if (factory == null)
						{
							factory = new ServiceRecordStatisticFactory();
						}
						var stats = factory.CreateSupportConceptStatisticsImpl(cb2ScOid.Value, DateTime.Now, newServiceRecord.ServiceRecordOid);
						//}


						decimal flmTotalRounded = 0;

						SupportConceptPeriodStatisticsDC periodStat = GetStatisticsForServiceRecord(stats, newServiceRecord);
						if (periodStat != null)
						{
							flmTotalRounded = periodStat.MinutesProvidedTotalRounded;
						}
						else
						{
							flmTotalRounded = stats.MinutesProvidedTotalRounded;
						}

						decimal totalFLSNew = (flmTotalRounded + rd) / 60;
						if (totalFLSNew > totalFLSApproved)
						{
							if (newServiceRecord.ServiceDescription.Category.IsBillable ||
								(periodStat != null && periodStat.SupportConceptApprovalPeriod != null && periodStat.SupportConceptApprovalPeriod.ServiceCategory != null))
							{
								var dc = new ServiceRecordValidationResultDC
								{
									ResultType = ServiceRecordValidationResult.ApprovedFLSOverspending,
									ApprovedHours = Math.Round(totalFLSApproved, 2, MidpointRounding.AwayFromZero),
									HoursNew = Math.Round(totalFLSNew, 2, MidpointRounding.AwayFromZero)
								};
								result.Add(dc);
							}
						}
					}

					// 3. OutsideOfSupportConcept
					if (reldc.StartDate.HasValue && reldc.EndDate.HasValue)
						if (!start.InBetween(reldc.StartDate.Value, reldc.EndDate.Value, true))
						{
							var dc = new ServiceRecordValidationResultDC
							{
								ResultType = ServiceRecordValidationResult.OutsideOfSupportConcept,
								StartDate = reldc.StartDate.Value,
								EndDate = reldc.EndDate.Value
							};
							result.Add(dc);
						}
				}
				catch (Exception)
				{
					result.Add(new ServiceRecordValidationResultDC { ResultType = ServiceRecordValidationResult.Error });
				}

				// 6. SettlementInvoiceAlreadyExisting
				try
				{
					IList<SettlementInvoice> allSettlementInvoices =
						DAOFactory.SearchDAO.GetSettlementInvoiceByCostBearer2SupportConceptOid(cb2ScOid.Value);
					if (allSettlementInvoices.Count > 0)
					{
						result.Add(new ServiceRecordValidationResultDC { ResultType = ServiceRecordValidationResult.SettlementInvoiceAlreadyExisting });
					}
				}
				catch (Exception)
				{
					result.Add(new ServiceRecordValidationResultDC { ResultType = ServiceRecordValidationResult.Error });
				}

				//7. Prüfe ob Datum in Zukunft liegt
				if (start.Date > DateTime.Now)
				{
					result.Add(new ServiceRecordValidationResultDC
					{
						ResultType = ServiceRecordValidationResult.Custom,
						Message = "Das gewählte Datum darf nicht in der Zukunft liegen."
					});
				}
				if (maxDaysEditServiceRecordsAllowed <= 0)
					return result;

				// 8. OutOfEditLimit
				try
				{
					var dt = new DateTime(start.Year, start.Month, 1);
					dt = dt.AddMonths(1);
					dt = dt.AddDays(maxDaysEditServiceRecordsAllowed);
					if (DateTime.Now >= dt)
						result.Add(new ServiceRecordValidationResultDC { ResultType = ServiceRecordValidationResult.OutOfEditLimit });
				}
				catch (Exception)
				{
					result.Add(new ServiceRecordValidationResultDC { ResultType = ServiceRecordValidationResult.Error });
				}
			}

			return result;
		}

		public virtual ServiceRecordValidationResultDC CheckOverlappingEmployeeServiceRecords(ServiceRecordDC newServiceRecord, DateTime start, long employeeOid, decimal roundedDuration)
		{
			var span = new DateTimeSpan();
			span.StartDate = start.Date.AddDays(-1);
			span.EndDate = newServiceRecord.End.Value.Date.AddDays(1);
			IList<ServiceRecord> empRecords = DAOFactory.SearchDAO.FindEmployeeServiceRecords(employeeOid, span, null, null);
			if (newServiceRecord.ServiceRecordOid.HasValue)
			{
				empRecords = empRecords.Where(r => r.Oid != newServiceRecord.ServiceRecordOid.Value).ToList();
			}
			empRecords = empRecords.Where(r => r.Start.HasValue && r.Start.Value.Second == 0).ToList();
			try
			{
				ServiceRecord overlappingRecord =
					empRecords.FirstOrDefault(
						record =>
							TimeSpanOverlapsWithOtherTimeSpan(start, record.Start, roundedDuration, record.RoundedDuration));

				if (overlappingRecord != null)
				{
					var rdc = new ServiceRecordValidationResultDC
					{
						ResultType = ServiceRecordValidationResult.EmployeeOverlapping,
						Message = String.Format("{0}, Dauer {1:0} Minuten", overlappingRecord.Start.Value.ToShortTimeString(),
							overlappingRecord.GroupRoundedDuration ?? overlappingRecord.RoundedDuration)
					};

					Customer c = overlappingRecord.Customer;
					if (c != null)
					{
						rdc.Message += String.Format(" bei Klient/in {0}, {1}", c.Person.LastName, c.Person.FirstName);
					}
					Employee e = overlappingRecord.Employee;
					if (e != null)
					{
						rdc.EmployeeName = String.Format("{0} {1}", e.Person.FirstName, e.Person.LastName);
					}

					return rdc;
				}
			}
			catch (Exception)
			{
				return new ServiceRecordValidationResultDC { ResultType = ServiceRecordValidationResult.Error };
			}
			return null;
		}

		private SupportConceptPeriodStatisticsDC GetStatisticsForServiceRecord(SupportConceptStatisticsDC statistics, ServiceRecordDC newServiceRecord)
		{
			if (statistics != null && statistics.PeriodStatistics.Count > 0)
			{
				foreach (SupportConceptPeriodStatisticsDC pstat in statistics.PeriodStatistics)
				{
					if (BelongsToPeriodStatistic(newServiceRecord, pstat))
						return pstat;
				}
				foreach (SupportConceptPeriodStatisticsDC pstat in statistics.PeriodStatistics)
				{
					if (pstat.SupportConceptApprovalPeriod != null && pstat.SupportConceptApprovalPeriod.ServiceCategory == null)
						return pstat;
				}
			}

			return null;
		}

		public static bool TimeSpanOverlapsWithOtherTimeSpan(DateTime? pStart, DateTime? rStart, decimal pRoundedDuration, decimal rRoundedDuration)
		{
			DateTime? pEnd = pStart.Value.AddMinutes(Convert.ToDouble(pRoundedDuration));
			DateTime? rEnd = rStart.Value.AddMinutes(Convert.ToDouble(rRoundedDuration));

			bool overlaps = pStart == rStart || rEnd == pEnd || pEnd == rStart || pStart == rEnd ||
							rStart > pStart && rStart < pEnd || rEnd > pStart && rEnd < pEnd ||
							pStart > rStart && pStart < rEnd || pEnd > rStart && pEnd < rEnd;

			overlaps = !(pEnd <= rStart || pStart >= rEnd);
			return overlaps;
		}

		private bool BelongsToPeriodStatistic(ServiceRecordDC newServiceRecord, SupportConceptPeriodStatisticsDC pstat)
		{
			if (pstat.SupportConceptApprovalPeriod != null && pstat.SupportConceptApprovalPeriod.ServiceCategory != null)
				if (pstat.SupportConceptApprovalPeriod.ServiceCategory.ServiceCategoryOid == newServiceRecord.ServiceDescription.Category.ServiceCategoryOid)
					return true;
			return false;
		}

	}
}