#pragma once

#include <xrpld/core/Config.h>

#include <xrpl/ledger/Ledger.h>
#include <xrpl/rdb/RelationalDatabase.h>

namespace xrpl {
namespace detail {

/* Need to change TableTypeCount if TableType is modified. */
enum class TableType { Ledgers, Transactions, AccountTransactions };
constexpr int TableTypeCount = 3;

struct DatabasePairValid
{
    std::unique_ptr<DatabaseCon> ledgerDb;
    std::unique_ptr<DatabaseCon> transactionDb;
    bool valid;
};

/**
 * @brief makeLedgerDBs Opens ledger and transactions databases.
 * @param config Config object.
 * @param setup Path to database and opening parameters.
 * @param checkpointerSetup Database checkpointer setup.
 * @param j Journal.
 * @return Struct DatabasePairValid which contain unique pointers to ledger
 *         and transaction databases and flag if opening was successful.
 */
DatabasePairValid
makeLedgerDBs(
    Config const& config,
    DatabaseCon::Setup const& setup,
    DatabaseCon::CheckpointerSetup const& checkpointerSetup,
    beast::Journal j);

/**
 * @brief getMinLedgerSeq Returns minimum ledger sequence in given table.
 * @param session Session with database.
 * @param type Table ID for which the result is returned.
 * @return Ledger sequence or none if no ledgers exist.
 */
std::optional<LedgerIndex>
getMinLedgerSeq(soci::session& session, TableType type);

/**
 * @brief getMaxLedgerSeq Returns maximum ledger sequence in given table.
 * @param session Session with database.
 * @param type Table ID for which the result is returned.
 * @return Ledger sequence or none if no ledgers exist.
 */
std::optional<LedgerIndex>
getMaxLedgerSeq(soci::session& session, TableType type);

/**
 * @brief deleteByLedgerSeq Deletes all entries in given table
 *        for the ledger with given sequence.
 * @param session Session with database.
 * @param type Table ID from which entries will be deleted.
 * @param ledgerSeq Ledger sequence.
 */
void
deleteByLedgerSeq(soci::session& session, TableType type, LedgerIndex ledgerSeq);

/**
 * @brief deleteBeforeLedgerSeq Deletes all entries in given table
 *        for the ledgers with given sequence and all sequences below it.
 * @param session Session with database.
 * @param type Table ID from which entries will be deleted.
 * @param ledgerSeq Ledger sequence.
 */
void
deleteBeforeLedgerSeq(soci::session& session, TableType type, LedgerIndex ledgerSeq);

/**
 * @brief getRows Returns number of rows in given table.
 * @param session Session with database.
 * @param type Table ID for which the result is returned.
 * @return Number of rows.
 */
std::size_t
getRows(soci::session& session, TableType type);

/**
 * @brief getRowsMinMax Returns minimum ledger sequence,
 *        maximum ledger sequence and total number of rows in given table.
 * @param session Session with database.
 * @param type Table ID for which the result is returned.
 * @return Struct CountMinMax which contain minimum sequence,
 *         maximum sequence and number of rows.
 */
RelationalDatabase::CountMinMax
getRowsMinMax(soci::session& session, TableType type);

/**
 * @brief saveValidatedLedger Saves ledger into database.
 * @param lgrDB Link to ledgers database.
 * @param txnDB Link to transactions database.
 * @param app Application object.
 * @param ledger The ledger.
 * @param current True if ledger is current.
 * @return True is saving was successful.
 */
bool
saveValidatedLedger(
    DatabaseCon& ldgDB,
    std::unique_ptr<DatabaseCon> const& txnDB,
    Application& app,
    std::shared_ptr<Ledger const> const& ledger,
    bool current);

/**
 * @brief getLedgerInfoByIndex Returns ledger by its sequence.
 * @param session Session with database.
 * @param ledgerSeq Ledger sequence.
 * @param j Journal.
 * @return Ledger or none if ledger not found.
 */
std::optional<LedgerHeader>
getLedgerInfoByIndex(soci::session& session, LedgerIndex ledgerSeq, beast::Journal j);

/**
 * @brief getNewestLedgerInfo Returns info of newest saved ledger.
 * @param session Session with database.
 * @param j Journal.
 * @return Ledger info or none if ledger not found.
 */
std::optional<LedgerHeader>
getNewestLedgerInfo(soci::session& session, beast::Journal j);

/**
 * @brief getLimitedOldestLedgerInfo Returns info of oldest ledger
 *        from ledgers with sequences greater or equal to given.
 * @param session Session with database.
 * @param ledgerFirstIndex Minimum ledger sequence.
 * @param j Journal.
 * @return Ledger info or none if ledger not found.
 */
std::optional<LedgerHeader>
getLimitedOldestLedgerInfo(soci::session& session, LedgerIndex ledgerFirstIndex, beast::Journal j);

/**
 * @brief getLimitedNewestLedgerInfo Returns info of newest ledger
 *        from ledgers with sequences greater or equal to given.
 * @param session Session with database.
 * @param ledgerFirstIndex Minimum ledger sequence.
 * @param j Journal.
 * @return Ledger info or none if ledger not found.
 */
std::optional<LedgerHeader>
getLimitedNewestLedgerInfo(soci::session& session, LedgerIndex ledgerFirstIndex, beast::Journal j);

/**
 * @brief getLedgerInfoByHash Returns info of ledger with given hash.
 * @param session Session with database.
 * @param ledgerHash Hash of the ledger.
 * @param j Journal.
 * @return Ledger or none if ledger not found.
 */
std::optional<LedgerHeader>
getLedgerInfoByHash(soci::session& session, uint256 const& ledgerHash, beast::Journal j);

/**
 * @brief getHashByIndex Returns hash of ledger with given sequence.
 * @param session Session with database.
 * @param ledgerIndex Ledger sequence.
 * @return Hash of the ledger.
 */
uint256
getHashByIndex(soci::session& session, LedgerIndex ledgerIndex);

/**
 * @brief getHashesByIndex Returns hash of the ledger and hash of parent
 *        ledger for the ledger of given sequence.
 * @param session Session with database.
 * @param ledgerIndex Ledger sequence.
 * @param j Journal.
 * @return Struct LedgerHashPair which contain hashes of the ledger and
 *         its parent ledger.
 */
std::optional<LedgerHashPair>
getHashesByIndex(soci::session& session, LedgerIndex ledgerIndex, beast::Journal j);

/**
 * @brief getHashesByIndex Returns hash of the ledger and hash of parent
 *        ledger for all ledgers with sequences from given minimum limit
 *        to given maximum limit.
 * @param session Session with database.
 * @param minSeq Minimum ledger sequence.
 * @param maxSeq Maximum ledger sequence.
 * @param j Journal.
 * @return Map which points sequence number of found ledger to the struct
 *         LedgerHashPair which contains ledger hash and its parent hash.
 */
std::map<LedgerIndex, LedgerHashPair>
getHashesByIndex(soci::session& session, LedgerIndex minSeq, LedgerIndex maxSeq, beast::Journal j);

/**
 * @brief getTxHistory Returns given number of most recent transactions
 *        starting from given number of entry.
 * @param session Session with database.
 * @param app Application object.
 * @param startIndex Offset of first returned entry.
 * @param quantity Number of returned entries.
 * @return Vector of shared pointers to transactions sorted in
 *         descending order by ledger sequence. Also number of transactions
 *         if count == true.
 */
std::pair<std::vector<std::shared_ptr<Transaction>>, int>
getTxHistory(soci::session& session, Application& app, LedgerIndex startIndex, int quantity);

/**
 * @brief getOldestAccountTxs Returns oldest transactions for given
 *        account which match given criteria starting from given offset.
 * @param session Session with database.
 * @param app Application object.
 * @param ledgerMaster LedgerMaster object.
 * @param options Struct AccountTxOptions which contain criteria to match:
 *        the account, minimum and maximum ledger numbers to search,
 *        offset of first entry to return, number of transactions to return,
 *        flag if this number unlimited.
 * @param j Journal.
 * @return Vector of pairs of found transactions and their metadata
 *         sorted in ascending order by account sequence.
 *         Also number of transactions processed or skipped.
 *         If this number is >= 0, then it means number of transactions
 *         processed, if it is < 0, then -number means number of transactions
 *         skipped. We need to skip some quantity of transactions if option
 *         offset is > 0 in the options structure.
 */
std::pair<RelationalDatabase::AccountTxs, int>
getOldestAccountTxs(
    soci::session& session,
    Application& app,
    LedgerMaster& ledgerMaster,
    RelationalDatabase::AccountTxOptions const& options,
    beast::Journal j);

/**
 * @brief getNewestAccountTxs Returns newest transactions for given
 *        account which match given criteria starting from given offset.
 * @param session Session with database.
 * @param app Application object.
 * @param ledgerMaster LedgerMaster object.
 * @param options Struct AccountTxOptions which contain criteria to match:
 *        the account, minimum and maximum ledger numbers to search,
 *        offset of first entry to return, number of transactions to return,
 *        flag if this number unlimited.
 * @param j Journal.
 * @return Vector of pairs of found transactions and their metadata
 *         sorted in descending order by account sequence.
 *         Also number of transactions processed or skipped.
 *         If this number is >= 0, then it means number of transactions
 *         processed, if it is < 0, then -number means number of transactions
 *         skipped. We need to skip some quantity of transactions if option
 *         offset is > 0 in the options structure.
 */
std::pair<RelationalDatabase::AccountTxs, int>
getNewestAccountTxs(
    soci::session& session,
    Application& app,
    LedgerMaster& ledgerMaster,
    RelationalDatabase::AccountTxOptions const& options,
    beast::Journal j);

/**
 * @brief getOldestAccountTxsB Returns oldest transactions in binary form
 *        for given account which match given criteria starting from given
 *        offset.
 * @param session Session with database.
 * @param app Application object.
 * @param options Struct AccountTxOptions which contain criteria to match:
 *        the account, minimum and maximum ledger numbers to search,
 *        offset of first entry to return, number of transactions to return,
 *        flag if this number unlimited.
 * @param j Journal.
 * @return Vector of tuples of found transactions, their metadata and
 *         account sequences sorted in ascending order by account
 *         sequence. Also number of transactions processed or skipped.
 *         If this number is >= 0, then it means number of transactions
 *         processed, if it is < 0, then -number means number of transactions
 *         skipped. We need to skip some quantity of transactions if option
 *         offset is > 0 in the options structure.
 */
std::pair<std::vector<RelationalDatabase::txnMetaLedgerType>, int>
getOldestAccountTxsB(
    soci::session& session,
    Application& app,
    RelationalDatabase::AccountTxOptions const& options,
    beast::Journal j);

/**
 * @brief getNewestAccountTxsB Returns newest transactions in binary form
 *        for given account which match given criteria starting from given
 *        offset.
 * @param session Session with database.
 * @param app Application object.
 * @param options Struct AccountTxOptions which contain criteria to match:
 *        the account, minimum and maximum ledger numbers to search,
 *        offset of first entry to return, number of transactions to return,
 *        flag if this number unlimited.
 * @param j Journal.
 * @return Vector of tuples of found transactions, their metadata and
 *         account sequences sorted in descending order by account
 *         sequence. Also number of transactions processed or skipped.
 *         If this number is >= 0, then it means number of transactions
 *         processed, if it is < 0, then -number means number of transactions
 *         skipped. We need to skip some quantity of transactions if option
 *         offset is > 0 in the options structure.
 */
std::pair<std::vector<RelationalDatabase::txnMetaLedgerType>, int>
getNewestAccountTxsB(
    soci::session& session,
    Application& app,
    RelationalDatabase::AccountTxOptions const& options,
    beast::Journal j);

/**
 * @brief oldestAccountTxPage Searches oldest transactions for given
 *        account which match given criteria starting from given marker
 *        and calls callback for each found transaction.
 * @param session Session with database.
 * @param onUnsavedLedger Callback function to call on each found unsaved
 *        ledger within given range.
 * @param onTransaction Callback function to call on each found transaction.
 * @param options Struct AccountTxPageOptions which contain criteria to
 *        match: the account, minimum and maximum ledger numbers to search,
 *        marker of first returned entry, number of transactions to return,
 *        flag if this number unlimited.
 * @param page_length Total number of transactions to return.
 * @return Vector of tuples of found transactions, their metadata and
 *         account sequences sorted in ascending order by account
 *         sequence and marker for next search if search not finished.
 *         Also number of transactions processed during this call.
 */
std::pair<std::optional<RelationalDatabase::AccountTxMarker>, int>
oldestAccountTxPage(
    soci::session& session,
    std::function<void(std::uint32_t)> const& onUnsavedLedger,
    std::function<void(std::uint32_t, std::string const&, Blob&&, Blob&&)> const& onTransaction,
    RelationalDatabase::AccountTxPageOptions const& options,
    std::uint32_t page_length);

/**
 * @brief newestAccountTxPage Searches newest transactions for given
 *        account which match given criteria starting from given marker
 *        and calls callback for each found transaction.
 * @param session Session with database.
 * @param onUnsavedLedger Callback function to call on each found unsaved
 *        ledger within given range.
 * @param onTransaction Callback function to call on each found transaction.
 * @param options Struct AccountTxPageOptions which contain criteria to
 *        match: the account, minimum and maximum ledger numbers to search,
 *        marker of first returned entry, number of transactions to return,
 *        flag if this number unlimited.
 * @param page_length Total number of transactions to return.
 * @return Vector of tuples of found transactions, their metadata and
 *         account sequences sorted in descending order by account
 *         sequence and marker for next search if search not finished.
 *         Also number of transactions processed during this call.
 */
std::pair<std::optional<RelationalDatabase::AccountTxMarker>, int>
newestAccountTxPage(
    soci::session& session,
    std::function<void(std::uint32_t)> const& onUnsavedLedger,
    std::function<void(std::uint32_t, std::string const&, Blob&&, Blob&&)> const& onTransaction,
    RelationalDatabase::AccountTxPageOptions const& options,
    std::uint32_t page_length);

/**
 * @brief getTransaction Returns transaction with given hash. If not found
 *        and range given then check if all ledgers from the range are
 *        present in the database.
 * @param session Session with database.
 * @param app Application object.
 * @param id Hash of the transaction.
 * @param range Range of ledgers to check, if present.
 * @param ec Default value of error code.
 * @return Transaction and its metadata if found, TxSearched::all if range
 *         given and all ledgers from range are present in the database,
 *         TxSearched::some if range given and not all ledgers are present,
 *         TxSearched::unknown if range not given or deserializing error
 *         occurred. In the last case error code modified in ec link
 *         parameter, in other cases default error code remained.
 */
std::variant<RelationalDatabase::AccountTx, TxSearched>
getTransaction(
    soci::session& session,
    Application& app,
    uint256 const& id,
    std::optional<ClosedInterval<uint32_t>> const& range,
    error_code_i& ec);

/**
 * @brief dbHasSpace Checks if given database has available space.
 * @param session Session with database.
 * @param config Config object.
 * @param j Journal.
 * @return True if space is available.
 */
bool
dbHasSpace(soci::session& session, Config const& config, beast::Journal j);

}  // namespace detail
}  // namespace xrpl
