using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using BeWo.Data;
using BeWo.Service.Multitenancy;
using BeWo.Service.Plugins;
using BS.Shared.DataContracts.Compact;
using BeWo.Data.Access;
using BeWo.Data.Entities;
using BeWo.Service.Core;
using BeWo.Service.DCEntityMapper;
using BeWo.Service.ServiceContracts;

using BS.Shared;
using BS.Shared.Core;
using BS.Shared.DataContracts;

using Utils = BeWo.Service.Core.Utils;
using BeWo.Service.Invoicing;

namespace BeWo.Service.ServiceImplementations
{
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall, ConcurrencyMode = ConcurrencyMode.Single)]
    public class AccountingServiceImp : IAccountingService
    {
        public void DeactivateInvoiceBases(List<InvoiceBaseDC> invoices)
        {
            try
            {
                ServiceLogic.SetActivationType<InvoiceBase>(invoices.ToDictionary(i => i.InvoiceBaseOid.Value, i => i.InvoiceBaseVersion.Value), ActivationTypeId.Deleted);
            }
            catch (Exception e)
            {
                throw Utils.CreateBeWoFaultException(e);
            }
        }

        public void DeactivateServiceInvoices(List<ServiceInvoiceDC> invoices)
        {
            try
            {
                ServiceLogic.SetActivationType<ServiceInvoice>(invoices.ToDictionary(i => i.ServiceInvoiceOid.Value, i => i.ServiceInvoiceVersion.Value), ActivationTypeId.Deleted);
                ServiceLogic.SetActivationType<InvoiceBase>(invoices.ToDictionary(i => i.InvoiceBase.InvoiceBaseOid.Value, i => i.InvoiceBase.InvoiceBaseVersion.Value), ActivationTypeId.Deleted);
            }
            catch (Exception e)
            {
                throw Utils.CreateBeWoFaultException(e);
            }
        }

        public void DeactivateSettlementInvoice(List<Settlement2DC> invoices)
        {
            try
            {
                ServiceLogic.SetActivationType<SettlementInvoice>(invoices.ToDictionary(i => i.SettlementIvoiceOid.Value, i => i.SettlementInvoiceVersion.Value), ActivationTypeId.Deleted);
                ServiceLogic.SetActivationType<InvoiceBase>(invoices.ToDictionary(i => i.InvoiceBaseOid.Value, i => i.InvoiceBaseVersion.Value), ActivationTypeId.Deleted);
            }
            catch (Exception e)
            {
                throw Utils.CreateBeWoFaultException(e);
            }
        }

        public List<ServiceInvoiceDC> GenerateInitialServiceInvoices(
            long costBearerOid,
            long? supportConceptOid,
            DateTimeSpan invoicePeriod)
        {
            try
            {
                var operationService = new OperationsServiceImp();

				IEnumerable<CompactSupportConceptDC> supportConcepts;
                // GetAllActiveSupportConceptCostbearer returns a "Flat" SupportConcept List, one for each CostBearer2SupportConcept!
				if (!supportConceptOid.HasValue)
				{
					supportConcepts = operationService.GetAllActiveSupportConceptCostbearer(true, true, 1)
						.Where(i => i.CostBearerOids2CostBearerRelOids.ContainsKey(costBearerOid) && i.IsApproved);
				}
				else
				{
					supportConcepts = operationService.GetCompactSupportConcepts(supportConceptOid.Value, null)
						.Where(i => i.CostBearerOids2CostBearerRelOids.ContainsKey(costBearerOid) && i.IsApproved);
				}

				DateTimeSpan newSpan = null;
				if (invoicePeriod != null)
				{
					//invoicePeriod.StartDateTime = invoicePeriod.StartDateTime.Date;
					//invoicePeriod.EndDateTime = invoicePeriod.EndDateTime.Date;
					newSpan = new DateTimeSpan();
					newSpan.StartDateTime = invoicePeriod.StartDateTime.Date;
					newSpan.EndDateTime = invoicePeriod.EndDateTime.Date.AddDays(1);
					if (!supportConceptOid.HasValue)
					{
						supportConcepts =
							supportConcepts.Where(sc => sc.StartDate < newSpan.EndDate && sc.EndDate >= newSpan.StartDate);
					}
				}
				var supportConcepts2ServiceRecords = supportConcepts.ToDictionary(
                                                                                      i => i,
                                                                                      i => MapperFactory.ServiceRecordDC_ServiceRecord.MapToNewDCs(
                                                                                                                                                   DAOFactory.SearchDAO.FindServiceRecordsInSpan(i.CostBearerRelOids[0], newSpan)));
				var cb = DAOFactory.GenericDAO.LoadByID<CostBearer>(costBearerOid);
				String tenant = null;

				if (MultitenancyOperationContextExt.Current != null)
					tenant = MultitenancyOperationContextExt.Current.Tenant;

				var factory = PluginLoader.FindClass<InvoiceFactory>(cb.ID);

	            if (factory == null)
	            {
		            factory = InvoiceFactory.GetInstance(cb.ID, tenant);
	            }

	            var creator = factory.CreateInvoiceCreator(cb.ID, tenant, supportConcepts2ServiceRecords);

				//String text = String.Format("Tenant={0}, Context=Null? {1}, Factory = {2}", tenant,
				//	MultitenancyOperationContextExt.Current == null, factory);

				//Utils.SendErrorMail("CB DEBUG", text);

                return creator.GenerateServiceInvoices(costBearerOid, invoicePeriod, supportConcepts2ServiceRecords);
            }
            catch (Exception e)
            {
                throw Utils.CreateBeWoFaultException(e);
            }
        }

        public List<InvoiceBaseDC> GetAllActiveInvoiceBases()
        {
            try
            {
                return MapperFactory.InvoiceBaseDC_InvoiceBase.MapToNewDCs(DAOFactory.GenericDAO.GetAllActive<InvoiceBase>());
            }
            catch (Exception e)
            {
                throw Utils.CreateBeWoFaultException(e);
            }
        }

		public List<InvoiceBaseDC> GetInvoiceBases(DateTime? startDate, DateTime? endDate)
		{
			try
			{
				DateTime? end = endDate;
				if (end.HasValue)
				{
					end = end.Value.Date.AddDays(1).AddTicks(-1);
				}
				var list = DAOFactory.SearchDAO.GetInvoiceBases(startDate, end);

				return MapperFactory.InvoiceBaseDC_InvoiceBase.MapToNewDCs(list);
			}
			catch (Exception e)
			{
				throw Utils.CreateBeWoFaultException(e);
			}
		}

        public List<InvoiceBaseDC> GetGeneralInvoiceBasesForCustomer(long customerOid)
        {
            try
            {
                return MapperFactory.InvoiceBaseDC_InvoiceBase.MapToNewDCs(DAOFactory.SearchDAO.GetInvoiceBaseByCustomerOid(InvoiceType.General, customerOid));
            }
            catch (Exception e)
            {
                throw Utils.CreateBeWoFaultException(e);
            }
        }

        public List<InvoiceBaseDC> GetGeneralInvoiceBasesForOrganisation(long organisationOid)
        {
            try
            {
                return MapperFactory.InvoiceBaseDC_InvoiceBase.MapToNewDCs(DAOFactory.SearchDAO.GetInvoiceBaseByOrganisationOid(InvoiceType.General, organisationOid));
            }
            catch (Exception e)
            {
                throw Utils.CreateBeWoFaultException(e);
            }
        }

        public List<InvoiceBaseDC> GetGeneralInvoiceBasesForPerson(long personOid)
        {
            try
            {
                return MapperFactory.InvoiceBaseDC_InvoiceBase.MapToNewDCs(DAOFactory.SearchDAO.GetInvoiceBaseByPersonOid(InvoiceType.General, personOid));
            }
            catch (Exception e)
            {
                throw Utils.CreateBeWoFaultException(e);
            }
        }

        public List<InvoiceBaseDC> GetInvoiceBasesForSupportConcept(InvoiceType type, long supportConceptOid)
        {
            try
            {
                return MapperFactory.InvoiceBaseDC_InvoiceBase.MapToNewDCs(DAOFactory.SearchDAO.GetInvoiceBaseByTypeAndSupportConceptOid(type, supportConceptOid));
            }
            catch (Exception e)
            {
                throw Utils.CreateBeWoFaultException(e);
            }
        }

        public ServiceInvoiceDC GetServiceInvoiceByInvoiceBaseOid(long invoiceBaseOid)
        {
            try
            {
                var dc = MapperFactory.ServiceInvoiceDC_ServiceInvoice.MapToNewDC(DAOFactory.SearchDAO.GetServiceInvoiceByInvoiceBaseOid(invoiceBaseOid));

                SortServiceInvoicePeriods(dc);
                return dc;
            }
            catch (Exception e)
            {
                throw Utils.CreateBeWoFaultException(e);
            }
        }

        public List<ServiceInvoiceDC> GetServiceInvoices(long costBearerOid, long? supportConceptOid, DateTimeSpan period)
        {
            try
            {
                List<ServiceInvoice> entities = DAOFactory.SearchDAO.GetServiceInvoices(costBearerOid, supportConceptOid, period);
                entities.Sort((x, y) =>
                              {
                                  if (y.InvoiceBase.InvoiceDate.Value.Date.Equals(x.InvoiceBase.InvoiceDate.Value.Date))
                                  {
                                      return y.InvoiceBase.InvoiceNumber.CompareTo(x.InvoiceBase.InvoiceNumber);
                                  }

                                  return y.InvoiceBase.InvoiceDate.Value.Date.CompareTo(x.InvoiceBase.InvoiceDate.Value.Date);
                              });
                
                var dcList = MapperFactory.ServiceInvoiceDC_ServiceInvoice.MapToNewDCs(entities);

                foreach (var serviceInvoice in dcList)
                {
                    SortServiceInvoicePeriods(serviceInvoice);
                }
                return dcList;
            }
            catch (Exception e)
            {
                throw Utils.CreateBeWoFaultException(e);
            }
        }

        private void SortServiceInvoicePeriods(ServiceInvoiceDC serviceInvoice)
        {
            if (serviceInvoice.ServiceInvoicePeriods != null)
            {
                serviceInvoice.ServiceInvoicePeriods.Sort((x, y) =>
                {
                    if (x.Customer != null && y.Customer != null)
                        return x.Customer.ToString().CompareTo(y.Customer.ToString());
                    else if (x.Start != null && y.Start != null)
                    {
                        x.Start.Value.CompareTo(y.Start.Value);
                    }
                    return 0;
                });
            }
        }

        public Settlement2DC GetSettlementByInvoiceBaseOid(long invoiceBaseOid)
        {
            try
            {
                return MapperFactory.SettlementDC_SettlementInvoice.MapToNewDC(DAOFactory.SearchDAO.GetSettlementInvoiceByInvoiceBaseOid(invoiceBaseOid));
            }
            catch (Exception e)
            {
                throw Utils.CreateBeWoFaultException(e);
            }
        }

        public List<Settlement2DC> GetSettlementInvoicesForCostBearerAndPeriod(long costBearerOid, DateTime periodStart, DateTime periodEnd)
        {
            try
            {
                return MapperFactory.SettlementDC_SettlementInvoice.MapToNewDCs(DAOFactory.SearchDAO.GetSettlementInvoicesForCostBearerAndPeriod(costBearerOid, periodStart, periodEnd));
            }
            catch (Exception e)
            {
                throw Utils.CreateBeWoFaultException(e);
            }
        }

        public List<Settlement2DC> GetSettlementInvoicesForSupportConcept(long supportConceptOid)
        {
            try
            {
                return MapperFactory.SettlementDC_SettlementInvoice.MapToNewDCs(DAOFactory.SearchDAO.GetSettlementInvoiceBySupportConceptOid(supportConceptOid));
            }
            catch (Exception e)
            {
                throw Utils.CreateBeWoFaultException(e);
            }
        }

        public List<long> InsertNewInvoiceBases(List<InvoiceBaseDC> invoices)
        {
            try
            {
                var newInvoices = MapperFactory.InvoiceBaseDC_InvoiceBase.MapToNewEntities(invoices);
                int count = 0;

	            var inc = PluginLoader.FindClass<InvoiceNumberGenerator>();
	            
	            foreach (var ib in newInvoices)
	            {
					var nextNumber = inc.GetNextInvoiceNumber(count, ib);
		            if (!String.IsNullOrEmpty(nextNumber))
		            {
			            if (!String.IsNullOrEmpty(ib.InvoiceNumber)) // Rechnungsnummer schon vorher erzeugt oder per Hand vergeben.
			            {
							if (ib.InvoiceNumber == nextNumber)
					            //Die per Hand vergebene Rechnungsnummer ist gleich der nächsten berechneten Rechnungsnummer, daher Rechnungsnummerzähler erhöhen
				            {
					            count++;
				            }
			            }
			            else
			            {
				            ib.InvoiceNumber = nextNumber;
				            count++;
			            }
		            }
	            }
                DAOFactory.GenericDAO.Insert(newInvoices);
                
                inc.IncreaseInvoiceNumber(count);

                return newInvoices.Select(i => i.Oid.Value).ToList();
            }
            catch (Exception e)
            {
                throw Utils.CreateBeWoFaultException(e);
            }
        }

        public List<long> InsertNewServiceInvoices(List<ServiceInvoiceDC> invoices)
        {
            try
            {
                var newInvoices = MapperFactory.ServiceInvoiceDC_ServiceInvoice.MapToNewEntities(invoices);
	            if (newInvoices.Count > 0)
	            {
		            int count = 0;
		            var inc = PluginLoader.FindClass<InvoiceNumberGenerator>();

		            foreach (var i in newInvoices)
		            {
			            var nextNumber = inc.GetNextInvoiceNumber(count, i.InvoiceBase);
			            if (!String.IsNullOrEmpty(nextNumber))
			            {
				            if (!String.IsNullOrEmpty(i.InvoiceBase.InvoiceNumber))
					            // Rechnungsnummer schon vorher erzeugt oder per Hand vergeben.
				            {
					            if (i.InvoiceBase.InvoiceNumber == nextNumber || newInvoices.Count > 1)
						            //Die per Hand vergebene Rechnungsnummer ist gleich der nächsten berechneten Rechnungsnummer, daher Rechnungsnummerzähler erhöhen
					            {
						            count++;
					            }
				            }
				            else
				            {
					            i.InvoiceBase.InvoiceNumber = nextNumber;
					            count++;
				            }
			            }
		            }

		            DAOFactory.GenericDAO.Insert(newInvoices);

		            inc.IncreaseInvoiceNumber(count);
	            }
	            return newInvoices.Select(i => i.Oid.Value).ToList();
            }
            catch (Exception e)
            {
                throw Utils.CreateBeWoFaultException(e);
            }
        }


        public List<long> InsertNewSettlementInvoice(List<Settlement2DC> invoices)
        {
            try
            {

                var newInvoices = MapperFactory.SettlementDC_SettlementInvoice.MapToNewEntities(invoices);
                int count = 0;
				var inc = PluginLoader.FindClass<InvoiceNumberGenerator>();

				foreach (var i in newInvoices)
				{
					var nextNumber = inc.GetNextInvoiceNumber(count, i.InvoiceBase);
					if (!String.IsNullOrEmpty(nextNumber))
					{
						if (!String.IsNullOrEmpty(i.InvoiceBase.InvoiceNumber)) // Rechnungsnummer schon vorher erzeugt oder per Hand vergeben.
						{
							if (i.InvoiceBase.InvoiceNumber == nextNumber)
							//Die per Hand vergebene Rechnungsnummer ist gleich der nächsten berechneten Rechnungsnummer, daher Rechnungsnummerzähler erhöhen
							{
								count++;
							}
						}
						else
						{
							i.InvoiceBase.InvoiceNumber = nextNumber;
							count++;
						}
					}
				}

                DAOFactory.GenericDAO.Insert(newInvoices);

                inc.IncreaseInvoiceNumber(count);
                return newInvoices.Select(i => i.Oid.Value).ToList();
            }
            catch (Exception e)
            {
                throw Utils.CreateBeWoFaultException(e);
            }
        }

        public InvoiceBaseDC LoadInvoiceBaseByOid(long invoiceBaseOid)
        {
            try
            {
                var r = MapperFactory.InvoiceBaseDC_InvoiceBase.MapToNewDC(
                DAOFactory.GenericDAO.LoadByID<InvoiceBase>(invoiceBaseOid));

                if (r.Type == InvoiceType.Settlement && r.SupportConceptOid != null)
                {
                    SupportConcept sc = DAOFactory.GenericDAO.LoadByID<SupportConcept>(r.SupportConceptOid.Value);
                    r.CustomerFirstName = sc.Customer.Person.FirstName;
                    r.CustomerLastName = sc.Customer.Person.LastName;

                }

                return r;
            }
            catch (Exception e)
            {
                throw Utils.CreateBeWoFaultException(e);
            }
        }

        public InvoiceNumberDC LoadInvoiceNumber()
        {
            try
            {
                var inr = DAOFactory.GenericDAO.LoadByID<InvoiceNumber>(1);
                if (inr != null)
                    return MapperFactory.InvoiceNumberDC_InvoiceNumber.MapToNewDC(DAOFactory.GenericDAO.LoadByID<InvoiceNumber>(1));
                return null;
            }
            catch (Exception e)
            {
                throw Utils.CreateBeWoFaultException(e);
            }
        }

        public ServiceInvoiceDC LoadServiceInvoiceByOid(long serviceInvoiceOid)
        {
            try
            {
                var r = MapperFactory.ServiceInvoiceDC_ServiceInvoice.MapToNewDC(DAOFactory.GenericDAO.LoadByID<ServiceInvoice>(serviceInvoiceOid));

                return r;
            }
            catch (Exception e)
            {
                throw Utils.CreateBeWoFaultException(e);
            }
        }

        public List<long> UpdateInvoiceBases(List<InvoiceBaseDC> invoices)
        {
            try
            {
                var originals = DAOFactory.GenericDAO.LoadByIDs<InvoiceBase>(invoices.Select(i => i.InvoiceBaseOid.Value));
                MapperFactory.InvoiceBaseDC_InvoiceBase.MergeWithEntitys(invoices, originals);
                DAOFactory.GenericDAO.Update(originals);
                return originals.Select(e => e.Version.Value).ToList();
            }
            catch (Exception e)
            {
                throw Utils.CreateBeWoFaultException(e);
            }
        }

        public List<long> SetInvoicePaid(List<InvoiceBaseDC> invoices, bool isPaid)
        {
            try
            {
                var originals = DAOFactory.GenericDAO.LoadByIDs<InvoiceBase>(invoices.Select(i => i.InvoiceBaseOid.Value));
                foreach (var invoiceBase in originals)
                {
                    invoiceBase.IsPaid = isPaid;
                }
                DAOFactory.GenericDAO.Update(originals);
                return originals.Select(e => e.Version.Value).ToList();
            }
            catch (Exception e)
            {
                throw Utils.CreateBeWoFaultException(e);
            }
        }

        public InvoiceNumberDC UpdateInvoiceNumber(InvoiceNumberDC dc)
        {
            try
            {

                var old = DAOFactory.GenericDAO.LoadByID<InvoiceNumber>(1);
                MapperFactory.InvoiceNumberDC_InvoiceNumber.MergeWithEntity(dc, old);
                DAOFactory.GenericDAO.Update(old);

                return MapperFactory.InvoiceNumberDC_InvoiceNumber.MapToNewDC(old);
            }
            catch (Exception e)
            {
                throw Utils.CreateBeWoFaultException(e);
            }
        }

        public List<long> UpdateServiceInvoices(List<ServiceInvoiceDC> invoices)
        {
            try
            {
                var originals = DAOFactory.GenericDAO.LoadByIDs<ServiceInvoice>(invoices.Select(i => i.ServiceInvoiceOid.Value));
                MapperFactory.ServiceInvoiceDC_ServiceInvoice.MergeWithEntitys(invoices, originals);
                DAOFactory.GenericDAO.Update(originals);
                return originals.Select(e => e.Version.Value).ToList();
            }
            catch (Exception e)
            {
                throw Utils.CreateBeWoFaultException(e);
            }
        }

        public void UpdateSettlementInvoice(List<Settlement2DC> invoices)
        {
            try
            {
                var originals = DAOFactory.GenericDAO.LoadByIDs<SettlementInvoice>(invoices.Select(i => i.SettlementIvoiceOid.Value));
                MapperFactory.SettlementDC_SettlementInvoice.MergeWithEntitys(invoices, originals);
                
				//originals.ForEach(
                //                  i =>
                //                  {
                                      //i.InvoiceBase.Type = InvoiceType.Settlement;

                                      // We´ll add an invoiceitem, so we can treat a settlement like any other invoice
                                      //i.InvoiceBase.InvoiceItems.Clear();
                                      //i.InvoiceBase.InvoiceItems.Add(
                                      //                               new InvoiceItem
                                      //                               {
                                      //                                   AmountTotal = i.Claim,
                                      //                                   ItemPeriodStart = i.InvoiceBase.AccountingPeriodStart,
                                      //                                   ItemPeriodEnd = i.InvoiceBase.AccountingPeriodEnd,
                                      //                                   ItemDescription = "Spitzabrechung"
                                      //                               });
                                  //});
                DAOFactory.GenericDAO.Update(originals);
            }
            catch (Exception e)
            {
                throw Utils.CreateBeWoFaultException(e);
            }
        }
    }
}