getTransactions :: MonadIO m
                => Kernel.PassiveWallet
                -> Maybe V1.WalletId
                -> Maybe V1.AccountIndex
                -> Maybe (V1 Address)
                -> RequestParams
                -> FilterOperations '[V1 TxId, V1 Timestamp] V1.Transaction
                -> SortOperations V1.Transaction
                -> m (Either GetTxError (APIResponse [V1.Transaction]))
getTransactions wallet mbWalletId mbAccountIndex mbAddress params fop sop = liftIO $ runExceptT $ do
    let PaginationParams{..}  = rpPaginationParams params
    let PerPage pp = ppPerPage
    let Page cp = ppPage
    (txs, total) <- go cp pp ([], Nothing)
    return $ respond params txs total
  where
    -- NOTE: See cardano-wallet#141
    --
    -- We may end up with some inconsistent metadata in the store. When fetching
    -- them all, instead of failing with a non very helpful 'WalletNotfound' or
    -- 'AccountNotFound' error because one or more metadata in the list contains
    -- unknown ids, we simply discard them from what we fetched and we fetch
    -- another batch up until we have enough (== pp).
    go cp pp (acc, total)
        | length acc >= pp =
            return $ (take pp acc, total)
        | otherwise = do
            accountFops <- castAccountFiltering mbWalletId mbAccountIndex
            mbSorting <- castSorting sop
            (metas, mbTotalEntries) <- liftIO $ TxMeta.getTxMetas
                (wallet ^. Kernel.walletMeta)
                (TxMeta.Offset . fromIntegral $ (cp - 1) * pp)
                (TxMeta.Limit . fromIntegral $ pp)
                accountFops
                (unV1 <$> mbAddress)
                (castFiltering $ mapIx unV1 <$> F.findMatchingFilterOp fop)
                (castFiltering $ mapIx unV1 <$> F.findMatchingFilterOp fop)
                mbSorting
            db <- liftIO $ Kernel.getWalletSnapshot wallet
            sc <- liftIO $ Node.getSlotCount (wallet ^. Kernel.walletNode)
            currentSlot <- liftIO $ Node.getTipSlotId (wallet ^. Kernel.walletNode)
            if null metas then
                -- A bit artificial, but we force the termination and make sure
                -- in the meantime that the algorithm only exits by one and only
                -- one branch.
                go cp (min pp $ length acc) (acc, total <|> mbTotalEntries)
            else do
                txs <- catMaybes <$> forM metas (\meta -> do
                    runExceptT (metaToTx db sc currentSlot meta) >>= \case
                        Left e -> do
                            let warn = lift . ((wallet ^. Kernel.walletLogMessage) Warning)
                            warn $ "Inconsistent entry in the metadata store: " <> sformat build e
                            return Nothing

                        Right tx ->
                            return (Just tx)
                    )
                go (cp + 1) pp (acc ++ txs, total <|> mbTotalEntries)
getTransactions :: MonadIO m
                => Kernel.PassiveWallet
                -> Maybe V1.WalletId
                -> Maybe V1.AccountIndex
                -> Maybe (V1 Address)
                -> RequestParams
                -> FilterOperations '[V1 TxId, V1 Timestamp] V1.Transaction
                -> SortOperations V1.Transaction
                -> m (Either GetTxError (APIResponse [V1.Transaction]))
getTransactions wallet mbWalletId mbAccountIndex mbAddress params fop sop = liftIO $ runExceptT $ do
    let PaginationParams{..}  = rpPaginationParams params
    let PerPage pp = ppPerPage
    let Page cp = ppPage
    (txs, total) <- go cp pp ([], Nothing)
    return $ respond params txs total
  where
    -- NOTE: See cardano-wallet#141
    --
    -- We may end up with some inconsistent metadata in the store. When fetching
    -- them all, instead of failing with a non very helpful 'WalletNotfound' or
    -- 'AccountNotFound' error because one or more metadata in the list contains
    -- unknown ids, we simply discard them from what we fetched and we fetch
    -- another batch up until we have enough (== pp).
    go cp pp (acc, total)
        | length acc >= pp =
            return $ (take pp acc, total)
        | otherwise = do
            accountFops <- castAccountFiltering mbWalletId mbAccountIndex
            mbSorting <- castSorting sop
            (metas, mbTotalEntries) <- liftIO $ TxMeta.getTxMetas
                (wallet ^. Kernel.walletMeta)
                (TxMeta.Offset . fromIntegral $ (cp - 1) * pp)
                (TxMeta.Limit . fromIntegral $ pp)
                accountFops
                (unV1 <$> mbAddress)
                (castFiltering $ mapIx unV1 <$> F.findMatchingFilterOp fop)
                (castFiltering $ mapIx unV1 <$> F.findMatchingFilterOp fop)
                mbSorting
            db <- liftIO $ Kernel.getWalletSnapshot wallet
            sc <- liftIO $ Node.getSlotCount (wallet ^. Kernel.walletNode)
            currentSlot <- liftIO $ Node.getTipSlotId (wallet ^. Kernel.walletNode)
            if null metas then
                -- A bit artificial, but we force the termination and make sure
                -- in the meantime that the algorithm only exits by one and only
                -- one branch.
                go cp (min pp $ length acc) (acc, total <|> mbTotalEntries)
            else do
                txs <- catMaybes <$> forM metas (\meta -> do
                    runExceptT (metaToTx db sc currentSlot meta) >>= \case
                        Left e -> do
                            let warn = lift . ((wallet ^. Kernel.walletLogMessage) Warning)
                            warn $ "Inconsistent entry in the metadata store: " <> sformat build e
                            return Nothing

                        Right tx ->
                            return (Just tx)
                    )
                go (cp + 1) pp (acc ++ txs, total <|> mbTotalEntries)
getTransactions wallet mbWalletId mbAccountIndex mbAddress params fop sop = liftIO $ runExceptT $ do
    let PaginationParams{..}  = rpPaginationParams params
    let PerPage pp = ppPerPage
    let Page cp = ppPage
    (txs, total) <- go cp pp ([], Nothing)
    return $ respond params txs total
  where
    -- NOTE: See cardano-wallet#141
    --
    -- We may end up with some inconsistent metadata in the store. When fetching
    -- them all, instead of failing with a non very helpful 'WalletNotfound' or
    -- 'AccountNotFound' error because one or more metadata in the list contains
    -- unknown ids, we simply discard them from what we fetched and we fetch
    -- another batch up until we have enough (== pp).
    go cp pp (acc, total)
        | length acc >= pp =
            return $ (take pp acc, total)
        | otherwise = do
            accountFops <- castAccountFiltering mbWalletId mbAccountIndex
            mbSorting <- castSorting sop
            (metas, mbTotalEntries) <- liftIO $ TxMeta.getTxMetas
                (wallet ^. Kernel.walletMeta)
                (TxMeta.Offset . fromIntegral $ (cp - 1) * pp)
                (TxMeta.Limit . fromIntegral $ pp)
                accountFops
                (unV1 <$> mbAddress)
                (castFiltering $ mapIx unV1 <$> F.findMatchingFilterOp fop)
                (castFiltering $ mapIx unV1 <$> F.findMatchingFilterOp fop)
                mbSorting
            db <- liftIO $ Kernel.getWalletSnapshot wallet
            sc <- liftIO $ Node.getSlotCount (wallet ^. Kernel.walletNode)
            currentSlot <- liftIO $ Node.getTipSlotId (wallet ^. Kernel.walletNode)
            if null metas then
                -- A bit artificial, but we force the termination and make sure
                -- in the meantime that the algorithm only exits by one and only
                -- one branch.
                go cp (min pp $ length acc) (acc, total <|> mbTotalEntries)
            else do
                txs <- catMaybes <$> forM metas (\meta -> do
                    runExceptT (metaToTx db sc currentSlot meta) >>= \case
                        Left e -> do
                            let warn = lift . ((wallet ^. Kernel.walletLogMessage) Warning)
                            warn $ "Inconsistent entry in the metadata store: " <> sformat build e
                            return Nothing

                        Right tx ->
                            return (Just tx)
                    )
                go (cp + 1) pp (acc ++ txs, total <|> mbTotalEntries)