/// <summary>
        /// Creates deductibles (claim transaction details) where required for the new reserve/payment/recovery reserve/recovery receipt transaction
        /// </summary>
        /// <param name="financialContext">wrapper for context variables</param>
        /// <returns>Deductible Result</returns>
        private static DeductibleResult GenerateDeductibles(FinancialTransactionContext financialContext)
        {
            IDictionary<string, ProductClaimDetail> claimDetailProductMap = MapClaimDetailRefToProductClaimDetail(financialContext.ClaimHeader.ClaimDetails, financialContext.ProductClaimDetails);
            var currentProductClaimDetail = claimDetailProductMap[financialContext.ClaimDetail.ClaimDetailReference];

            bool isCalculatingExcess = IsCalculatingExcess(currentProductClaimDetail, financialContext.ClaimDetail);
            bool isCalculatingDeductibles = IsCalculatingDeductibles(currentProductClaimDetail, financialContext.ProductClaimDefinition, financialContext.ClaimHeader);



            if (isCalculatingExcess)
            {
                var deductibles = ResolveDeductibles(financialContext, currentProductClaimDetail);
                IEnumerable<ClaimFinancialAmount> reserves, payments, recoveryReserves;
                LoadFinancialAmounts(financialContext.TransactionSource, financialContext.ProductClaimDefinition, financialContext.ClaimDetail, financialContext.ClaimTransactionHeader, out reserves, out payments, out recoveryReserves);
                return CalculateDeductibles(financialContext.ProductClaimDefinition, financialContext, deductibles, reserves, payments, recoveryReserves);
            }
            else if (isCalculatingDeductibles)
            {
                var deductibles = ResolveDeductibles(financialContext);
                IEnumerable<ClaimFinancialAmount> reserves, payments, recoveryReserves;
                LoadFinancialAmounts(financialContext.TransactionSource, financialContext.ProductClaimDefinition, claimDetailProductMap, financialContext.ClaimHeader, financialContext.ClaimTransactionHeader, out reserves, out payments, out recoveryReserves);
                return CalculateDeductibles(financialContext.ProductClaimDefinition, financialContext, deductibles, reserves, payments, recoveryReserves);
            }

            return new DeductibleResult { Success = true };
        }
        /// <summary>
        /// Try and find the latest deductible reserve
        /// </summary>
        /// <param name="reserves">all reserves</param>
        /// <param name="context">context variables</param>
        /// <param name="movementType">movement type of the latest reserve</param>
        /// <returns>latest reserve or null</returns>
        private static ClaimFinancialAmount LatestReserveOrNull(IEnumerable<ClaimFinancialAmount> reserves, FinancialTransactionContext context, string movementType)
        {
            var claimDetailRef = context.ClaimDetail.ClaimDetailReference;
            var origCurrency = context.ClaimTransactionGroup.OriginalCurrencyCode;
            var accCurrency = context.ClaimTransactionGroup.AccountingCurrencyCode;

            return (from reserve in reserves
                    where reserve.ClaimDetailReference == claimDetailRef
                    && reserve.MovementType == movementType
                    && reserve.OriginalCurrencyCode == origCurrency
                    && reserve.AccountingCurrencyCode == accCurrency
                    select reserve)
                    .SingleOrDefault();
        }
        private static IEnumerable<DeductibleDefinition> ResolveDeductibles(FinancialTransactionContext context, ProductClaimDetail productClaimDetail)
        {
            var claimDetail = context.ClaimDetail;
            var productCode = context.Product.Product.Code;
            var definitions = new List<DeductibleDefinition>();
            var user = context.CurrentUser;
            var claimTransactionContext = claimDetail.Context;
            CreateDeductibleDefinitionIfEnabled(claimTransactionContext, true, definitions, claimDetail.PolicyDeductible01, claimDetail.IsDeductible01PaidByInsurer, productClaimDetail.AutomaticDeductible01MovementTypeCode, productClaimDetail.InsurerFundedDeductible01MovementTypeCode, productCode, user);

            return definitions;
        }
        private static IEnumerable<DeductibleDefinition> ResolveDeductibles(FinancialTransactionContext context)
        {
            var productClaimDefinition = context.ProductClaimDefinition;
            var claim = context.ClaimHeader;
            var productCode = context.Product.Product.Code;
            var definitions = new List<DeductibleDefinition>();
            var user = context.CurrentUser;
            var claimTransactionContext = claim.Context;
            CreateDeductibleDefinitionIfEnabled(claimTransactionContext, false, definitions, claim.PolicyDeductible01, claim.IsDeductible01PaidByInsurer, productClaimDefinition.AutomaticDeductible01MovementTypeCode, productClaimDefinition.InsurerFundedDeductible01MovementTypeCode, productCode, user);
            CreateDeductibleDefinitionIfEnabled(claimTransactionContext, false, definitions, claim.PolicyDeductible02, claim.IsDeductible02PaidByInsurer, productClaimDefinition.AutomaticDeductible02MovementTypeCode, productClaimDefinition.InsurerFundedDeductible02MovementTypeCode, productCode, user);
            CreateDeductibleDefinitionIfEnabled(claimTransactionContext, false, definitions, claim.PolicyDeductible03, claim.IsDeductible03PaidByInsurer, productClaimDefinition.AutomaticDeductible03MovementTypeCode, productClaimDefinition.InsurerFundedDeductible03MovementTypeCode, productCode, user);
            CreateDeductibleDefinitionIfEnabled(claimTransactionContext, false, definitions, claim.PolicyDeductible04, claim.IsDeductible04PaidByInsurer, productClaimDefinition.AutomaticDeductible04MovementTypeCode, productClaimDefinition.InsurerFundedDeductible04MovementTypeCode, productCode, user);
            CreateDeductibleDefinitionIfEnabled(claimTransactionContext, false, definitions, claim.PolicyDeductible05, claim.IsDeductible05PaidByInsurer, productClaimDefinition.AutomaticDeductible05MovementTypeCode, productClaimDefinition.InsurerFundedDeductible05MovementTypeCode, productCode, user);



            return definitions;
        }
        private static decimal AddReserve(decimal amount, FinancialTransactionContext financialContext, ClaimDetail claimDetail, ClaimTransactionGroup claimTransactionGroup, DeductibleDefinition deductibleDefinition, DeductibleReserveCapacity deductibleReserveContext, ClaimFinancialAmount latestReserve = null)
        {
            StaticValues.AmountType amountType;
            string nonFundedMovementType;
            if (IsRecovery(financialContext.TransactionSource))
            {
                amountType = StaticValues.AmountType.RecoveryReserve;
                nonFundedMovementType = deductibleDefinition.RecoveryNonFundedMovementType;
            }
            else
            {
                amountType = StaticValues.AmountType.Reserve;
                nonFundedMovementType = deductibleDefinition.NonFundedMovementType;
            }


            if (latestReserve == null)
            {
                if (amount > 0)
                {
                    AddClaimTransactionDetails(financialContext.ClaimTransactionHeader.TransactionDate.GetValueOrDefault(DateTime.MinValue), claimTransactionGroup, amountType, deductibleDefinition, amount, latestReserve);
                    deductibleReserveContext.AdjustDeductibleAttached(claimDetail.ClaimDetailReference, nonFundedMovementType, amount);
                }
                else
                {
                    amount = 0;
                }
            }
            else if (amount != Math.Abs(latestReserve.TransactionAmountClaimCurrency.GetValueOrDefault(0)))
            {
                AddClaimTransactionDetails(financialContext.ClaimTransactionHeader.TransactionDate.GetValueOrDefault(DateTime.MinValue), claimTransactionGroup, amountType, deductibleDefinition, amount, latestReserve);
                deductibleReserveContext.AdjustDeductibleAttached(claimDetail.ClaimDetailReference, nonFundedMovementType, amount + latestReserve.TransactionAmountClaimCurrency.GetValueOrDefault());
            }

            return amount;
        }
        private static DeductibleResult ApplyDeductibleReserveAdjustments(string movementType, DeductibleDefinition deductibleDefinition, DeductibleReserveCapacity deductibleReserveContext, FinancialTransactionContext financialContext, IEnumerable<ClaimFinancialAmount> reserves, ClaimFinancialAmount latestReserve, decimal reserveAdjustment)
        {
            string claimDetailRef = financialContext.ClaimDetail.ClaimDetailReference;
            decimal incurredTotal = deductibleReserveContext.ResolveCapacity(claimDetailRef);
            decimal revisedAmount = Math.Max(Math.Min(incurredTotal, reserveAdjustment), 0);

            // if it's a recovery receipt transaction and there is reserve adjustment, stop.
            if (IsRecovery(financialContext.TransactionSource)
                && movementType == deductibleDefinition.NonFundedMovementType
                && ((latestReserve == null && reserveAdjustment != 0) || (latestReserve != null && reserveAdjustment != latestReserve.TransactionAmountClaimCurrency)))
            {
                return new DeductibleResult { Success = false, Message = MessageConstants.RecoveryReservesRequireManualReview };
            }

            reserveAdjustment -= AddReserve(revisedAmount, financialContext, financialContext.ClaimDetail, financialContext.ClaimTransactionGroup, deductibleDefinition, deductibleReserveContext, latestReserve);

            // if the reserve adjustment couldn't be absorbed by the current claim detail apply accross the claim
            if (reserveAdjustment != 0 && !deductibleDefinition.IsClaimDetailDeductible)
            {
                // don't process if currencies aren't all the same
                if (!AreReservesInSameCurrency(reserves, movementType))
                {
                    return new DeductibleResult { Success = false, Message = MessageConstants.ReservesRequireManualReview };
                }

                var reservesForMovementType = reserves.Where(a => a.MovementType == movementType);
                var claimDetails = from claimDetail in financialContext.ClaimHeader.ClaimDetails
                                   join reserve in reservesForMovementType on claimDetail.ClaimDetailReference equals reserve.ClaimDetailReference into cr
                                   from groupData in cr.DefaultIfEmpty()
                                   where claimDetail.ClaimDetailReference != claimDetailRef
                                   orderby groupData != null ? Math.Abs(groupData.TransactionAmountClaimCurrency.GetValueOrDefault()) : 0 descending
                                   select claimDetail;


                foreach (var claimDetail in claimDetails)
                {
                    incurredTotal = deductibleReserveContext.ResolveCapacity(claimDetail.ClaimDetailReference);
                    ClaimFinancialAmount remainingAmount = reserves.SingleOrDefault(a => a.ClaimDetailReference == claimDetail.ClaimDetailReference && a.MovementType == movementType);
                    decimal remainingAbsAmount = remainingAmount != null ? Math.Abs(remainingAmount.TransactionAmountClaimCurrency.GetValueOrDefault()) : 0;
                    revisedAmount = Math.Max(Math.Min(incurredTotal, remainingAbsAmount + reserveAdjustment), 0);

                    var newClaimTransactionGroup = ResolveClaimTransactionGroup(financialContext.ClaimTransactionHeader, financialContext.ClaimTransactionGroup, claimDetail);
                    revisedAmount = AddReserve(revisedAmount, financialContext, claimDetail, newClaimTransactionGroup, deductibleDefinition, deductibleReserveContext, remainingAmount);
                    reserveAdjustment -= revisedAmount - remainingAbsAmount;

                    if (reserveAdjustment == 0)
                    {
                        break;
                    }
                }
            }

            return new DeductibleResult { Success = true };
        }
        /// <summary>
        /// For each deductible associated with this transaction we need to calculate how the latest transaction has affected the the current deductibles and generate any new deductibles as required
        /// </summary>
        /// <param name="productClaimDefinition">calim product</param>
        /// <param name="financialContext">context variables</param>
        /// <param name="deductibleDefinitions">deductibles associated with this transaction</param>
        /// <param name="reserves">historical reserves</param>
        /// <param name="payments">historical payments</param>
        /// <param name="recoveryReserves">recovery reserves</param>
        /// <returns>Deductible Result</returns>
        private static DeductibleResult CalculateDeductibles(ProductClaimDefinition productClaimDefinition, FinancialTransactionContext financialContext, IEnumerable<DeductibleDefinition> deductibleDefinitions, IEnumerable<ClaimFinancialAmount> reserves, IEnumerable<ClaimFinancialAmount> payments, IEnumerable<ClaimFinancialAmount> recoveryReserves)
        {
            var deductibleResult = new DeductibleResult { Success = true };
            var deductibleMovementTypes = deductibleDefinitions.SelectMany(a => a.GetMovementTypes());
            var recoveryDeductibleMovementTypes = deductibleDefinitions.SelectMany(a => a.GetRecoveryMovementTypes());


            var claimDetailRefs = financialContext.ClaimHeader.ClaimDetails.Select(a => a.ClaimDetailReference);
            var deductibleReserveContext = new DeductibleReserveCapacity(claimDetailRefs, reserves, deductibleMovementTypes);
            var deductibleRecoveryReserveContext = new DeductibleReserveCapacity(claimDetailRefs, recoveryReserves, recoveryDeductibleMovementTypes);

            bool isRecovery = IsRecovery(financialContext.TransactionSource);
            decimal sumOfLowerDeductibles = 0;
            decimal totalPaid = payments.Where(a => !deductibleMovementTypes.Contains(a.MovementType)).Sum(a => a.TransactionAmountClaimCurrency.GetValueOrDefault());
            decimal totalIncurred = CalculateTotalIncurred(productClaimDefinition, totalPaid, reserves.Where(a => !deductibleMovementTypes.Contains(a.MovementType)));
            decimal totalRecoveryIncurred = totalIncurred + recoveryReserves.Where(a => !recoveryDeductibleMovementTypes.Contains(a.MovementType)).Sum(a => a.TransactionAmountClaimCurrency.GetValueOrDefault(0));


            foreach (var deductible in deductibleDefinitions)
            {
                decimal deductiblePaid = CalculateDeductiblePayment(deductible, totalPaid, sumOfLowerDeductibles, payments);
                if (deductiblePaid != 0)
                {
                    var amountType = isRecovery ? StaticValues.AmountType.RecoveryReceipt : StaticValues.AmountType.Payment;
                    AddClaimTransactionDetails(financialContext.ClaimTransactionHeader.TransactionDate.GetValueOrDefault(DateTime.MinValue), financialContext.ClaimTransactionGroup, amountType, deductible, deductiblePaid);
                }

                if (isRecovery && deductible.RecoveryNonFundedMovementType != null)
                {
                    ClaimFinancialAmount latestReserve = LatestReserveOrNull(recoveryReserves, financialContext, deductible.RecoveryNonFundedMovementType);
                    decimal deductibleRecoveryReserve = CalculateDeductibleRecoveryReserves(deductible, latestReserve, totalIncurred, totalRecoveryIncurred, sumOfLowerDeductibles, recoveryReserves);
                    // although a deductible recovery reserve is a negative amount we want to keep it as a positive for now so we can apply deductible reserve adjustments
                    deductibleRecoveryReserve = -deductibleRecoveryReserve;
                    var reserveAdjResult = ApplyDeductibleReserveAdjustments(deductible.RecoveryNonFundedMovementType, deductible, deductibleRecoveryReserveContext, financialContext, recoveryReserves, latestReserve, deductibleRecoveryReserve);
                    deductibleRecoveryReserveContext.AppendToLowerDeductibles(deductible.RecoveryNonFundedMovementType);

                    if (!reserveAdjResult.Success)
                    {
                        deductibleResult = reserveAdjResult;
                    }
                }

                if (financialContext.TransactionSource != StaticValues.ClaimTransactionSource.RecoveryReserve)
                {
                    ClaimFinancialAmount latestReserve = LatestReserveOrNull(reserves, financialContext, deductible.NonFundedMovementType);
                    decimal deductibleReserve = CalculateDeductibleReserves(deductible, latestReserve, totalPaid, totalIncurred, sumOfLowerDeductibles, reserves);
                    var reserveAdjResult = ApplyDeductibleReserveAdjustments(deductible.NonFundedMovementType, deductible, deductibleReserveContext, financialContext, reserves, latestReserve, deductibleReserve);
                    deductibleReserveContext.AppendToLowerDeductibles(deductible.NonFundedMovementType);

                    if (!reserveAdjResult.Success)
                    {
                        deductibleResult = reserveAdjResult;
                    }
                }



                sumOfLowerDeductibles += deductible.Amount;
            }

            return deductibleResult;
        }