
#include "../src/MicroCore.h"
#include "../src/CurrentBlockchainStatus.h"
#include "../src/ThreadRAII.h"

#include "gmock/gmock.h"
#include "gtest/gtest.h"

#include "helpers.h"
#include "mocks.h"

namespace
{



class BCSTATUS_TEST : public ::testing::TestWithParam<network_type>
{
public:

    static void
    SetUpTestCase()
    {
        string config_path {"../config/config.json"};
        config_json = xmreg::BlockchainSetup::read_config(config_path);
    }

protected:

    virtual void
    SetUp()
    {
        net_type = GetParam();

        bc_setup = xmreg::BlockchainSetup {
                net_type, do_not_relay, config_json};

        mcore = std::make_unique<MockMicroCore>();
        mcore_ptr = mcore.get();

        rpc = std::make_unique<MockRPCCalls>("dummy deamon url");
        rpc_ptr = rpc.get();

        tp = std::make_unique<TP::ThreadPool>();
        tp_ptr = tp.get();

        bcs = std::make_unique<xmreg::CurrentBlockchainStatus>(
                    bc_setup, std::move(mcore), std::move(rpc),
                    std::move(tp));
    }

     network_type net_type {network_type::STAGENET};
     bool do_not_relay {false};
     xmreg::BlockchainSetup bc_setup;
     std::unique_ptr<MockMicroCore> mcore;
     std::unique_ptr<MockRPCCalls> rpc;
     std::unique_ptr<TP::ThreadPool> tp;
     std::unique_ptr<xmreg::CurrentBlockchainStatus> bcs;

     MockMicroCore* mcore_ptr;
     MockRPCCalls* rpc_ptr;
     TP::ThreadPool* tp_ptr;

     static nlohmann::json config_json;
};

nlohmann::json BCSTATUS_TEST::config_json;



TEST_P(BCSTATUS_TEST, DefaultConstruction)
{
    xmreg::CurrentBlockchainStatus bcs {
        bc_setup, nullptr, nullptr, nullptr};
    EXPECT_TRUE(true);
}



TEST_P(BCSTATUS_TEST, InitMoneroBlockchain)
{
    EXPECT_CALL(*mcore_ptr, init(_, _))
            .WillOnce(Return(true));

    EXPECT_TRUE(bcs->init_monero_blockchain());
}

TEST_P(BCSTATUS_TEST, GetBlock)
{
   EXPECT_CALL(*mcore_ptr, get_block_from_height(_, _))
           .WillOnce(Return(true));

    uint64_t height = 1000;
    block blk;

    EXPECT_TRUE(bcs->get_block(height, blk));
}


ACTION(ThrowBlockDNE)
{
    throw BLOCK_DNE("Mock Throw: Block does not exist!");
}

TEST_P(BCSTATUS_TEST, GetBlockRange)
{

   vector<block> blocks_to_return {block(), block(), block()};

   EXPECT_CALL(*mcore_ptr, get_blocks_range(_, _))
           .WillOnce(Return(blocks_to_return));

    uint64_t h1 = 1000;
    uint64_t h2 = h1+2;

    vector<block> blocks = bcs->get_blocks_range(h1, h2);

    EXPECT_EQ(blocks, blocks_to_return);

    EXPECT_CALL(*mcore_ptr, get_blocks_range(_, _))
            .WillOnce(ThrowBlockDNE());

    blocks = bcs->get_blocks_range(h1, h2);

    EXPECT_TRUE(blocks.empty());
}

TEST_P(BCSTATUS_TEST, GetBlockTxs)
{
    EXPECT_CALL(*mcore_ptr, get_transactions(_, _, _))
            .WillOnce(Return(true));

    const block dummy_blk;
    vector<transaction> blk_txs;
    vector<crypto::hash> missed_txs;

    EXPECT_TRUE(bcs->get_block_txs(dummy_blk, blk_txs, missed_txs));

    EXPECT_CALL(*mcore_ptr, get_transactions(_, _, _))
            .WillOnce(Return(false));

    EXPECT_FALSE(bcs->get_block_txs(dummy_blk, blk_txs, missed_txs));
}

TEST_P(BCSTATUS_TEST, GetTxs)
{
    EXPECT_CALL(*mcore_ptr, get_transactions(_, _, _))
            .WillOnce(Return(true));

    vector<crypto::hash> txs_to_get;
    vector<transaction> blk_txs;
    vector<crypto::hash> missed_txs;

    EXPECT_TRUE(bcs->get_txs(txs_to_get, blk_txs, missed_txs));

    EXPECT_CALL(*mcore_ptr, get_transactions(_, _, _))
            .WillOnce(Return(false));

    EXPECT_FALSE(bcs->get_txs(txs_to_get, blk_txs, missed_txs));
}

TEST_P(BCSTATUS_TEST, TxExist)
{
    EXPECT_CALL(*mcore_ptr, have_tx(_))
            .WillOnce(Return(true));

    EXPECT_TRUE(bcs->tx_exist(crypto::hash()));

    uint64_t mock_tx_index_to_return {4444};

    // return true and set tx_index (ret by ref) to mock_tx_index_to_return
    EXPECT_CALL(*mcore_ptr, tx_exists(_, _))
            .WillOnce(DoAll(SetArgReferee<1>(mock_tx_index_to_return),
                            Return(true)));

    uint64_t tx_index {0};

    EXPECT_TRUE(bcs->tx_exist(crypto::hash(), tx_index));
    EXPECT_EQ(tx_index, mock_tx_index_to_return);

    // just some dummy hash
    string tx_hash_str
        {"fc4b8d5956b30dc4a353b171b4d974697dfc32730778f138a8e7f16c11907691"};

    tx_index = 0;

    EXPECT_CALL(*mcore_ptr, tx_exists(_, _))
            .WillOnce(DoAll(SetArgReferee<1>(mock_tx_index_to_return),
                            Return(true)));

    EXPECT_TRUE(bcs->tx_exist(tx_hash_str, tx_index));
    EXPECT_EQ(tx_index, mock_tx_index_to_return);

    tx_hash_str = "wrong_hash";

    EXPECT_FALSE(bcs->tx_exist(tx_hash_str, tx_index));
}

TEST_P(BCSTATUS_TEST, GetTx)
{
    // tx_hash 2ff79595683b546c8959e353b2c345394ea80694073d9237a146448bb65915dd
    string tx_hex {"02c2d20701ff86d20701f88ceccabdbf06027b0b71991727c05f0ec98bdc0e12fe598acf8eb808790c3692550a464b722ec22101d9d9ddd1f2b7010715195405742af8fa89e12c0d6c3f490c81a08e836e3d516300"};

    TX_FROM_HEX(tx_hex);

    EXPECT_CALL(*mcore_ptr, get_tx(_, _))
            .WillRepeatedly(DoAll(SetArgReferee<1>(tx),
                            Return(true)));

    crypto::hash any_tx_hash;
    transaction returned_tx;

    // using tx_hash
    EXPECT_TRUE(bcs->get_tx(any_tx_hash, returned_tx));

    EXPECT_EQ(get_transaction_hash(returned_tx), tx_hash);

    // tx_hash as string
    transaction returned_tx2;

    EXPECT_TRUE(bcs->get_tx(tx_hash_str, returned_tx2));

    EXPECT_EQ(get_transaction_hash(returned_tx), tx_hash);

    // tx hash as string fails to parse

    EXPECT_FALSE(bcs->get_tx("hash of wrong format", returned_tx2));
}

//MOCK_CONST_METHOD1(get_tx_block_height,
//                   uint64_t(crypto::hash const& tx_hash));

TEST_P(BCSTATUS_TEST, GetTxBlockHeight)
{
    // tx_hash 2ff79595683b546c8959e353b2c345394ea80694073d9237a146448bb65915dd
    string tx_hex {"02c2d20701ff86d20701f88ceccabdbf06027b0b71991727c05f0ec98bdc0e12fe598acf8eb808790c3692550a464b722ec22101d9d9ddd1f2b7010715195405742af8fa89e12c0d6c3f490c81a08e836e3d516300"};

    TX_FROM_HEX(tx_hex);

    EXPECT_CALL(*mcore_ptr, have_tx(_))
            .WillOnce(Return(true));

    EXPECT_CALL(*mcore_ptr, get_tx_block_height(_))
            .WillRepeatedly(Return(123));

    int64_t height_returned;

    EXPECT_TRUE(bcs->get_tx_block_height(tx_hash, height_returned));

    EXPECT_EQ(height_returned, 123);

    // tx does not exist

    EXPECT_CALL(*mcore_ptr, have_tx(_))
            .WillOnce(Return(false));

    EXPECT_FALSE(bcs->get_tx_block_height(tx_hash, height_returned));
}

TEST_P(BCSTATUS_TEST, GetTxWithOutput)
{
    // some dummy tx hash
    RAND_TX_HASH();

    const tx_out_index tx_idx_to_return = make_pair(tx_hash, 6);

    EXPECT_CALL(*mcore_ptr, get_output_tx_and_index(_, _))
            .WillOnce(Return(tx_idx_to_return));

    EXPECT_CALL(*mcore_ptr, get_tx(_, _))
            .WillOnce(Return(true));

    const uint64_t mock_output_idx {4};
    const uint64_t mock_amount {11110};

    transaction tx_returned;
    uint64_t out_idx_returned;

    EXPECT_TRUE(bcs->get_tx_with_output(mock_output_idx, mock_amount,
                                        tx_returned, out_idx_returned));
}

ACTION(ThrowOutputDNE)
{
    throw OUTPUT_DNE("Mock Throw: Output does not exist!");
}

TEST_P(BCSTATUS_TEST, GetTxWithOutputFailure)
{
    // some dummy tx hash
    RAND_TX_HASH();

    const tx_out_index tx_idx_to_return = make_pair(tx_hash,
                                                    6 /* output idx */);

    EXPECT_CALL(*mcore_ptr, get_output_tx_and_index(_, _))
            .WillOnce(Return(tx_idx_to_return));

    EXPECT_CALL(*mcore_ptr, get_tx(_, _))
            .WillOnce(Return(false));

    const uint64_t mock_output_idx {4};
    const uint64_t mock_amount {11110};

    transaction tx_returned;
    uint64_t out_idx_returned;

    EXPECT_FALSE(bcs->get_tx_with_output(mock_output_idx, mock_amount,
                                        tx_returned, out_idx_returned));

    // or

    EXPECT_CALL(*mcore_ptr, get_output_tx_and_index(_, _))
            .WillOnce(ThrowOutputDNE());

    EXPECT_FALSE(bcs->get_tx_with_output(mock_output_idx, mock_amount,
                                        tx_returned, out_idx_returned));
}

TEST_P(BCSTATUS_TEST, GetCurrentHeight)
{
    uint64_t mock_current_height {1619148};

    EXPECT_CALL(*rpc_ptr, get_current_height(_))
            .WillOnce(SetArgReferee<0>(mock_current_height));

    bcs->update_current_blockchain_height();

    EXPECT_EQ(bcs->get_current_blockchain_height(),
              mock_current_height - 1);
}

TEST_P(BCSTATUS_TEST, IsTxSpendtimeUnlockedScenario1)
{
    // there are two main scenerious here.
    // Scenerio 1: tx_unlock_time is block height
    // Scenerio 2: tx_unlock_time is timestamp.

    const uint64_t mock_current_height {100};

    EXPECT_CALL(*rpc_ptr, get_current_height(_))
            .WillOnce(SetArgReferee<0>(mock_current_height));

    bcs->update_current_blockchain_height();

    // SCENARIO 1: tx_unlock_time is block height

    // expected unlock time is in future, thus a tx is still locked

    uint64_t tx_unlock_time {mock_current_height
                + CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE};

    uint64_t not_used_block_height {0}; // not used in the first
                                        // part of the test case

    EXPECT_FALSE(bcs->is_tx_unlocked(
                     tx_unlock_time, not_used_block_height));

    // expected unlock time is in the future
    // (1 blocks from now), thus a tx is locked

    tx_unlock_time = mock_current_height + 1;

    EXPECT_FALSE(bcs->is_tx_unlocked(
                     tx_unlock_time, not_used_block_height));

    // expected unlock time is in the past
    // (10 blocks behind), thus a tx is unlocked

    tx_unlock_time = mock_current_height
            - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE;

    EXPECT_TRUE(bcs->is_tx_unlocked(tx_unlock_time,
                                    not_used_block_height));

    // expected unlock time is same as as current height
    // thus a tx is unlocked

    tx_unlock_time = mock_current_height;

    EXPECT_TRUE(bcs->is_tx_unlocked(tx_unlock_time,
                                    not_used_block_height));
}


class MockTxUnlockChecker : public xmreg::TxUnlockChecker
{
public:

    // mock system call to get current timestamp
    MOCK_CONST_METHOD0(get_current_time, uint64_t());
    //MOCK_CONST_METHOD1(get_leeway, uint64_t(uint64_t tx_block_height));
};

TEST_P(BCSTATUS_TEST, IsTxSpendtimeUnlockedScenario2)
{
    // there are two main scenerious here.
    // Scenerio 1: tx_unlock_time is block height
    // Scenerio 2: tx_unlock_time is timestamp.

    const uint64_t mock_current_height {100};

    EXPECT_CALL(*rpc_ptr, get_current_height(_))
            .WillOnce(SetArgReferee<0>(mock_current_height));

    bcs->update_current_blockchain_height();

    // SCENARIO 2: tx_unlock_time is timestamp.

    MockTxUnlockChecker mock_tx_unlock_checker;

    const uint64_t current_timestamp {1000000000};

    EXPECT_CALL(mock_tx_unlock_checker, get_current_time())
            .WillRepeatedly(Return(1000000000));

    uint64_t block_height = mock_current_height;

    // tx unlock time is now
    uint64_t tx_unlock_time {current_timestamp}; // mock timestamp

    EXPECT_TRUE(bcs->is_tx_unlocked(tx_unlock_time, block_height,
                                    mock_tx_unlock_checker));

    // unlock time is 1 second more than needed
    tx_unlock_time = current_timestamp
            + mock_tx_unlock_checker.get_leeway(
                block_height, bcs->get_bc_setup().net_type) + 1;

    EXPECT_FALSE(bcs->is_tx_unlocked(tx_unlock_time, block_height,
                                     mock_tx_unlock_checker));

}


TEST_P(BCSTATUS_TEST, GetOutputKeys)
{
    // we are going to expect two outputs
    vector<output_data_t> outputs_to_return;

    outputs_to_return.push_back(
                output_data_t {
                crypto::rand<crypto::public_key>(),
                1000, 2222,
                crypto::rand<rct::key>()});

    outputs_to_return.push_back(
                output_data_t {
                crypto::rand<crypto::public_key>(),
                3333, 5555,
                crypto::rand<rct::key>()});

    EXPECT_CALL(*mcore_ptr, get_output_key(_, _, _))
            .WillOnce(SetArgReferee<2>(outputs_to_return));

    const uint64_t mock_amount {1111};
    const vector<uint64_t> mock_absolute_offsets;
    vector<cryptonote::output_data_t> outputs;

    EXPECT_TRUE(bcs->get_output_keys(mock_amount,
                                     mock_absolute_offsets,
                                     outputs));

    EXPECT_EQ(outputs.back().pubkey, outputs_to_return.back().pubkey);

    EXPECT_CALL(*mcore_ptr, get_output_key(_, _, _))
            .WillOnce(ThrowOutputDNE());

    EXPECT_FALSE(bcs->get_output_keys(mock_amount,
                                      mock_absolute_offsets,
                                      outputs));
}

TEST_P(BCSTATUS_TEST, GetAccountIntegratedAddressAsStr)
{
    // bcs->get_account_integrated_address_as_str only forwards
    // call to cryptonote function. so we just check if
    // forwarding is correct, not wether the cryptonote
    // function works correctly.

    crypto::hash8 payment_id8 = crypto::rand<crypto::hash8>();
    string payment_id8_str = pod_to_hex(payment_id8);

    string expected_int_addr
            = cryptonote::get_account_integrated_address_as_str(
                bcs->get_bc_setup().net_type,
                bcs->get_bc_setup().import_payment_address.address,
                payment_id8);

    string resulting_int_addr
            = bcs->get_account_integrated_address_as_str(payment_id8);

    EXPECT_EQ(expected_int_addr, resulting_int_addr);

    resulting_int_addr
                = bcs->get_account_integrated_address_as_str(
                payment_id8_str);

    EXPECT_EQ(expected_int_addr, resulting_int_addr);


    resulting_int_addr
                = bcs->get_account_integrated_address_as_str(
                "wrong_payment_id8");

    EXPECT_TRUE(resulting_int_addr.empty());
}


ACTION(ThrowTxDNE)
{
    throw TX_DNE("Mock Throw: Tx does not exist!");
}

TEST_P(BCSTATUS_TEST, GetAmountSpecificIndices)
{
    vector<uint64_t> out_indices_to_return {1,2,3};

    EXPECT_CALL(*mcore_ptr, tx_exists(_, _))
            .WillOnce(Return(true));

    EXPECT_CALL(*mcore_ptr, get_tx_amount_output_indices(_))
            .WillOnce(Return(out_indices_to_return));

    vector<uint64_t> out_indices;

    RAND_TX_HASH();

    EXPECT_TRUE(bcs->get_amount_specific_indices(tx_hash, out_indices));

    EXPECT_EQ(out_indices, out_indices_to_return);

    EXPECT_CALL(*mcore_ptr, tx_exists(_, _))
            .WillOnce(Return(false));

    EXPECT_FALSE(bcs->get_amount_specific_indices(tx_hash, out_indices));

    EXPECT_CALL(*mcore_ptr, tx_exists(_, _))
            .WillOnce(ThrowTxDNE());

    EXPECT_FALSE(bcs->get_amount_specific_indices(tx_hash, out_indices));
}

//TEST_P(BCSTATUS_TEST, GetRandomOutputs)
//{
//    using out_for_amount = COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS
//                                    ::outs_for_amount;

//    std::vector<out_for_amount> outputs_to_return;

//    outputs_to_return.push_back(out_for_amount {22, {}});
//    outputs_to_return.push_back(out_for_amount {66, {}});

//    COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response res;

//    res.outs = outputs_to_return;

//    EXPECT_CALL(*mcore_ptr, get_random_outs_for_amounts(_, _))
//            .WillOnce(DoAll(SetArgReferee<1>(res), Return(true)));

//    const vector<uint64_t> mock_amounts {444, 556, 77}; // any
//    const uint64_t mock_outs_count {3}; // any

//    std::vector<out_for_amount> found_outputs;

//    EXPECT_TRUE(bcs->get_random_outputs(
//                    mock_amounts, mock_outs_count,
//                    found_outputs));

//    EXPECT_EQ(found_outputs.size(), outputs_to_return.size());
//    EXPECT_EQ(found_outputs.back().amount,
//              outputs_to_return.back().amount);

//    EXPECT_CALL(*mcore_ptr, get_random_outs_for_amounts(_, _))
//            .WillOnce(Return(false));

//    EXPECT_FALSE(bcs->get_random_outputs(
//                    mock_amounts, mock_outs_count,
//                    found_outputs));
//}

TEST_P(BCSTATUS_TEST, GetOutput)
{
    using outkey = COMMAND_RPC_GET_OUTPUTS_BIN::outkey;

    outkey output_key_to_return {
        crypto::rand<crypto::public_key>(),
        crypto::rand<rct::key>(),
        true, 444,
        crypto::rand<crypto::hash>()};

    COMMAND_RPC_GET_OUTPUTS_BIN::response res;

    res.outs.push_back(output_key_to_return);

    EXPECT_CALL(*mcore_ptr, get_outs(_, _))
            .WillOnce(DoAll(SetArgReferee<1>(res), Return(true)));

    const uint64_t mock_amount {0};
    const uint64_t mock_global_output_index {0};
    outkey output_info;

    EXPECT_TRUE(bcs->get_output(mock_amount,
                                mock_global_output_index,
                                output_info));

    EXPECT_EQ(output_info.key, output_key_to_return.key);

    EXPECT_CALL(*mcore_ptr, get_outs(_, _))
            .WillOnce(Return(false));

    EXPECT_FALSE(bcs->get_output(mock_amount,
                                mock_global_output_index,
                                output_info));
}

TEST_P(BCSTATUS_TEST, GetDynamicPerKbFeeEstimate)
{
    EXPECT_CALL(*mcore_ptr, get_dynamic_base_fee_estimate(_))
            .WillOnce(Return(3333));

    EXPECT_EQ(bcs->get_dynamic_per_kb_fee_estimate(), 3333 * 1024);
}

TEST_P(BCSTATUS_TEST, CommitTx)
{
    EXPECT_CALL(*rpc_ptr, commit_tx(_, _, _))
            .WillOnce(Return(true));

    string tx_blob {"mock blob"};
    string error_msg;

    EXPECT_TRUE(bcs->commit_tx(tx_blob, error_msg, true));

    EXPECT_CALL(*rpc_ptr, commit_tx(_, _, _))
            .WillOnce(Return(false));

    EXPECT_FALSE(bcs->commit_tx(tx_blob, error_msg, true));
}


TEST_P(BCSTATUS_TEST, ReadMempool)
{
    // stagenet tx: 4b40cfb2fdce2cd57a834a380901d55d70aba29dad13ac6c4dc82a895f439ecf
    const string tx_4b40_hex {"[encoded data, 26504 characters]"};

    std::string tx_blob;

    ASSERT_TRUE(xmreg::hex_to_tx_blob(tx_4b40_hex, tx_blob));

    vector<tx_info> mempool_txs_to_return;

    mempool_txs_to_return.push_back(tx_info{});

    mempool_txs_to_return.back().tx_blob = tx_blob;

    EXPECT_CALL(*mcore_ptr, get_mempool_txs(_, _))
            .WillOnce(DoAll(
                          SetArgReferee<0>(mempool_txs_to_return),
                          Return(true)));

    EXPECT_TRUE(bcs->read_mempool());

    xmreg::CurrentBlockchainStatus::mempool_txs_t mempool_txs;

    mempool_txs = bcs->get_mempool_txs();

    TX_FROM_HEX(tx_4b40_hex);

    EXPECT_EQ(get_transaction_hash(mempool_txs[0].second),
            tx_hash);
}


TEST_P(BCSTATUS_TEST, ReadMempoolFailure)
{
    vector<tx_info> mempool_txs_to_return;

    mempool_txs_to_return.push_back(tx_info{});
    mempool_txs_to_return.push_back(tx_info{});
    mempool_txs_to_return.push_back(tx_info{});

    EXPECT_CALL(*mcore_ptr, get_mempool_txs(_, _))
            .WillOnce(DoAll(
                          SetArgReferee<0>(mempool_txs_to_return),
                          Return(true)));

    EXPECT_FALSE(bcs->read_mempool());

    EXPECT_CALL(*mcore_ptr, get_mempool_txs(_, _))
            .WillOnce(DoAll(
                          SetArgReferee<0>(mempool_txs_to_return),
                          Return(false)));

    EXPECT_FALSE(bcs->read_mempool());
}

TEST_P(BCSTATUS_TEST, SearchIfPaymentMade)
{
    // test search_if_payment_made for testnet
    // and stagenet blockchains.

    if (GetParam() == 0) // if mainnet
    {
        EXPECT_TRUE(true);
        return;
    }

    vector<tx_info> mempool_txs_to_return;

    vector<string> txs_hex;

    string expected_payment_id_str;
    uint64_t desired_amount {0}; // 0.1 xmr
    string expected_tx_hash_str;

    if (GetParam() == 2) // stagenet
    {
        // tx_hash 2ff79595683b546c8959e353b2c345394ea80694073d9237a146448bb65915dd
        txs_hex.emplace_back("02c2d20701ff86d20701f88ceccabdbf06027b0b71991727c05f0ec98bdc0e12fe598acf8eb808790c3692550a464b722ec22101d9d9ddd1f2b7010715195405742af8fa89e12c0d6c3f490c81a08e836e3d516300");

        // tx_hash 8a7e09ae4bc4d85153ff85350da82d076461db61e94b321fb727a0fcdd96e8e6
        // this tx should have 0.1 xmr for stagnet wallet 53mqDDKtVkib8inMa41HuNJG4tj9CcaNKGr6EVSbvhWGJdpDQCiNNYBUNF1oDb8BczU5aD68d3HNKXaEsPq8cvbQE2FBkTS
        // viewkey: 53c5850e895122574c53a4f952c726be3fe22bcd2b08f4bfed8946d887cc950b
        // payment_id8: 1f5d434f0b34448b
        txs_hex.emplace_back("[encoded data, 27584 characters]");

        expected_tx_hash_str = "8a7e09ae4bc4d85153ff85350da82d076461db61e94b321fb727a0fcdd96e8e6";
        expected_payment_id_str = "1f5d434f0b34448b";
         desired_amount = 100000000000ull; // 0.1 xmr

        // tx_hash ac715b386010bd95a506fde0ac0405aa8ad72080b1d7cef257b5b112d9ed84bb
        txs_hex.emplace_back("[encoded data, 42122 characters]");
    }
    else if (GetParam() == 1) // testnet
    {
        // tx_hash 84ec08256fe54f3fbbe8dd109a177a35cd1d7788c1cfdf9074684ffb7b058a62
        txs_hex.emplace_back("[encoded data, 5388 characters]");

        // tx_hash 6aa5057aeccac8e1bf632eaa813554352b2f5a4b6bd02f920a44cf3081488a2f
        // this tx should have 0.1 xmr for testnet wallet7 9tzmPMTViHYM3z6NAgQni1Qm1Emzxy5hQFibPgWD3LVTAz91yok5Eni1pH6zKhBHzpTU15GZooPHSGHXFvFuXEdmEG2sWAZ
        // viewkey: ae6184287ca791609844f140b8502ccfa2223c04c8699cf31fcd0af1f1d0be08
        // payment_id8: 6c224eea64220435
        txs_hex.emplace_back("[encoded data, 26136 characters]");

        expected_tx_hash_str = "6aa5057aeccac8e1bf632eaa813554352b2f5a4b6bd02f920a44cf3081488a2f";
        expected_payment_id_str = "6c224eea64220435";
        desired_amount = 100000000000; // 0.123 xmr

        // tx_hash 61f9949d200593d9de4043db15b504dd8aeef0785e8f03d81f23eb76059cd419
        txs_hex.emplace_back("[encoded data, 8678 characters]");

        //tx hash 8b3d170d3a5f4075e144b42073a04f54c4be16929f782039fa438befe2a2d0c2
        // also mine
        txs_hex.emplace_back("[encoded data, 26138 characters]");


        //tx hash 7cc28c836abf278ad8f329c1ae07868aa9cda16bd2b52126424bfc7cc97566ec
        // second tx which has payment_id8 for us
        txs_hex.emplace_back("[encoded data, 27056 characters]");

    }

    vector<string> txs_blobs;

    for (auto const& tx_hex: txs_hex)
    {
        string tx_blob;
        ASSERT_TRUE(xmreg::hex_to_tx_blob(tx_hex, tx_blob));
        txs_blobs.push_back(tx_blob);
    }

    for (auto const& tx_blob: txs_blobs)
    {
        mempool_txs_to_return.emplace_back();
        mempool_txs_to_return.back().tx_blob = tx_blob;
    }


    EXPECT_CALL(*mcore_ptr, get_mempool_txs(_, _))
            .WillOnce(DoAll(
                          SetArgReferee<0>(mempool_txs_to_return),
                          Return(true)));

    block mock_blk; // just an empty block

    EXPECT_CALL(*mcore_ptr, get_current_blockchain_height())
            .WillRepeatedly(Return(100));

    bcs->update_current_blockchain_height();

    EXPECT_CALL(*mcore_ptr, get_block_from_height(_, _))
            .WillRepeatedly(DoAll(SetArgReferee<1>(mock_blk),
                            Return(true)));

    EXPECT_CALL(*mcore_ptr, get_transactions(_, _, _))
            .WillRepeatedly(Return(true));

    ASSERT_TRUE(bcs->read_mempool());


    string tx_hash_with_payment;

    EXPECT_TRUE(bcs->search_if_payment_made(expected_payment_id_str,
                                            desired_amount,
                                            tx_hash_with_payment));

    EXPECT_EQ(tx_hash_with_payment, expected_tx_hash_str);

    // desired amount is more than what tx has, so
    // it should be false
    EXPECT_FALSE(bcs->search_if_payment_made(expected_payment_id_str,
                                            desired_amount*2,
                                            tx_hash_with_payment));

//    EXPECT_CALL(*mcore_ptr, get_block_from_height(_, _))
//            .WillRepeatedly(DoAll(SetArgReferee<1>(mock_blk),
//                            Return(false)));



    EXPECT_CALL(*mcore_ptr, get_block_from_height(_, _))
            .WillOnce(Return(false));

    EXPECT_FALSE(bcs->search_if_payment_made(expected_payment_id_str,
                                            desired_amount,
                                            tx_hash_with_payment));

    EXPECT_CALL(*mcore_ptr, get_block_from_height(_, _))
            .WillRepeatedly(DoAll(SetArgReferee<1>(mock_blk),
                            Return(true)));

    EXPECT_CALL(*mcore_ptr, get_transactions(_, _, _))
            .WillRepeatedly(Return(false));

    EXPECT_FALSE(bcs->search_if_payment_made(expected_payment_id_str,
                                            desired_amount,
                                            tx_hash_with_payment));
}

TEST_P(BCSTATUS_TEST, GetOutputKey)
{

    output_data_t output_to_return {
                                crypto::rand<crypto::public_key>(),
                                1000, 2222,
                                crypto::rand<rct::key>()};

    EXPECT_CALL(*mcore_ptr, get_output_key(_, _))
            .WillOnce(Return(output_to_return));


    output_data_t result = bcs->get_output_key(333 /* any */,
                                               444 /* any */);

    EXPECT_EQ(result.pubkey, output_to_return.pubkey);
}

ACTION(MockSearchWhile)
{
    cout << "\nMocking while search in txsearch class\n" << endl;
}

TEST_P(BCSTATUS_TEST, StartTxSearchThread)
{
    xmreg::XmrAccount acc; // empty, mock account

    auto tx_search = std::make_unique<MockTxSearch>();

    EXPECT_CALL(*tx_search, operator_fcall()) // mock operator()
            .WillOnce(MockSearchWhile());

    EXPECT_CALL(*tx_search, get_exception_ptr())
            .WillOnce(Return(nullptr));

    EXPECT_TRUE(bcs->start_tx_search_thread(acc, std::move(tx_search)));

    auto tx_search2 = std::make_unique<MockTxSearch>();

    // trying launching the same thread for already running account
    // should also return true as this is fine
    EXPECT_TRUE(bcs->start_tx_search_thread(acc, std::move(tx_search2)));


    auto tx_search3 = std::make_unique<MockTxSearch>();

    // here we test what happens if TxSearchThread throws some
    // has thrown an execption and std::exception_ptr is not null


    xmreg::XmrAccount acc2; // empty, mock account

    acc2.address = "mock address";

    auto expt_ptr = std::make_exception_ptr(
                std::runtime_error("some exception"));

    EXPECT_CALL(*tx_search3, get_exception_ptr())
            .WillOnce(Return(expt_ptr));

    EXPECT_CALL(*tx_search3, operator_fcall()) // mock operator()
            .WillOnce(MockSearchWhile());

    EXPECT_TRUE(bcs->start_tx_search_thread(acc2, std::move(tx_search3)));

    // wait a bit, just to give time to tx_search mock thread to finish
    // its mocking action
    std::this_thread::sleep_for(1s);

    // cleaning up the search threads should detect that one
    // thread has some exception
    bcs->clean_search_thread_map();

    EXPECT_FALSE(bcs->search_thread_exist(acc2.address));
}

ACTION(MockSearchWhile2)
{
    cout << "\nMocking while search in txsearch class2\n" << endl;
    std::this_thread::sleep_for(1s);
}

TEST_P(BCSTATUS_TEST, GetSearchthreadThrows)
{
    // CurrentBlockchainStatus::get_search_thread will throw
    // exception if it is called for an address that does not exisit.
    EXPECT_THROW(bcs->get_search_thread("whatever adddress"),
                 std::runtime_error);
}

TEST_P(BCSTATUS_TEST, PingSearchThread)
{
    xmreg::XmrAccount acc; // empty, mock account

    acc.address = "whatever mock address";

    auto tx_search = std::make_unique<MockTxSearch>();

    EXPECT_CALL(*tx_search, operator_fcall()) // mock operator()
            .WillOnce(MockSearchWhile2());

    EXPECT_CALL(*tx_search, ping()).WillOnce(Return());

    EXPECT_CALL(*tx_search, still_searching())
            .WillRepeatedly(Return(false));

    ASSERT_TRUE(bcs->start_tx_search_thread(acc, std::move(tx_search)));

    EXPECT_TRUE(bcs->ping_search_thread(acc.address));

    EXPECT_TRUE(bcs->set_new_searched_blk_no(acc.address, 22222));

    while(bcs->search_thread_exist(acc.address))
    {
        cout << "\nsearch thread still exists\n";
        std::this_thread::sleep_for(1s);
        bcs->clean_search_thread_map();
    }

    // once we removed the search thread as it finshed,
    // we should be getting false now

    EXPECT_FALSE(bcs->ping_search_thread(acc.address));

    EXPECT_FALSE(bcs->set_new_searched_blk_no(acc.address, 22222));
}


TEST_P(BCSTATUS_TEST, GetSearchedBlkOutputsAndAddrViewkey)
{
    xmreg::XmrAccount acc; // empty, mock account

    acc.address = "whatever mock address";

    auto tx_search = std::make_unique<MockTxSearch>();

    EXPECT_CALL(*tx_search, operator_fcall()) // mock operator()
            .WillOnce(MockSearchWhile2());

    EXPECT_CALL(*tx_search, get_searched_blk_no())
            .WillOnce(Return(123));

    xmreg::TxSearch::known_outputs_t outputs_to_return;

    outputs_to_return.insert({crypto::rand<crypto::public_key>(), 33});
    outputs_to_return.insert({crypto::rand<crypto::public_key>(), 44});
    outputs_to_return.insert({crypto::rand<crypto::public_key>(), 55});

    EXPECT_CALL(*tx_search, get_known_outputs_keys())
            .WillOnce(Return(outputs_to_return));

    xmreg::TxSearch::addr_view_t mock_address = std::make_pair(
                bcs->get_bc_setup().import_payment_address,
                bcs->get_bc_setup().import_payment_viewkey);

    EXPECT_CALL(*tx_search, get_xmr_address_viewkey())
            .WillRepeatedly(Return(mock_address));

    EXPECT_CALL(*tx_search, still_searching())
            .WillRepeatedly(Return(false));

    ASSERT_TRUE(bcs->start_tx_search_thread(acc, std::move(tx_search)));

    uint64_t searched_blk_no {0};

    EXPECT_TRUE(bcs->get_searched_blk_no(acc.address, searched_blk_no));

    xmreg::TxSearch::known_outputs_t outputs_returned;

    EXPECT_TRUE(bcs->get_known_outputs_keys(acc.address, outputs_returned));

    EXPECT_EQ(outputs_returned.size(), outputs_to_return.size());

    address_parse_info address_returned;
    crypto::secret_key viewkey_returned;

    EXPECT_TRUE(bcs->get_xmr_address_viewkey(acc.address,
                                             address_returned,
                                             viewkey_returned));

    //can compare addresses only as string
    string address_returned_str
            = get_account_address_as_str(GetParam(), false,
                                         address_returned.address);

    string mock_address_str
            = get_account_address_as_str(GetParam(), false,
                                         mock_address.first.address);

    EXPECT_EQ(address_returned_str, mock_address_str);
    EXPECT_EQ(viewkey_returned, mock_address.second);

    while(bcs->search_thread_exist(acc.address))
    {
        cout << "\nsearch thread still exists\n";
        std::this_thread::sleep_for(1s);
        bcs->clean_search_thread_map();
    }

    // once we removed the search thread as it finshed,
    // we should be getting false now

    EXPECT_FALSE(bcs->get_searched_blk_no(acc.address, searched_blk_no));
    EXPECT_FALSE(bcs->get_known_outputs_keys(acc.address, outputs_returned));
    EXPECT_FALSE(bcs->get_xmr_address_viewkey(acc.address,
                                             address_returned,
                                             viewkey_returned));
}

TEST_P(BCSTATUS_TEST, FindTxsInMempool)
{
    xmreg::XmrAccount acc; // empty, mock account

    acc.address = "whatever mock address";

    auto tx_search = std::make_unique<MockTxSearch>();

    nlohmann::json txs_to_return = nlohmann::json::array();

    txs_to_return.push_back(nlohmann::json {"tx_hash1", "some_tx_hash1"});
    txs_to_return.push_back(nlohmann::json {"tx_hash2", "some_tx_hash2"});
    txs_to_return.push_back(nlohmann::json {"tx_hash3", "some_tx_hash3"});


    vector<string> txs_to_return_json_str;

    for (auto& tx_json: txs_to_return)
        txs_to_return_json_str.push_back(tx_json.dump());

    EXPECT_CALL(*tx_search, mock_find_txs_in_mempool(_,_))
            .WillRepeatedly(SetArgReferee<1>(txs_to_return_json_str));

    EXPECT_CALL(*tx_search, operator_fcall()) // mock operator()
            .WillRepeatedly(MockSearchWhile2());

    ASSERT_TRUE(bcs->start_tx_search_thread(acc, std::move(tx_search)));

    nlohmann::json txs;

    EXPECT_TRUE(bcs->find_txs_in_mempool(acc.address, txs));

    while(bcs->search_thread_exist(acc.address))
    {
        cout << "\nsearch thread still exists\n";
        std::this_thread::sleep_for(1s);
        bcs->clean_search_thread_map();
    }

    // once we removed the search thread as it finshed,
    // we should be getting false now
    EXPECT_FALSE(bcs->find_txs_in_mempool(acc.address, txs));
}

// returns some mock txs in hex or blob form
vector<string>
get_mock_txs_as_str(bool as_blob = false)
{
     vector<string> txs_hex;

     // tx_hash 66d2f912a652a670b5fe017144e5d49c06d3d0a936cc856a6800e917a7a0454d
     txs_hex.emplace_back("[encoded data, 26502 characters]");

     // tx_hash 8a7e09ae4bc4d85153ff85350da82d076461db61e94b321fb727a0fcdd96e8e6
     // this tx should have 0.1 xmr for stagnet wallet 53mqDDKtVkib8inMa41HuNJG4tj9CcaNKGr6EVSbvhWGJdpDQCiNNYBUNF1oDb8BczU5aD68d3HNKXaEsPq8cvbQE2FBkTS
     // viewkey: 53c5850e895122574c53a4f952c726be3fe22bcd2b08f4bfed8946d887cc950b
     // payment_id8: 1f5d434f0b34448b
     txs_hex.emplace_back("[encoded data, 27584 characters]");

     // tx_hash ac715b386010bd95a506fde0ac0405aa8ad72080b1d7cef257b5b112d9ed84bb
     // this tx has key_image: c0b2a4909185084c7113de20415c3b7545e5d4f0cc5b5fd21cd1d297ad1286ed
     txs_hex.emplace_back("[encoded data, 42122 characters]");

     // tx_hash dd081e09690c118429ccd773feb83b6d9668b38344f2f66cde06f087df720217
     txs_hex.emplace_back("02000102000bc1d802ddbf1081c20d9bc804d540a920d22b9105c4018e02e912bc7f3eebe5034123307ee4f08bc6f21799e863274d5757f64aaddc8fd34b101e02000227d1efcd0e6c502e5b6d6f1b6e492fb5f8cae10d93c45054835609d2effbd51a0002cee2cc19a8ef3517cc2c36d9b35d69dd540b691955d29306f457fac71b031e4f2101dd64455be994a89712f6a05d0d87bd75e1a829dfd19ad6ceb46134b54d0afae203b08ba09901503704fbaf6ced7287fcfcba3457b51206921eba3dce2780376081e6cdfaab0570161ba30da57ee3f4571e502f4b9d98aed0f68cfeef55e38833df3da7fa5006182435a6c1749e074bc967409b3c2999e52e0301b1250370d6138d9420c49500983a83a89108ede1504ac07643bfc8a2d9d11c07dbe89f4a882d27a4f10b3a025491dde515575a061f7dc53e2a9eeb45c63945125ec5e914a9cc661cb590b73a229d45c37af78cfbbe14d684769b8508c2186fbd3468e5a2fa5e1b448529e8d401000000c0e73f2c27fbb652e0885034ff9e0adb5358933bc0ce722d1dd320520df16d247b121d7b13e7a99516693e42f31ed6c2e4890c0ba35b6def80ecea40de70edbfc0efe4d6c10bdca3b9fb0259195d1e6348fd01211f20a35c18bca03ae3043694f75885a4b0785ee97706a6ed2605b49a1cb30addeb8c6b7e8314f17d18672a244e8fee0c25ef7c097d9014c51402d2851f1db7af2d9e3b552a8ba82bacc55b07a8f39547f3679de045aa1728a3cede6b1962891f84d5f1c37d0e3ac025301b00078f1051102053860be971f33b6faa4b7bf51eb7ee23415ef5c61765b806d7abc39889cf1a53a0b53908522a0bd69093120f272a6e171906ec65a852c7b63307b9be5ed4e3b0615853e20e34f3e48eb3535e8bb9a5b554a60b9f2c1d614f912c322bea1857b12c9dcc4ff776dba6677460f73c4ceec551f198bd17713a4653ef6a57958d3025fc341ff4a60dfb22d6dba90de7c44609af818bb72ec2babce6c279f780d514d4ef0008e216e2c43356e8cdfb1ca0994261bd6c7566c48c4f8275588088b6988e202c1174c5774c319dd8b94f2eb9d70653b4dca1486cf1200ce0d507e895b3909a24b2f3f0e07e2fd92d1f0f6a7a4ccac01c1d6a3fb54999ce8f3b3b57734a43dc8e899162e423e009bb3c6ff036eba67eb31512b080ff299b10c5d7812a658a4800484392aa6949adc6a981302379d359c7de76ec2b0fa6a18c94978d5df6847ece631b5e5178a8ac5e09460a7a3d40af02856721873749a075d42839fb68fc7358c48c4c1851c3088a92524534d4a31edc6437ad5b691ddec586bb62fbc8ee2e2e2b9117dc582f8bf9f6850d6884974ba5d324ba059e4049ddd0d5f8e5f02e9231f112aef0d6e53355ff58e589418dc2a05e0ae72833f0925efa00ad9b34fae89fd2436350e641b02b98d762d08e6321e05ef013d5c593be4d490a85b15bdd55bdefc2f08d663111ed3c204de71fa4c0ce0963d53c10398b53360fe4850e0ce66d802c7d6edfbffc87db5a569810637e66ba27bcb87105f1ee240d74fbf27669b223ed852f39c9f1da078ea5358acaef594cd42169f9cfefa1dc09f311f09b1be6ed06c96417c94f0a35f877a0d4101aef5c37ec1fcc541dbca90b5912a90bbe9fdf92f4e82ad4644d833feccec1df80c04c7c15b52e4675554e0f10db740575d8eb93f871fcd9ee1cf96f9055cba321ed062d74ea64a091f85f07e908696b972024a76b475023aabe7cd6dc885d85e6cf939cb613e29f5c688800ffe4b47966debc27772932f27bfcdcca0cdf9f2311ab8b9466237656c6c947075cb8c6f3d40ec1304142100b2753d9e7a26469140d02ed5f90cbc4d09e9109028d0038ecff3ba19c92b30d02b5cedbe3573135debacc8e0f67b3801bda3ebe0b99981f98f2db463f679efee14ca8e826b22b34355d4a3b529f154c16d9eaf108b49727cc99faa75e679706f3da45e5615f53ddc4eade8e043c6382bc32cbec0dc6c22646296bb9df3785103ae4beb4cc89c4e70d218dc07117858028a932500134d482ee1038cd546ddb25bdd0108051896ed4cf87347b44f85d7e2f442825090a1dd18201fc4203c906c7628f403cfcebfc2aebe9c915b1afaf925650927200663d9a356e4e1b5f11963cc607a2abcf05f90227e27f119adad43ea64a44910568409004a500e58fc746c3cfd52e6386a2931d6d5971c1ace95b0cae6707d608cb4d756b3e466fcd11b39c25cf969dc2c67f1af31649ae69f04ea74bda4e4d0bd2970cdbbb450b7f42e96e50210c52af054fcfdd1f49d359ccead3ef3dff2d0eaa54849b3916476019678961a2aae55a26242c59767723bcd87ab944fee52003c4f5dc73d314435c7c5b4c7c3c768ada45328721144dd35c2ae6a44cd97b0006f6312dc281e4806c1a4827d543791b362341bcad419a0ba1ccee97d4e878110d420958ef3b15a49f5f210ac8971ef352e5bd13b2bfb7417cc8c67bec57fb990a88fd766c29cddbfefe7ca198c092bb886a390016026612159c51c2cd9d03570f1c1186ca6b0a61e7210a2548f42bdc0b5ff6ba87d4e9cd4aa6f090dd06b3a207b3298f532abb82e8c328bff6f158191dfdf34e2367dd0d8b6027119325e2b384");

     // tx_hash 6aa5057aeccac8e1bf632eaa813554352b2f5a4b6bd02f920a44cf3081488a2f
     // this tx should have 0.1 xmr for testnet wallet (monerowallettestnet7)
     // 9tzmPMTViHYM3z6NAgQni1Qm1Emzxy5hQFibPgWD3LVTAz91yok5Eni1pH6zKhBHzpTU15GZooPHSGHXFvFuXEdmEG2sWAZ
     // viewkey: ae6184287ca791609844f140b8502ccfa2223c04c8699cf31fcd0af1f1d0be08
     // payment_id8: 6c224eea64220435
     txs_hex.emplace_back("[encoded data, 26136 characters]");

     // tx_hash 84ec08256fe54f3fbbe8dd109a177a35cd1d7788c1cfdf9074684ffb7b058a62
     txs_hex.emplace_back("[encoded data, 5388 characters]");

     //tx hash 1be093bf3e0d004bd5bf3a121e812fbc0f5e1230bb33b2eb8d9456345afea19f
     // also mine
     txs_hex.emplace_back("029ca54101ffe0a4410186d08b88c5f101028d49e37bb8c1ae92cbcd928aa89494121be17e065f0d26427c165eda1cefdded2b018e774da8f7f5fb0214a235bd20b8f76271591dbf7de16f76d0085a5563114c5a02080000000397a47a0000");

     //tx hash 8a0f4995709a67d5a175f4a435f6c3aa8fd5a0305e82484b4db2f9ef1df9b611
     // second tx which has payment_id8 (7f4cd286ecadddce) for us
     // tx has key_image: ??
     txs_hex.emplace_back("[encoded data, 26140 characters]");

     if (as_blob)
     {
         vector<string> txs_blobs;

         for (auto const& tx_hex: txs_hex)
         {
             string tx_blob;
             if (!xmreg::hex_to_tx_blob(tx_hex, tx_blob))
             {
                cerr << "Failed converting tx_hex to tx_blob\n";
                return {};
             }

             txs_blobs.push_back(tx_blob);
         }

         return  txs_blobs;
     }


     return txs_hex;
}

TEST_P(BCSTATUS_TEST, FindTxsInMempool2)
{
    vector<tx_info> mempool_txs_to_return;

    vector<string> txs_blobs = get_mock_txs_as_str(true);

    ASSERT_FALSE(txs_blobs.empty());

    transaction selected_tx;
    crypto::hash tx_hash;
    crypto::hash tx_prefix_hash;

    ASSERT_TRUE(parse_and_validate_tx_from_blob(
                    txs_blobs.at(2), selected_tx, tx_hash, tx_prefix_hash));

    for (auto const& tx_blob: txs_blobs)
    {
        mempool_txs_to_return.emplace_back();
        mempool_txs_to_return.back().tx_blob = tx_blob;
    }

    EXPECT_CALL(*mcore_ptr, get_mempool_txs(_, _))
            .WillRepeatedly(DoAll(
                          SetArgReferee<0>(mempool_txs_to_return),
                          Return(true)));

    ASSERT_TRUE(bcs->read_mempool());

    transaction tx;

    EXPECT_TRUE(bcs->find_tx_in_mempool(tx_hash, tx));

    EXPECT_EQ(tx_hash, get_transaction_hash(tx));


    crypto::hash non_existing_tx_hash = crypto::rand<crypto::hash>();

    EXPECT_FALSE(bcs->find_tx_in_mempool(non_existing_tx_hash, tx));
}

TEST_P(BCSTATUS_TEST, FindKeyImagesInMempool)
{
    vector<tx_info> mempool_txs_to_return;

    vector<string> txs_blobs = get_mock_txs_as_str(true);

    for (auto const& tx_blob: txs_blobs)
    {
        mempool_txs_to_return.emplace_back();
        mempool_txs_to_return.back().tx_blob = tx_blob;
    }

    EXPECT_CALL(*mcore_ptr, get_mempool_txs(_, _))
            .WillRepeatedly(DoAll(
                          SetArgReferee<0>(mempool_txs_to_return),
                          Return(true)));

    ASSERT_TRUE(bcs->read_mempool());


    vector<string> key_images_to_find_str {
        "18fff454382da01b4420cd666dc5f3101314537de00d62042206d4b393bf918a",
        "c0b2a4909185084c7113de20415c3b7545e5d4f0cc5b5fd21cd1d297ad1286ed"};

    vector<txin_v> key_images_to_find;

    for (auto& ki_str: key_images_to_find_str)
    {
        key_images_to_find.push_back(txin_to_key{});
        hex_to_pod(ki_str,
                   boost::get<txin_to_key>(key_images_to_find.back()).k_image);
    }


    EXPECT_TRUE(bcs->find_key_images_in_mempool(key_images_to_find));

    // now do same but using tx as input, instead of vector of key images

    transaction tx;
    crypto::hash tx_hash;
    crypto::hash tx_prefix_hash;

    parse_and_validate_tx_from_blob(
                mempool_txs_to_return[1].tx_blob,
                tx, tx_hash, tx_prefix_hash);

    EXPECT_TRUE(bcs->find_key_images_in_mempool(tx));

    // and now check what happens if key images are not in the mempool

    // we change just last character in the key images, thus
    // making them different with those in the mempool
    vector<string> key_images_to_find_str2 {
        "18fff454382da01b4420cd666dc5f3101314537de00d62042206d4b393bf918h",
        "c0b2a4909185084c7113de20415c3b7545e5d4f0cc5b5fd21cd1d297ad1286e2"};

    vector<txin_v> key_images_to_find2;

    for (auto& ki_str: key_images_to_find_str2)
    {
        key_images_to_find2.push_back(txin_to_key{});
        hex_to_pod(ki_str,
                   boost::get<txin_to_key>(key_images_to_find2.back()).k_image);
    }


    EXPECT_FALSE(bcs->find_key_images_in_mempool(key_images_to_find2));
}

TEST_P(BCSTATUS_TEST, ConstructOutputRctField_RingCTTx)
{
    // testnet tx_hash 4942dbb5421c8516c05473f3cf191f07e419109fbdaf22bfcc7f8e95a0e73bf2
    string ringct_tx_hex {"[encoded data, 27046 characters]"};

    TX_FROM_HEX(ringct_tx_hex);

    const tx_out_index tx_idx_to_return = make_pair(tx_hash,
                                                    1 /* output idx */);

    EXPECT_CALL(*mcore_ptr, get_output_tx_and_index(_, _))
            .WillRepeatedly(Return(tx_idx_to_return));

    uint64_t mock_global_amount_index {33333}; // any index
    uint64_t mock_out_amount {0}; // any amount

    // first we check if get_tx returns false when it
    // cant locate the txs that we need
    EXPECT_CALL(*mcore_ptr, get_tx(_, _)).WillOnce(Return(false));

    auto rct_fields = bcs->construct_output_rct_field(
                                mock_global_amount_index,
                                mock_out_amount);

    EXPECT_TRUE(std::get<0>(rct_fields).empty());
    EXPECT_TRUE(std::get<1>(rct_fields).empty());
    EXPECT_TRUE(std::get<2>(rct_fields).empty());

    // now get_tx will return true and the tx that we want
    EXPECT_CALL(*mcore_ptr, get_tx(_, _))
            .WillOnce(DoAll(SetArgReferee<1>(tx), Return(true)));

    rct_fields = bcs->construct_output_rct_field(
                              mock_global_amount_index,
                              mock_out_amount);

    string expected_rtc_outpk {"cc9f9fcddc3a8fde9d7ad923b122eeb783d54f4c4c6712d32de661d7f9c35473"};
    string expected_rtc_mask {"4e3b4564048f50151f79dd73f17b97ea9cd2b64ac585ecc6fa95e8d4eb68ef07"};
    string expected_rtc_amount {"3a724b8f8b6e2c321a2d67e82b2625cffdb90eeec533fb3f04db7c55bf703807"};

    EXPECT_EQ(std::get<0>(rct_fields), expected_rtc_outpk);
    EXPECT_EQ(std::get<1>(rct_fields), expected_rtc_mask);
    EXPECT_EQ(std::get<2>(rct_fields), expected_rtc_amount);
}

TEST_P(BCSTATUS_TEST, ConstructOutputRctField_LegacyTx)
{
    // mainnet tx_hash a898e407e3427200f8356e1d33d08023aa1440e01b2c2ce2fa9dfd5b7fc89c88
    string ringct_tx_hex {"0100020280a0d9e61d018f90012489c0dd35d368c61d8bf7b692054cf2ecdf1d5e4c985b140a5afdf66525a97f0280c0dfda8ee90601a80cb030275216f7958e0c3a17cf42d8b1e8b17f1a5703b63bef978dea03f87cd62d048088aca3cf0202a65b1dd8cfe0e41e15af75dd0c1d18c26371e51ec9f293af5b909a0a5fa59be180d0b8e1981a02186734d4374cd227a0f7a65daeeae275828cf3c72fb5d93e1184322dfff394b580a0b6cef7850202958ecb9dee140f74ea0605f265d231261dec5e58c8a89975719690c50cb4cfaf808095e789c60402b9b075493ef432e589ca2a7dc63ecbee7b9e05bcd416596ed0498c7802f71c9e44022100462e3cdfa219d8724fb531eb7e741a65f6802546aed7ff7064236e038421b32801d33c3170a4d9417f13e2e3a83c5241d3383de20b3913a6d295d6e8cc5322df862b571136213f79ea1b834b361fec3d8eb4d297fc007f24e27a00d76c4716b6080db448368abdcaf65267d099de23c796f868ebe8eb6c91829258222abee147026144104a8e3233d953f68d265f8d15f8b15deddf5e1eedfc4302139f30160e08552888d925706c48f0f1f205e4dacf4ab314cca13819359e109094bc3c8d7707"};

    // this tx is not realy used in this case for checking expectaions.
    // we only have it so that we can get to the else section
    // in the if statment in construct_output_rct_field

    TX_FROM_HEX(ringct_tx_hex);

    const tx_out_index tx_idx_to_return = make_pair(tx_hash,
                                                    1 /* output idx */);

    EXPECT_CALL(*mcore_ptr, get_output_tx_and_index(_, _))
            .WillRepeatedly(Return(tx_idx_to_return));

    uint64_t mock_global_amount_index {33333}; // any index
    uint64_t mock_out_amount {0}; // any amount

    // now get_tx will return true and the tx that we want
    EXPECT_CALL(*mcore_ptr, get_tx(_, _))
            .WillOnce(DoAll(SetArgReferee<1>(tx), Return(true)));


    // for legact txs we only need their outputs and commitment
    // thus only rtc_outpk will be set in tuple produced
    // by construct_output_rct_field
    // since we mock eveyrhting, the commitment returned can
    // be random.

    output_data_t output_to_return {
                crypto::rand<crypto::public_key>(),
                1000, 2222,
                crypto::rand<rct::key>() /* commitment */};

    EXPECT_CALL(*mcore_ptr, get_output_key(_, _))
            .WillOnce(Return(output_to_return));

    auto rct_fields = bcs->construct_output_rct_field(
                              mock_global_amount_index,
                              mock_out_amount);

    string expected_rtc_outpk = pod_to_hex(output_to_return.commitment);
    string expected_rtc_mask(64, '0'); // expected to be zero
    string expected_rtc_amount(64, '0'); // expected to be zero

    EXPECT_EQ(std::get<0>(rct_fields), expected_rtc_outpk);
    EXPECT_EQ(std::get<1>(rct_fields), expected_rtc_mask);
    EXPECT_EQ(std::get<2>(rct_fields), expected_rtc_amount);
}

vector<string>
get_blocks_hex()
{
    vector<string> blks;

    // monero donation address
    // 44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A
    // f359631075708155cc3d92a32b75a7d02a5dcf27756707b47a2b31b21c389501

    // some of the txs in the blocks below have xmr for the above address

    // complete block hex

    // mainnet 1031380
    blks.emplace_back("[encoded data, 29672 characters]");

    // mainet 1623891
    blks.emplace_back("[encoded data, 354626 characters]");

    // mainet 1633437
    blks.emplace_back("[encoded data, 56694 characters]");

    // mainet
    //blks.emplace_back("");

    return blks;
}


TEST_P(BCSTATUS_TEST, InitTxsDataVector )
{
    // complete blocks string as hex
    vector<string> cblks_str = get_blocks_hex();

    vector<block_complete_entry> cblks;

    ASSERT_TRUE(xmreg::hex_to_complete_block(cblks_str, cblks));

    vector<block> blocks;
    vector<transaction> txs_to_return;

    ASSERT_TRUE(xmreg::blocks_and_txs_from_complete_blocks(
                    cblks, blocks, txs_to_return));

    crypto::hash expected_first_hash
            = get_transaction_hash(blocks.front().miner_tx);
    crypto::hash expected_last_hash
            = blocks.back().tx_hashes.back();

    vector<crypto::hash> txs_to_get;
    vector<xmreg::CurrentBlockchainStatus::txs_tuple_t> txs_data;

    bcs->init_txs_data_vector(blocks, txs_to_get, txs_data);

    EXPECT_EQ(txs_to_get.size(), txs_to_return.size());
    EXPECT_EQ(txs_to_get.front(), expected_first_hash);
    EXPECT_EQ(txs_to_get.back(), expected_last_hash);
}

TEST_P(BCSTATUS_TEST, GetTxsInBlocks)
{
    // complete blocks string as hex
    vector<string> cblks_str = get_blocks_hex();

    vector<block_complete_entry> cblks;

    ASSERT_TRUE(xmreg::hex_to_complete_block(cblks_str, cblks));

    vector<block> blocks;
    vector<transaction> txs_to_return;

    ASSERT_TRUE(xmreg::blocks_and_txs_from_complete_blocks(
                    cblks, blocks, txs_to_return));

    crypto::hash expected_first_hash
            = get_transaction_hash(blocks.front().miner_tx);
    crypto::hash expected_last_hash
            = blocks.back().tx_hashes.back();


    vector<crypto::hash> missed_txs;

    EXPECT_CALL(*mcore_ptr, get_transactions(_, _, _))
            .WillOnce(DoAll(SetArgReferee<1>(txs_to_return),
                            SetArgReferee<2>(missed_txs),
                            Return(true)));

    vector<crypto::hash> txs_hashes;
    vector<transaction> txs;
    vector<xmreg::CurrentBlockchainStatus::txs_tuple_t> txs_data;

    EXPECT_TRUE(bcs->get_txs_in_blocks(blocks, txs_hashes, txs, txs_data));

    EXPECT_EQ(txs, txs_to_return);
    EXPECT_EQ(txs_hashes.front(), expected_first_hash);
    EXPECT_EQ(txs_hashes.back(), expected_last_hash);
    EXPECT_EQ(is_coinbase(txs.front()), true);

    // now if there are some missing txs

    // some missing tx hash
    missed_txs.push_back(crypto::rand<crypto::hash>());
    missed_txs.push_back(crypto::rand<crypto::hash>());

    EXPECT_CALL(*mcore_ptr, get_transactions(_, _, _))
            .WillOnce(DoAll(SetArgReferee<1>(txs_to_return),
                            SetArgReferee<2>(missed_txs),
                            Return(true)));

    EXPECT_FALSE(bcs->get_txs_in_blocks(blocks, txs_hashes,
                                        txs, txs_data));
}

TEST_P(BCSTATUS_TEST, MonitorBlockchain)
{
    uint64_t mock_current_height {1619148};

    EXPECT_CALL(*mcore_ptr, get_current_blockchain_height())
            .WillRepeatedly(Return(mock_current_height));

    // stagenet tx: 4b40cfb2fdce2cd57a834a380901d55d70aba29dad13ac6c4dc82a895f439ecf
    const string tx_4b40_hex {"[encoded data, 26504 characters]"};

    std::string tx_blob;

    ASSERT_TRUE(xmreg::hex_to_tx_blob(tx_4b40_hex, tx_blob));

    vector<tx_info> mempool_txs_to_return;

    mempool_txs_to_return.push_back(tx_info{});

    mempool_txs_to_return.back().tx_blob = tx_blob;

    EXPECT_CALL(*mcore_ptr, get_mempool_txs(_, _))
            .WillRepeatedly(DoAll(
                          SetArgReferee<0>(mempool_txs_to_return),
                          Return(true)));

    // set refresh rate to 1 second as we dont wont to wait long
    xmreg::BlockchainSetup bs = bcs->get_bc_setup();
    bs.refresh_block_status_every = 1s;
    bcs->set_bc_setup(bs);
    bcs->is_running = false;

    auto thread_function = std::bind(
                &xmreg::CurrentBlockchainStatus::monitor_blockchain,
                bcs.get());

    {
        xmreg::ThreadRAII blockchain_monitoring_thread(
                    std::thread(thread_function),
                    xmreg::ThreadRAII::DtorAction::join);

        // wait few seconds so that several loops are done
        std::this_thread::sleep_for(4s);

        bcs->stop_blockchain_monitor_loop = true;
    }

    while(bcs->is_running)
    {
        cout << "Waiting for blockchain_monitoring_thread to stop"
             << endl;
        std::this_thread::sleep_for(1s);
    }

    EXPECT_EQ(bcs->get_mempool_txs().size(), 1);

}


INSTANTIATE_TEST_CASE_P(
        DifferentMoneroNetworks, BCSTATUS_TEST,
        ::testing::Values(
                network_type::MAINNET,
                network_type::TESTNET,
                network_type::STAGENET));

}
