using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BeWo.Data.Entities;
using BeWo.Service.DCEntityMapper;
using BeWo.Service.Invoicing;
using BS.Shared;
using BS.Shared.Core;
using BS.Shared.DataContracts;
using BS.Shared.Extensions;
using BS.Shared.Services;
using VSDHammReports.Invoicing;

namespace VSDHammReports
{
	//internal class VSDSettlementInvoiceCreation //: SettlementInvoiceCreation
	//{
		//public override bool CanCreateInvoiceTheOldWay(Settlement2DC si, CostBearer2SupportConcept c2s)
		//{
		//	var can = base.CanCreateInvoiceTheOldWay(si, c2s);
		//	if (can)
		//	{
		//		DateTime periodStart = si.AccountingPeriodStart.Value.Date;
		//		DateTime periodEnd = si.AccountingPeriodEnd.Value.Date;
		//		var relDC = c2s.MapToNewDC();
		//		var rateFactorsInSpanList = relDC.CostBearer.CostRatePeriods.GetSpans(CostRatePeriodType.RateFactor, periodStart, periodEnd).ToList();

		//		if (rateFactorsInSpanList.Count > 1)
		//		{
		//			can = false;
		//		}
		//	}
		//	return can;
		//}

		//public override void CalculateInvoiceItemAmounts(InvoiceItemDC ii)
		//{
		//	if (ii.UnitCount.HasValue)
		//	{
		//		var uc = ii.UnitCount.Value;
		//		if (ii.MaxApprovedUnitCount.HasValue && ii.MaxApprovedUnitCount.Value < uc)
		//			uc = ii.MaxApprovedUnitCount.Value;
		//		ii.UnitCount = Math.Round(uc, 2, MidpointRounding.AwayFromZero);

				
		//		if (ii.AmountPerUnit.HasValue)
		//		{
		//			if (ii.RateFactor.HasValue && ii.RateFactor.Value != 0)
		//			{
		//				ii.UnitCount = Math.Round(ii.UnitCount.Value * ((100m + ii.RateFactor.Value) / 100m), 2, MidpointRounding.AwayFromZero);
		//			}

		//			ii.AmountTotal = Math.Round(ii.UnitCount.Value * ii.AmountPerUnit.Value, 2, MidpointRounding.AwayFromZero);
		//			ii.GrossAmountTotal = ii.AmountTotal;
		//		}
		//	}
		//}

	public class VSDSettlementInvoiceCreation : SettlementInvoiceCreation
	{
		public override bool CanCreateInvoiceTheOldWay(Settlement2DC si, CostBearer2SupportConcept c2s)
		{
			return true;
		}

		public override void CreateInvoiceDetailsTheOldWay(Settlement2DC si, CostBearer2SupportConcept c2s)
		{
			String id = c2s.CostBearer.ID;
			
			var calc = Calculations.GetInstance(id);
			var relDc = c2s.MapToNewDC();

			var apList = CreateApprovalPeriods(si, c2s);
			si.ApprovedFLS = CalculateApprovedTotalFLS(apList);

			DateTime periodStart = si.AccountingPeriodStart.Value.Date;
			DateTime periodEnd = si.AccountingPeriodEnd.Value.Date;

			//Prüfe ob vorher abgemeldet
			if (c2s.SupportConcept.Customer.TerminationDate.HasValue &&
				c2s.SupportConcept.Customer.TerminationDate.Value < periodEnd)
			{
				periodEnd = c2s.SupportConcept.Customer.TerminationDate.Value;

			}

			var hourlyRatesInSpanList = relDc.CostBearer.CostRatePeriods.GetSpans(CostRatePeriodType.HourlyRate, periodStart, periodEnd).ToList();

			si.DifferentHourlyRateCount = hourlyRatesInSpanList.Count;

			var rateFactorsInSpanList = relDc.CostBearer.CostRatePeriods.GetSpans(CostRatePeriodType.RateFactor, periodStart, periodEnd).ToList();
			if (rateFactorsInSpanList.Count > 0)
			{
				var cdc = rateFactorsInSpanList.Last().Key;
				if (cdc.CostRateValue.HasValue)
					si.RateFactor = cdc.CostRateValue.Value;
			}
			decimal rf = GetRateFactor(si);

			var records = c2s.ServiceRecords;

			records = records.Where(i => i.Start.HasValue && i.Start.Value.InBetween(periodStart, periodEnd, true)).ToList();

			var recordDCS = records.MapToNewDCs();

			si.RecordedBillableFLSNotBillableCustomerAbsent
				= calc.GetHoursNotBillableDueToAbsenceTimes(relDc, recordDCS,
				c2s.SupportConcept.Customer.GetAbsencesTimesNotBillableWholeWeeks(), false, false);

			si.RecordedBillableFLSNotBillableOutOfApprovedSpan = calc.GetHoursNotBillableDueToOutOfSpan(relDc, recordDCS);
			//calc.GetHoursNotBillableDueToMoreThanApproved(relDC, recordDCS, false, false);

			Dictionary<long, bool> groupBookingDict = new Dictionary<long, bool>();

			
			//decimal flmCleared = 0;
			//decimal approvedMinutes = ((si.ApprovedFLS ?? 0) * 60) / rf;

			records = records.OrderBy<ServiceRecord, DateTime>(sr => sr.Start.HasValue ? sr.Start.Value : DateTime.MinValue).ToList<ServiceRecord>();

			foreach (var iRecord in records)
			{
				if (iRecord.ServiceDescription.ServiceCategory.IsBillable)
				{
					if (iRecord.GroupOid == null || !groupBookingDict.ContainsKey(iRecord.GroupOid.Value))
					{
						decimal rd = iRecord.RoundedDuration;
						if (iRecord.ServiceDescription.ServiceCategory.Percentage != 100)
						{
							rd = (rd * iRecord.ServiceDescription.ServiceCategory.Percentage) / 100;
						}

						
						var ap = apList.FirstOrDefault(
							iap =>
							{
								return iap.PeriodStartDate <= iRecord.Start &&
									   (iap.PeriodEndDate >= DateTime.MaxValue.AddDays(-1) || iRecord.Start < iap.PeriodEndDate.AddDays(1));
								
							});

						//decimal duration = rd / 60;
						if (ap != null)
						{
						//si.RecordedBillableFLSNetto += duration;
							decimal durationToAdd = rd;

							//if (flmCleared + rd > approvedMinutes)
							//{
							//	durationToAdd = approvedMinutes - flmCleared;
							//	if (durationToAdd < 0)
							//		durationToAdd = 0;
							//}
							//flmCleared += rd;
						 

						
							ap.RecordedMinutes += durationToAdd;
						}
						

						

						if (iRecord.GroupOid != null && !groupBookingDict.ContainsKey(iRecord.GroupOid.Value))
						{
							groupBookingDict.Add(iRecord.GroupOid.Value, true);
						}

					}
				}
			}

			
			//decimal? maxAmount = CalculateMaxAmount(si, apList);

			si.InvoiceItems = CreateInvoiceItems(si, apList);

			decimal totalAmount = 0;
			decimal rateFactorAmount = 0;
			foreach (var ii in si.InvoiceItems)
			{
				decimal grossAmount = ii.GrossAmountTotal ?? 0;
				totalAmount += grossAmount;

				if (ii.RateFactor.HasValue && ii.RateFactor.Value > 0)
				{
					rateFactorAmount += (grossAmount - (grossAmount / ((100 + ii.RateFactor.Value) / 100)));
				}
			}


			si.RecordedBillableFLSNetto = si.InvoiceItems.Sum(ii => (ii.UnitCount ?? 0)); // * rf; //eigentlich falsch dass netto = brutto ist.
			si.RecordedBillableFLSBrutto = si.RecordedBillableFLSNetto;

			si.AmountBillableTotal = totalAmount;
			si.AmountWithRateFactor = rateFactorAmount;
			

			//AdvancedPayments
			CalculateAdvancedPayments(si, c2s, null, null, hourlyRatesInSpanList);
			//EquityContribution (Eigenanteil)
			CalculateEquityContribution(si, c2s.SupportConcept, null, null);

			si.Claim = si.AmountBillableTotal - (si.AmountAdvancedPayments ?? 0) - si.EquityContribution;

			
		}

		private decimal GetRateFactor(Settlement2DC si)
		{
			decimal rf = 1;
			if (si.RateFactor.HasValue && si.RateFactor.Value != 0)
			{
				rf = si.RateFactor.Value;
				rf = (100 + rf) / 100;
			}
			return rf;
		}

		private List<InvoiceItemDC> CreateInvoiceItems(Settlement2DC si, IList<ApprovalPeriod> apList)
		{
			List<InvoiceItemDC> iiList = new List<InvoiceItemDC>();



			var apsByHourlyRate = new Dictionary<String, IList<VsdApprovalPeriod>>();

			foreach (var ap in apList)
			{
				VsdApprovalPeriod vap = ap as VsdApprovalPeriod;

				IList<VsdApprovalPeriod> list = null;
				String key = String.Format("{0}_{1}", ap.AmountPerUnit, vap.RateFactor);

				if (apsByHourlyRate.ContainsKey(key))
				{
					list = apsByHourlyRate[key];
				}
				else
				{
					list = new List<VsdApprovalPeriod>();
					apsByHourlyRate[key] = list;
				}

				list.Add(vap);
			}


			decimal maxApprovedFLS = si.ApprovedFLS ?? 0;
			decimal totalHours = 0;
			foreach (var key in apsByHourlyRate.Keys)
			{
				IList<VsdApprovalPeriod> list = apsByHourlyRate[key];
				var ii = new InvoiceItemDC();
				
				decimal minutesTotal = 0;
				DateTime start = DateTime.MaxValue;
				DateTime end = DateTime.MinValue;

				decimal rf = si.RateFactor ?? 0;
				decimal hr = 0;

				foreach (var approvalPeriod in list)
				{
					minutesTotal += approvalPeriod.RecordedMinutes;
					if (approvalPeriod.PeriodStartDate < start)
						start = approvalPeriod.PeriodStartDate;
					if (approvalPeriod.PeriodEndDate > end)
						end = approvalPeriod.PeriodEndDate;

					hr = approvalPeriod.AmountPerUnit;
					if (approvalPeriod.RateFactor.HasValue)
					{
						rf = approvalPeriod.RateFactor.Value;
					}
				}
				ii.ItemPeriodStart = start;
				ii.ItemPeriodEnd = end;
				ii.RateFactor = rf;
				ii.AmountPerUnit = hr;
				decimal hours = Math.Round(minutesTotal / 60m, 2, MidpointRounding.AwayFromZero);
				hours *= (100 + rf) / 100;
				hours = Math.Round(hours, 2, MidpointRounding.AwayFromZero);

				if (totalHours + hours > maxApprovedFLS)
				{
					hours = maxApprovedFLS - totalHours;
					if (hours < 0)
						hours = 0;
				}
				totalHours += hours;


				ii.UnitCount = hours;
				ii.AmountTotal = Math.Round(hr * hours, 2, MidpointRounding.AwayFromZero);
				ii.GrossAmountTotal = ii.AmountTotal;

				iiList.Add(ii);
			}

			return iiList.OrderBy(ii => ii.ItemPeriodStart).ToList();
		}

		private decimal? CalculateApprovedTotalFLS(IList<ApprovalPeriod> apList)
		{
			decimal flsTotal = 0;
			foreach (var ap in apList)
			{
				

				flsTotal += ap.ApprovedTotal;
			}
			return flsTotal;
		}
		

		public override void InitApprovalPeriod(Settlement2DC si, ApprovalPeriod approvalPeriod)
		{
			base.InitApprovalPeriod(si, approvalPeriod);
			approvalPeriod.Weeks = Math.Round(approvalPeriod.Days*0.1427m, 2);
			if (approvalPeriod.ApprovalUnit == AccountingIntervalType.Weekly)
			{
				approvalPeriod.ApprovedTotal = Math.Round(approvalPeriod.Weeks*approvalPeriod.ApprovedPerUnit, 2);
			}
			approvalPeriod.ApprovedAmountTotal = Math.Round(approvalPeriod.ApprovedTotal * approvalPeriod.AmountPerUnit, 2);
		}

		public override IList<ApprovalPeriod> CreateApprovalPeriods(Settlement2DC si, CostBearer2SupportConcept c2s)
		{
			var relDc = c2s.MapToNewDC();

			var hourlyRatesInSpanList = relDc.CostBearer.CostRatePeriods.GetSpans(CostRatePeriodType.HourlyRate,
				si.AccountingPeriodStart.Value.Date, si.AccountingPeriodEnd.Value.Date).ToList();

			Dictionary<DateTime, VsdApprovalPeriod> dtApDict = new Dictionary<DateTime, VsdApprovalPeriod>();
			foreach (var keyValuePair in hourlyRatesInSpanList)
			{
				var ap = new VsdApprovalPeriod();
				ap.PeriodStartDate = keyValuePair.Value.StartDate;
				ap.PeriodEndDate = DateTime.MaxValue;
				dtApDict.Add(ap.PeriodStartDate, ap);
			}

			var rateFactorInSpanList = relDc.CostBearer.CostRatePeriods.GetSpans(CostRatePeriodType.RateFactor,
				si.AccountingPeriodStart.Value.Date, si.AccountingPeriodEnd.Value.Date).ToList();

			foreach (var keyValuePair in rateFactorInSpanList)
			{
				if (!dtApDict.ContainsKey(keyValuePair.Value.StartDate))
				{
					var ap = new VsdApprovalPeriod();
					ap.PeriodStartDate = keyValuePair.Value.StartDate;
					ap.PeriodEndDate = DateTime.MaxValue;
					dtApDict.Add(ap.PeriodStartDate, ap);
				}
			}

			foreach (var scap in c2s.ApprovalPeriodList)
			{
				if (scap.StartDate.HasValue && scap.EndDate.HasValue)
				{
					if (!dtApDict.ContainsKey(scap.StartDate.Value))
					{
						if ((scap.ApprovedBEInterval.HasValue && scap.ApprovedBEPerInterval.HasValue) || scap.ApprovedBETotal.HasValue)
						{
							var ap = new VsdApprovalPeriod();
							ap.PeriodStartDate = scap.StartDate.Value;
							ap.PeriodEndDate = DateTime.MaxValue;
							dtApDict.Add(ap.PeriodStartDate, ap);
						}
					}
				}
			}

			foreach (var dt in dtApDict.Keys)
			{
				VsdApprovalPeriod ap = dtApDict[dt];
				var hrspan = hourlyRatesInSpanList.FirstOrDefault(ispan => ispan.Value.StartDate <= dt && dt <= ispan.Value.EndDate);
				var rfspan = rateFactorInSpanList.FirstOrDefault(ispan => ispan.Value.StartDate <= dt && dt <= ispan.Value.EndDate);

				var scap = c2s.ApprovalPeriodList.FirstOrDefault(iscap => iscap.StartDate <= dt && dt <= iscap.EndDate);

				if (hrspan.Key != null && hrspan.Key.CostRateValue.HasValue)
				{
					ap.AmountPerUnit = hrspan.Key.CostRateValue.Value;
					ap.BillingInterval = AccountingIntervalType.Hourly;
				}
				if (hrspan.Value != null && hrspan.Value.EndDate < ap.PeriodEndDate)
				{
					ap.PeriodEndDate = hrspan.Value.EndDate;
				}
				if (rfspan.Key != null && rfspan.Key.CostRateValue.HasValue)
				{
					ap.RateFactor = rfspan.Key.CostRateValue.Value;
				}
				if (rfspan.Value != null && rfspan.Value.EndDate < ap.PeriodEndDate)
				{
					ap.PeriodEndDate = rfspan.Value.EndDate;
				}
				if (scap != null)
				{
					if (scap.EndDate.HasValue && scap.EndDate < ap.PeriodEndDate)
						ap.PeriodEndDate = scap.EndDate.Value;

					CalculateApprovedAmountForApprovalPeriod(ap, scap);
				}
			}


			var list = dtApDict.Values.OrderBy(ap => ap.PeriodStartDate).ToList();

			foreach (var approvalPeriod in list)
			{
				InitApprovalPeriod(si, approvalPeriod);
			}

			var returnlist = new List<ApprovalPeriod>();
			foreach (var vap in list)
			{
				returnlist.Add(vap);
			}
			return returnlist;
		}
	}
}
