public static async Task <HttpResponseMessage> GetBalanceRun(
            [HttpTrigger(AuthorizationLevel.Function, "GET", Route = @"GetBalance/{accountnumber}/{asOfDate?}")] HttpRequestMessage req,
            string accountnumber,
            string asOfDate,
            [Projection("Bank", "Account", "{accountnumber}", nameof(Balance))] Projection prjBankAccountBalance)
        {
            // Set the start time for how long it took to process the message
            DateTime startTime = DateTime.UtcNow;

            string result = $"No balance found for account {accountnumber}";

            if (null != prjBankAccountBalance)
            {
                if (await prjBankAccountBalance.Exists())
                {
                    // Get request body
                    Nullable <DateTime> asOfDateValue = null;
                    if (!string.IsNullOrEmpty(asOfDate))
                    {
                        DateTime dtTest;
                        if (DateTime.TryParse(asOfDate, out dtTest))
                        {
                            asOfDateValue = dtTest;
                        }
                    }

                    // Run the "Balance" projection
                    Balance projectedBalance = await prjBankAccountBalance.Process <Balance>(asOfDateValue);

                    if (null != projectedBalance)
                    {
                        result = $"Balance for account {accountnumber} is ${projectedBalance.CurrentBalance} ";
                        return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.OK,
                                                                               ProjectionFunctionResponse.CreateResponse(startTime,
                                                                                                                         false,
                                                                                                                         result,
                                                                                                                         projectedBalance.CurrentSequenceNumber),
                                                                               FunctionResponse.MEDIA_TYPE));
                    }
                }
                else
                {
                    result = $"Account {accountnumber} is not yet created - cannot retrieve a balance for it";
                    return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.NotFound,
                                                                           ProjectionFunctionResponse.CreateResponse(startTime,
                                                                                                                     true,
                                                                                                                     result,
                                                                                                                     0),
                                                                           FunctionResponse.MEDIA_TYPE));
                }
            }

            // If we got here no balance was found
            return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.NotFound,
                                                                   ProjectionFunctionResponse.CreateResponse(startTime,
                                                                                                             true,
                                                                                                             result,
                                                                                                             0),
                                                                   FunctionResponse.MEDIA_TYPE));
        }
        public static async Task <HttpResponseMessage> ExtendOverdraftForInterestRun(
            [HttpTrigger(AuthorizationLevel.Function, "POST", Route = @"ExtendOverdraftForInterest/{accountnumber}")] HttpRequestMessage req,
            string accountnumber,
            [EventStream("Bank", "Account", "{accountnumber}")]  EventStream bankAccountEvents,
            [Projection("Bank", "Account", "{accountnumber}", nameof(InterestDue))] Projection prjInterestDue,
            [Projection("Bank", "Account", "{accountnumber}", nameof(Balance))] Projection prjBankAccountBalance,
            [Projection("Bank", "Account", "{accountnumber}", nameof(OverdraftLimit))] Projection prjBankAccountOverdraft
            )
        {
            // Bolierplate: Set the start time for how long it took to process the message
            DateTime startTime = DateTime.UtcNow;

            // Check the balance is negative
            // get the interest owed / due as now
            InterestDue interestDue = await prjInterestDue.Process <InterestDue>();

            if (null != interestDue)
            {
                // if the interest due is negative we need to make sure the account has sufficient balance
                if (interestDue.Due < 0.00M)
                {
                    Balance balance = await prjBankAccountBalance.Process <Balance>();

                    if (null != balance)
                    {
                        decimal availableBalance = balance.CurrentBalance;

                        // is there an overdraft?
                        OverdraftLimit overdraft = await prjBankAccountOverdraft.Process <OverdraftLimit>();

                        if (null != overdraft)
                        {
                            availableBalance += overdraft.CurrentOverdraftLimit;
                        }

                        if (availableBalance < interestDue.Due)
                        {
                            decimal           newOverdraft = overdraft.CurrentOverdraftLimit;
                            decimal           extension    = 10.00M + Math.Abs(interestDue.Due % 10.00M);
                            OverdraftLimitSet evNewLimit   = new OverdraftLimitSet()
                            {
                                OverdraftLimit = newOverdraft + extension,
                                Commentary     = $"Overdraft extended to pay interest of {interestDue.Due} ",
                                Unauthorised   = true
                            };

                            await bankAccountEvents.AppendEvent(evNewLimit);

                            return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.OK,
                                                                                   ProjectionFunctionResponse.CreateResponse(startTime,
                                                                                                                             false,
                                                                                                                             $"Extended the overdraft by {extension} for payment of interest {interestDue.Due} for account {accountnumber}",
                                                                                                                             interestDue.CurrentSequenceNumber),
                                                                                   FunctionResponse.MEDIA_TYPE));
                        }
                    }
                }

                // Not needed
                return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.OK,
                                                                       ProjectionFunctionResponse.CreateResponse(startTime,
                                                                                                                 false,
                                                                                                                 $"Extending the overdraft for interest not required for account {accountnumber}",
                                                                                                                 interestDue.CurrentSequenceNumber),
                                                                       FunctionResponse.MEDIA_TYPE));
            }
            else
            {
                return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.Forbidden,
                                                                       ProjectionFunctionResponse.CreateResponse(startTime,
                                                                                                                 true,
                                                                                                                 $"Unable to extend the overdraft for account {accountnumber} for interest payment",
                                                                                                                 0),
                                                                       FunctionResponse.MEDIA_TYPE
                                                                       ));
            }
        }
        public static async Task <HttpResponseMessage> PayInterestRun(
            [HttpTrigger(AuthorizationLevel.Function, "POST", Route = @"PayInterest/{accountnumber}")] HttpRequestMessage req,
            string accountnumber,
            [EventStream("Bank", "Account", "{accountnumber}")]  EventStream bankAccountEvents,
            [Projection("Bank", "Account", "{accountnumber}", nameof(InterestDue))] Projection prjInterestDue,
            [Projection("Bank", "Account", "{accountnumber}", nameof(Balance))] Projection prjBankAccountBalance,
            [Projection("Bank", "Account", "{accountnumber}", nameof(OverdraftLimit))] Projection prjBankAccountOverdraft)
        {
            // Set the start time for how long it took to process the message
            DateTime startTime = DateTime.UtcNow;

            if (!await bankAccountEvents.Exists())
            {
                // You cannot pay interest if the account does not exist
                return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.Forbidden,
                                                                       ProjectionFunctionResponse.CreateResponse(startTime,
                                                                                                                 true,
                                                                                                                 $"Account {accountnumber} does not exist",
                                                                                                                 0),
                                                                       FunctionResponse.MEDIA_TYPE));
            }

            // get the interest owed / due as now
            InterestDue interestDue = await prjInterestDue.Process <InterestDue>();

            if (null != interestDue)
            {
                // if the interest due is negative we need to make sure the account has sufficient balance
                if (interestDue.Due < 0.00M)
                {
                    Balance balance = await prjBankAccountBalance.Process <Balance>();

                    if (null != balance)
                    {
                        decimal availableBalance = balance.CurrentBalance;

                        // is there an overdraft?
                        OverdraftLimit overdraft = await prjBankAccountOverdraft.Process <OverdraftLimit>();

                        if (null != overdraft)
                        {
                            availableBalance += overdraft.CurrentOverdraftLimit;
                        }

                        if (availableBalance < interestDue.Due)
                        {
                            // can't pay the interest
                            return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.Forbidden,
                                                                                   ProjectionFunctionResponse.CreateResponse(startTime,
                                                                                                                             true,
                                                                                                                             $"Unable to pay interest of {interestDue.Due} as available balance is only {availableBalance}",
                                                                                                                             interestDue.CurrentSequenceNumber),
                                                                                   FunctionResponse.MEDIA_TYPE));
                        }
                    }
                }

                // pay the interest
                decimal amountToPay = decimal.Round(interestDue.Due, 2, MidpointRounding.AwayFromZero);
                if (amountToPay != 0.00M)
                {
                    InterestPaid evInterestPaid = new InterestPaid()
                    {
                        AmountPaid = decimal.Round(interestDue.Due, 2, MidpointRounding.AwayFromZero),
                        Commentary = $"Interest due {interestDue.Due} as at {interestDue.CurrentSequenceNumber}"
                    };
                    await bankAccountEvents.AppendEvent(evInterestPaid);

                    return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.OK,
                                                                           ProjectionFunctionResponse.CreateResponse(startTime,
                                                                                                                     false,
                                                                                                                     evInterestPaid.Commentary,
                                                                                                                     interestDue.CurrentSequenceNumber),
                                                                           FunctionResponse.MEDIA_TYPE));
                }
                else
                {
                    return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.OK,
                                                                           ProjectionFunctionResponse.CreateResponse(startTime,
                                                                                                                     false,
                                                                                                                     $"No interest due so none was paid out",
                                                                                                                     interestDue.CurrentSequenceNumber),
                                                                           FunctionResponse.MEDIA_TYPE));
                }
            }
            else
            {
                return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.Forbidden,
                                                                       ProjectionFunctionResponse.CreateResponse(startTime,
                                                                                                                 true,
                                                                                                                 $"Unable to get interest due for account {accountnumber} for interest payment",
                                                                                                                 0),
                                                                       FunctionResponse.MEDIA_TYPE
                                                                       ));
            }
        }
        public static async Task <HttpResponseMessage> AccrueInterestRun(
            [HttpTrigger(AuthorizationLevel.Function, "POST", Route = @"AccrueInterest/{accountnumber}")] HttpRequestMessage req,
            string accountnumber,
            [EventStream("Bank", "Account", "{accountnumber}")]  EventStream bankAccountEvents,
            [Projection("Bank", "Account", "{accountnumber}", nameof(Balance))] Projection prjBankAccountBalance,
            [Classification("Bank", "Account", "{accountnumber}", nameof(InterestAccruedToday))] Classification clsAccruedToday)
        {
            // Set the start time for how long it took to process the message
            DateTime startTime = DateTime.UtcNow;

            if (!await bankAccountEvents.Exists())
            {
                // You cannot accrue interest if the account does not exist
                return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.Forbidden,
                                                                       ProjectionFunctionResponse.CreateResponse(startTime,
                                                                                                                 true,
                                                                                                                 $"Account {accountnumber} does not exist",
                                                                                                                 0)));
            }

            ClassificationResponse isAccrued = await clsAccruedToday.Classify <InterestAccruedToday>();

            if (isAccrued.Result == ClassificationResponse.ClassificationResults.Include)
            {
                // The accrual for today has been performed for this account
                return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.Forbidden,
                                                                       ProjectionFunctionResponse.CreateResponse(startTime,
                                                                                                                 true,
                                                                                                                 $"Interest accrual already done on account {accountnumber} today",
                                                                                                                 isAccrued.AsOfSequence),
                                                                       FunctionResponse.MEDIA_TYPE
                                                                       ));
            }


            // get the request body...
            InterestAccrualData data = await req.Content.ReadAsAsync <InterestAccrualData>();

            // Get the current account balance, as at midnight
            Balance projectedBalance = await prjBankAccountBalance.Process <Balance>(DateTime.Today);

            if (null != projectedBalance)
            {
                Account.Events.InterestAccrued evAccrued = new Account.Events.InterestAccrued()
                {
                    Commentary           = data.Commentary,
                    AccrualEffectiveDate = DateTime.Today  // set the accrual to midnight today
                };

                if (projectedBalance.CurrentBalance >= 0)
                {
                    // Using the credit rate
                    evAccrued.AmountAccrued = data.CreditInterestRate * projectedBalance.CurrentBalance;
                }
                else
                {
                    // Use the debit rate
                    evAccrued.AmountAccrued = data.DebitInterestRate * projectedBalance.CurrentBalance;
                }

                try
                {
                    await bankAccountEvents.AppendEvent(evAccrued, isAccrued.AsOfSequence);
                }
                catch (EventSourcingOnAzureFunctions.Common.EventSourcing.Exceptions.EventStreamWriteException exWrite)
                {
                    return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.Forbidden,
                                                                           ProjectionFunctionResponse.CreateResponse(startTime,
                                                                                                                     true,
                                                                                                                     $"Failed to write interest accrual event {exWrite.Message}",
                                                                                                                     projectedBalance.CurrentSequenceNumber),
                                                                           FunctionResponse.MEDIA_TYPE));
                }

                return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.OK,
                                                                       ProjectionFunctionResponse.CreateResponse(startTime,
                                                                                                                 false,
                                                                                                                 $"Interest accrued for account {accountnumber} is {evAccrued.AmountAccrued}",
                                                                                                                 projectedBalance.CurrentSequenceNumber),
                                                                       FunctionResponse.MEDIA_TYPE));
            }
            else
            {
                return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.Forbidden,
                                                                       ProjectionFunctionResponse.CreateResponse(startTime,
                                                                                                                 true,
                                                                                                                 $"Unable to get current balance for account {accountnumber} for interest accrual",
                                                                                                                 0),
                                                                       FunctionResponse.MEDIA_TYPE
                                                                       ));
            }
        }
        public static async Task <HttpResponseMessage> SetOverdraftLimitRun(
            [HttpTrigger(AuthorizationLevel.Function, "POST", Route = @"SetOverdraftLimit/{accountnumber}")] HttpRequestMessage req,
            string accountnumber,
            [EventStream("Bank", "Account", "{accountnumber}")]  EventStream bankAccountEvents,
            [Projection("Bank", "Account", "{accountnumber}", nameof(Balance))] Projection prjBankAccountBalance)
        {
            // Set the start time for how long it took to process the message
            DateTime startTime = DateTime.UtcNow;

            if (!await bankAccountEvents.Exists())
            {
                // You cannot set an overdraft if the account does not exist
                return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.Forbidden,
                                                                       ProjectionFunctionResponse.CreateResponse(startTime,
                                                                                                                 true,
                                                                                                                 $"Account {accountnumber} does not exist",
                                                                                                                 0)));
            }
            else
            {
                // get the request body...
                OverdraftSetData data = await req.Content.ReadAsAsync <OverdraftSetData>();

                // get the current account balance
                Balance projectedBalance = await prjBankAccountBalance.Process <Balance>();

                if (null != projectedBalance)
                {
                    if (projectedBalance.CurrentBalance >= (0 - data.NewOverdraftLimit))
                    {
                        // attempt to set the new overdraft limit
                        Account.Events.OverdraftLimitSet evOverdraftSet = new Account.Events.OverdraftLimitSet()
                        {
                            OverdraftLimit = data.NewOverdraftLimit,
                            Commentary     = data.Commentary
                        };
                        try
                        {
                            await bankAccountEvents.AppendEvent(evOverdraftSet, projectedBalance.CurrentSequenceNumber);
                        }
                        catch (EventSourcingOnAzureFunctions.Common.EventSourcing.Exceptions.EventStreamWriteException exWrite)
                        {
                            return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.Forbidden,
                                                                                   ProjectionFunctionResponse.CreateResponse(startTime,
                                                                                                                             true,
                                                                                                                             $"Failed to write overdraft limit event {exWrite.Message}",
                                                                                                                             projectedBalance.CurrentSequenceNumber),
                                                                                   FunctionResponse.MEDIA_TYPE));
                        }

                        return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.OK,
                                                                               ProjectionFunctionResponse.CreateResponse(startTime,
                                                                                                                         false,
                                                                                                                         $"{data.NewOverdraftLimit } set as the new overdraft limit for account {accountnumber}",
                                                                                                                         projectedBalance.CurrentSequenceNumber),
                                                                               FunctionResponse.MEDIA_TYPE));
                    }
                    else
                    {
                        return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.Forbidden,
                                                                               ProjectionFunctionResponse.CreateResponse(startTime,
                                                                                                                         true,
                                                                                                                         $"Account {accountnumber} has an outstanding balance beyond the new limit {data.NewOverdraftLimit} (Current balance: {projectedBalance.CurrentBalance} )",
                                                                                                                         projectedBalance.CurrentSequenceNumber),
                                                                               FunctionResponse.MEDIA_TYPE
                                                                               ));
                    }
                }
                else
                {
                    return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.Forbidden,
                                                                           ProjectionFunctionResponse.CreateResponse(startTime,
                                                                                                                     true,
                                                                                                                     $"Unable to get current balance for account {accountnumber}",
                                                                                                                     0),
                                                                           FunctionResponse.MEDIA_TYPE
                                                                           ));
                }
            }
        }
        public static async Task <HttpResponseMessage> WithdrawMoneyRun(
            [HttpTrigger(AuthorizationLevel.Function, "POST", Route = @"WithdrawMoney/{accountnumber}")] HttpRequestMessage req,
            string accountnumber,
            [EventStream("Bank", "Account", "{accountnumber}")]  EventStream bankAccountEvents,
            [Projection("Bank", "Account", "{accountnumber}", nameof(Balance))] Projection prjBankAccountBalance,
            [Projection("Bank", "Account", "{accountnumber}", nameof(OverdraftLimit))] Projection prjBankAccountOverdraft)
        {
            // Set the start time for how long it took to process the message
            DateTime startTime = DateTime.UtcNow;

            if (!await bankAccountEvents.Exists())
            {
                return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.NotFound,
                                                                       ProjectionFunctionResponse.CreateResponse(startTime,
                                                                                                                 true,
                                                                                                                 $"Account {accountnumber} does not exist",
                                                                                                                 0),
                                                                       FunctionResponse.MEDIA_TYPE));
            }
            else
            {
                // get the request body...
                MoneyWithdrawnData data = await req.Content.ReadAsAsync <MoneyWithdrawnData>();

                // get the current account balance
                Balance projectedBalance = await prjBankAccountBalance.Process <Balance>();

                if (null != projectedBalance)
                {
                    OverdraftLimit projectedOverdraft = await prjBankAccountOverdraft.Process <OverdraftLimit>();

                    decimal overdraftSet = 0.00M;
                    if (null != projectedOverdraft)
                    {
                        if (projectedOverdraft.CurrentSequenceNumber != projectedBalance.CurrentSequenceNumber)
                        {
                            // The two projections are out of synch.  In a real business case we would retry them
                            // n times to try and get a match but here we will just throw a consistency error
                            return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.Forbidden,
                                                                                   ProjectionFunctionResponse.CreateResponse(startTime,
                                                                                                                             true,
                                                                                                                             $"Unable to get a matching state for the current balance and overdraft for account {accountnumber}",
                                                                                                                             0),
                                                                                   FunctionResponse.MEDIA_TYPE));
                        }
                        else
                        {
                            overdraftSet = projectedOverdraft.CurrentOverdraftLimit;
                        }
                    }

                    if ((projectedBalance.CurrentBalance + overdraftSet) >= data.AmountWithdrawn)
                    {
                        // attempt the withdrawal
                        DateTime dateWithdrawn = DateTime.UtcNow;
                        Account.Events.MoneyWithdrawn evWithdrawn = new Account.Events.MoneyWithdrawn()
                        {
                            LoggedWithdrawalDate = dateWithdrawn,
                            AmountWithdrawn      = data.AmountWithdrawn,
                            Commentary           = data.Commentary
                        };
                        try
                        {
                            await bankAccountEvents.AppendEvent(evWithdrawn, projectedBalance.CurrentSequenceNumber);
                        }
                        catch (EventSourcingOnAzureFunctions.Common.EventSourcing.Exceptions.EventStreamWriteException exWrite)
                        {
                            return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.Forbidden,
                                                                                   ProjectionFunctionResponse.CreateResponse(startTime,
                                                                                                                             true,
                                                                                                                             $"Failed to write withdrawal event {exWrite.Message}",
                                                                                                                             0),
                                                                                   FunctionResponse.MEDIA_TYPE));
                        }

                        return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.OK,
                                                                               ProjectionFunctionResponse.CreateResponse(startTime,
                                                                                                                         false,
                                                                                                                         $"{data.AmountWithdrawn } withdrawn from account {accountnumber} (New balance: {projectedBalance.CurrentBalance - data.AmountWithdrawn}, overdraft: {overdraftSet} )",
                                                                                                                         projectedBalance.CurrentSequenceNumber),
                                                                               FunctionResponse.MEDIA_TYPE));
                    }
                    else
                    {
                        return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.Forbidden,
                                                                               ProjectionFunctionResponse.CreateResponse(startTime,
                                                                                                                         true,
                                                                                                                         $"Account {accountnumber} does not have sufficent funds for the withdrawal of {data.AmountWithdrawn} (Current balance: {projectedBalance.CurrentBalance}, overdraft: {overdraftSet} )",
                                                                                                                         projectedBalance.CurrentSequenceNumber),
                                                                               FunctionResponse.MEDIA_TYPE));
                    }
                }
                else
                {
                    return(req.CreateResponse <ProjectionFunctionResponse>(System.Net.HttpStatusCode.Forbidden,
                                                                           ProjectionFunctionResponse.CreateResponse(startTime,
                                                                                                                     true,
                                                                                                                     $"Unable to get current balance for account {accountnumber}",
                                                                                                                     projectedBalance.CurrentSequenceNumber),
                                                                           FunctionResponse.MEDIA_TYPE));
                }
            }
        }