public async Task <int> AddTransactionAsync(AddUpdateTransactionDtoBase dto)
        {
            await ValidateTransactionDtoAsync(dto);

            TransactionBase newTransaction;

            switch (dto)
            {
            case AddUpdateCategoryTransactionDto categoryDto:
                newTransaction = categoryDto.ToEntity();
                break;

            case AddUpdateWalletTransactionDto walletDto:
                newTransaction = walletDto.ToEntity();
                break;

            default: throw new ArgumentException("Unknown/unhandled addTransactionDto type.");
            }

            newTransaction.TimeStamp = dto.ManualTimestamp ?? DateTime.Now;

            _dbContext.Transactions.Add(newTransaction);

            await _dbContext.SaveChangesAsync();

            return(newTransaction.Id);
        }
        public async Task <TransactionDomain> UpdateTransactionAsync(int transactionId, AddUpdateTransactionDtoBase dto)
        {
            await ValidateTransactionDtoAsync(dto);

            TransactionBase transaction = _dbContext.Transactions.Find(transactionId);

            if (dto is AddUpdateCategoryTransactionDto && transaction is WalletTransaction ||
                dto is AddUpdateWalletTransactionDto && transaction is CategoryTransaction)
            {
                throw new AttemptedChangeOfTransactionTypeException();
            }

            transaction.Amount   = dto.Amount;
            transaction.WalletId = dto.WalletId;

            if (dto.ManualTimestamp != null)
            {
                transaction.TimeStamp = dto.ManualTimestamp.Value;
            }

            switch (transaction)
            {
            case CategoryTransaction ct:
                var cDto = dto as AddUpdateCategoryTransactionDto;
                ct.CategoryId = cDto.CaterodyId;
                break;

            case WalletTransaction wt:
                var wDto = dto as AddUpdateWalletTransactionDto;
                wt.OtherWalletId = wDto.OtherWalletId;
                break;

            default: throw new NotImplementedException(
                          "Updating for this transaction type is not implemented (or something went wrong)");
            }

            await _dbContext.SaveChangesAsync();

            return(await _dbContext.Transactions
                   .Where(t => t.Id == transactionId)
                   .Select(t => new TransactionDomain
            {
                Id = t.Id,
                Amount = t.Amount,
                TargetLabel = (t is CategoryTransaction) ?
                              (t as CategoryTransaction).Category.Name :
                              (t as WalletTransaction).OtherWallet.Name,
                Timestamp = t.TimeStamp
            })
                   .SingleAsync());
        }
        private async Task ValidateTransactionDtoAsync(AddUpdateTransactionDtoBase dto)
        {
            if (dto.Amount == 0)
            {
                throw new ValidationException(new()
                {
                    { nameof(dto.Amount), "Transaction amount can't be 0." }
                });
            }

            if (dto is AddUpdateCategoryTransactionDto)
            {
                var cDto = dto as AddUpdateCategoryTransactionDto;

                var categoryBelongsToWalletOwner = await _dbContext.Categories
                                                   .Where(c => c.Id == cDto.CaterodyId)
                                                   .Join(
                    _dbContext.Wallets.Where(w => w.Id == cDto.WalletId),
                    c => c.UserId,
                    w => w.UserId,
                    (c, w) => 1)
                                                   .AnyAsync();

                if (categoryBelongsToWalletOwner == false)
                {
                    throw new ValidationException(new() { { nameof(cDto.CaterodyId), "Category should belong to the wallet's owner." } });
                }

                var targetCategory = await _dbContext.Categories
                                     .AsNoTracking()
                                     .FirstAsync(c => c.Id == cDto.CaterodyId);

                if (targetCategory.IsIncome && cDto.Amount < 0)
                {
                    throw new ValidationException(new()
                    {
                        { nameof(cDto.Amount), "The transaction value should be positive for an income category." }
                    });
                }

                if (targetCategory.IsIncome == false && cDto.Amount > 0)
                {
                    throw new ValidationException(new()
                    {
                        { nameof(cDto.Amount), "The transaction value should be negative for an expense category." }
                    });
                }
            }

            if (dto is AddUpdateWalletTransactionDto)
            {
                var wDto = dto as AddUpdateWalletTransactionDto;

                if (wDto.WalletId == wDto.OtherWalletId)
                {
                    throw new ValidationException(new()
                    {
                        { nameof(wDto.OtherWalletId), "Source wallet can not be the same as the wallet transaction belongs to." }
                    });
                }

                bool userAuthorizedForOtherWallet = await _dbContext.Wallets
                                                    .Where(w =>
                                                           w.Id == wDto.OtherWalletId &&
                                                           (w.UserId == dto.UserId || w.WalletAllowedUsers.Any(wu => wu.UserId == dto.UserId)))
                                                    .AnyAsync();

                if (userAuthorizedForOtherWallet == false)
                {
                    throw new HttpStatusException(404, "other wallet not found.");
                }
            }
        }