public async Task <Result> SubtractMoney(int counterpartyAccountId, PaymentCancellationData data, UserInfo user)
        {
            return(await GetCounterpartyAccount(counterpartyAccountId)
                   .Ensure(a => AreCurrenciesMatch(a, data), "Account and payment currency mismatch")
                   .Ensure(IsAmountPositive, "Payment amount must be a positive number")
                   .BindWithLock(_locker, a => Result.Success(a)
                                 .BindWithTransaction(_context, account => Result.Success(account)
                                                      .Map(SubtractMoney)
                                                      .Map(WriteAuditLog))));

            bool IsAmountPositive(CounterpartyAccount account) => data.Amount.IsGreaterThan(decimal.Zero);


            async Task <CounterpartyAccount> SubtractMoney(CounterpartyAccount account)
            {
                account.Balance -= data.Amount;
                _context.Update(account);
                await _context.SaveChangesAsync();

                return(account);
            }

            async Task <CounterpartyAccount> WriteAuditLog(CounterpartyAccount account)
            {
                var eventData = new CounterpartyAccountBalanceLogEventData(null, account.Balance);
                await _auditService.Write(AccountEventType.CounterpartySubtract,
                                          account.Id,
                                          data.Amount,
                                          user,
                                          eventData,
                                          null);

                return(account);
            }
        }
        public async Task <Result> AddMoney(int counterpartyAccountId, PaymentData paymentData, UserInfo user)
        {
            return(await GetCounterpartyAccount(counterpartyAccountId)
                   .Ensure(IsReasonProvided, "Payment reason cannot be empty")
                   .Ensure(a => AreCurrenciesMatch(a, paymentData), "Account and payment currency mismatch")
                   .BindWithLock(_locker, a => Result.Success(a)
                                 .Ensure(IsAmountPositive, "Payment amount must be a positive number")
                                 .BindWithTransaction(_context, account => Result.Success(account)
                                                      .Map(AddMoneyToCounterparty)
                                                      .Map(WriteAuditLog))));

            bool IsReasonProvided(CounterpartyAccount account) => !string.IsNullOrEmpty(paymentData.Reason);

            bool IsAmountPositive(CounterpartyAccount account) => paymentData.Amount.IsGreaterThan(decimal.Zero);


            async Task <CounterpartyAccount> AddMoneyToCounterparty(CounterpartyAccount account)
            {
                account.Balance += paymentData.Amount;
                _context.Update(account);
                await _context.SaveChangesAsync();

                return(account);
            }

            async Task <CounterpartyAccount> WriteAuditLog(CounterpartyAccount account)
            {
                var eventData = new CounterpartyAccountBalanceLogEventData(paymentData.Reason, account.Balance);
                await _auditService.Write(AccountEventType.CounterpartyAdd,
                                          account.Id,
                                          paymentData.Amount,
                                          user,
                                          eventData,
                                          null);

                return(account);
            }
        }
        public async Task <Result> TransferToDefaultAgency(int counterpartyAccountId, MoneyAmount amount, UserInfo user)
        {
            return(await Result.Success(counterpartyAccountId)
                   .Ensure(IsAmountPositive, "Payment amount must be a positive number")
                   .Bind(GetCounterpartyAccount)
                   .Ensure(a => AreCurrenciesMatch(a, amount), "Account and payment currency mismatch")
                   .Bind(GetDefaultAgencyAccount)
                   .BindWithLock(_locker, a => Result.Success(a)
                                 .Ensure(IsBalanceSufficient, "Could not charge money, insufficient balance")
                                 .BindWithTransaction(_context, accounts => Result.Success(accounts)
                                                      .Map(TransferMoney)
                                                      .Map(WriteAuditLog))));

            bool IsAmountPositive(int _) => amount.Amount.IsGreaterThan(decimal.Zero);


            bool IsBalanceSufficient((CounterpartyAccount counterpartyAccount, AgencyAccount agencyAccount) accounts)
            => accounts.counterpartyAccount.Balance.IsGreaterOrEqualThan(amount.Amount);


            async Task <Result <(CounterpartyAccount, AgencyAccount)> > GetDefaultAgencyAccount(CounterpartyAccount counterpartyAccount)
            {
                var defaultAgency = await _context.Agencies
                                    .Where(a => a.CounterpartyId == counterpartyAccount.CounterpartyId && a.ParentId == null)
                                    .SingleOrDefaultAsync();

                if (defaultAgency == null)
                {
                    return(Result.Failure <(CounterpartyAccount, AgencyAccount)>("Could not find the default agency of the account owner"));
                }

                var agencyAccount = await _context.AgencyAccounts
                                    .Where(a => a.AgencyId == defaultAgency.Id && a.Currency == amount.Currency)
                                    .SingleOrDefaultAsync();

                if (agencyAccount == null)
                {
                    return(Result.Failure <(CounterpartyAccount, AgencyAccount)>("Could not find the default agency account"));
                }

                return(Result.Success <(CounterpartyAccount, AgencyAccount)>((counterpartyAccount, agencyAccount)));
            }

            async Task <(CounterpartyAccount, AgencyAccount)> TransferMoney((CounterpartyAccount, AgencyAccount) accounts)
            {
                var(counterpartyAccount, agencyAccount) = accounts;

                counterpartyAccount.Balance -= amount.Amount;
                _context.Update(counterpartyAccount);

                agencyAccount.Balance += amount.Amount;
                _context.Update(agencyAccount);

                await _context.SaveChangesAsync();

                return(counterpartyAccount, agencyAccount);
            }

            async Task <(CounterpartyAccount, AgencyAccount)> WriteAuditLog((CounterpartyAccount, AgencyAccount) accounts)
            {
                var(counterpartyAccount, agencyAccount) = accounts;

                var counterpartyEventData = new CounterpartyAccountBalanceLogEventData(null, counterpartyAccount.Balance);
                await _auditService.Write(AccountEventType.CounterpartyTransferToAgency,
                                          counterpartyAccount.Id,
                                          amount.Amount,
                                          user,
                                          counterpartyEventData,
                                          null);

                var agencyEventData = new AccountBalanceLogEventData(null, agencyAccount.Balance);
                await _auditService.Write(AccountEventType.CounterpartyTransferToAgency,
                                          agencyAccount.Id,
                                          amount.Amount,
                                          user,
                                          agencyEventData,
                                          null);

                return(counterpartyAccount, agencyAccount);
            }
        }