
"""Blockchaininterface functionality tests."""

import binascii
from commontest import create_wallet_for_sync

import pytest
from jmbase import get_log
from jmclient import load_test_config, jm_single, BaseWallet

log = get_log()

pytestmark = pytest.mark.usefixtures("setup_regtest_bitcoind")


def sync_test_wallet(fast, wallet_service):
    sync_count = 0
    wallet_service.synced = False
    while not wallet_service.synced:
        wallet_service.sync_wallet(fast=fast)
        sync_count += 1
        # avoid infinite loop
        assert sync_count < 10
        log.debug("Tried " + str(sync_count) + " times")


@pytest.mark.parametrize('fast', (False, True))
def test_empty_wallet_sync(setup_wallets, fast):
    wallet_service = create_wallet_for_sync([0, 0, 0, 0, 0], ['test_empty_wallet_sync'])

    sync_test_wallet(fast, wallet_service)

    broken = True
    for md in range(wallet_service.max_mixdepth + 1):
        for internal in (BaseWallet.ADDRESS_TYPE_INTERNAL,
                         BaseWallet.ADDRESS_TYPE_EXTERNAL):
            broken = False
            assert 0 == wallet_service.get_next_unused_index(md, internal)
    assert not broken


@pytest.mark.parametrize('fast,internal', (
        (False, BaseWallet.ADDRESS_TYPE_EXTERNAL),
        (False, BaseWallet.ADDRESS_TYPE_INTERNAL),
        (True, BaseWallet.ADDRESS_TYPE_EXTERNAL),
        (True, BaseWallet.ADDRESS_TYPE_INTERNAL)))
def test_sequentially_used_wallet_sync(setup_wallets, fast, internal):
    used_count = [1, 3, 6, 2, 23]
    wallet_service = create_wallet_for_sync(
        used_count, ['test_sequentially_used_wallet_sync'],
        populate_internal=internal)

    sync_test_wallet(fast, wallet_service)

    broken = True
    for md in range(len(used_count)):
        broken = False
        assert used_count[md] == wallet_service.get_next_unused_index(md, internal)
    assert not broken


@pytest.mark.parametrize('fast', (False,))
def test_gap_used_wallet_sync(setup_wallets, fast):
    """ After careful examination this test now only includes the Recovery sync.
    Note: pre-Aug 2019, because of a bug, this code was not in fact testing both
    Fast and Recovery sync, but only Recovery (twice). Also, the scenario set
    out in this test (where coins are funded to a wallet which has no index-cache,
    and initially no imports) is only appropriate for recovery-mode sync, not for
    fast-mode (the now default).
    """
    used_count = [1, 3, 6, 2, 23]
    wallet_service = create_wallet_for_sync(used_count, ['test_gap_used_wallet_sync'])
    wallet_service.gap_limit = 20

    for md in range(len(used_count)):
        x = -1
        for x in range(md):
            assert x <= wallet_service.gap_limit, "test broken"
            # create some unused addresses
            wallet_service.get_new_script(md, BaseWallet.ADDRESS_TYPE_INTERNAL)
            wallet_service.get_new_script(md, BaseWallet.ADDRESS_TYPE_EXTERNAL)
        used_count[md] += x + 2
        jm_single().bc_interface.grab_coins(wallet_service.get_new_addr(md,
                                        BaseWallet.ADDRESS_TYPE_INTERNAL), 1)
        jm_single().bc_interface.grab_coins(wallet_service.get_new_addr(md,
                                        BaseWallet.ADDRESS_TYPE_EXTERNAL), 1)

    # reset indices to simulate completely unsynced wallet
    for md in range(wallet_service.max_mixdepth + 1):
        wallet_service.set_next_index(md, BaseWallet.ADDRESS_TYPE_INTERNAL, 0)
        wallet_service.set_next_index(md, BaseWallet.ADDRESS_TYPE_EXTERNAL, 0)
    sync_test_wallet(fast, wallet_service)

    broken = True
    for md in range(len(used_count)):
        broken = False
        assert md + 1 == wallet_service.get_next_unused_index(md,
                                            BaseWallet.ADDRESS_TYPE_INTERNAL)
        assert used_count[md] == wallet_service.get_next_unused_index(md,
                                            BaseWallet.ADDRESS_TYPE_EXTERNAL)
    assert not broken


@pytest.mark.parametrize('fast', (False,))
def test_multigap_used_wallet_sync(setup_wallets, fast):
    """ See docstring for test_gap_used_wallet_sync; exactly the
    same applies here.
    """
    start_index = 5
    used_count = [start_index, 0, 0, 0, 0]
    wallet_service = create_wallet_for_sync(used_count, ['test_multigap_used_wallet_sync'])
    wallet_service.gap_limit = 5

    mixdepth = 0
    for w in range(5):
        for x in range(int(wallet_service.gap_limit * 0.6)):
            assert x <= wallet_service.gap_limit, "test broken"
            # create some unused addresses
            wallet_service.get_new_script(mixdepth,
                                        BaseWallet.ADDRESS_TYPE_INTERNAL)
            wallet_service.get_new_script(mixdepth,
                                        BaseWallet.ADDRESS_TYPE_EXTERNAL)
        used_count[mixdepth] += x + 2
        jm_single().bc_interface.grab_coins(wallet_service.get_new_addr(
            mixdepth, BaseWallet.ADDRESS_TYPE_INTERNAL), 1)
        jm_single().bc_interface.grab_coins(wallet_service.get_new_addr(
            mixdepth, BaseWallet.ADDRESS_TYPE_EXTERNAL), 1)

    # reset indices to simulate completely unsynced wallet
    for md in range(wallet_service.max_mixdepth + 1):
        wallet_service.set_next_index(md, BaseWallet.ADDRESS_TYPE_INTERNAL, 0)
        wallet_service.set_next_index(md, BaseWallet.ADDRESS_TYPE_EXTERNAL, 0)

    sync_test_wallet(fast, wallet_service)

    assert used_count[mixdepth] - start_index == \
           wallet_service.get_next_unused_index(mixdepth,
                        BaseWallet.ADDRESS_TYPE_INTERNAL)
    assert used_count[mixdepth] == wallet_service.get_next_unused_index(
        mixdepth, BaseWallet.ADDRESS_TYPE_EXTERNAL)


@pytest.mark.parametrize('fast', (False, True))
def test_retain_unused_indices_wallet_sync(setup_wallets, fast):
    used_count = [0, 0, 0, 0, 0]
    wallet_service = create_wallet_for_sync(used_count,
                    ['test_retain_unused_indices_wallet_sync'])

    for x in range(9):
        wallet_service.get_new_script(0, BaseWallet.ADDRESS_TYPE_INTERNAL)

    sync_test_wallet(fast, wallet_service)

    assert wallet_service.get_next_unused_index(0,
                    BaseWallet.ADDRESS_TYPE_INTERNAL) == 9


@pytest.mark.parametrize('fast', (False, True))
def test_imported_wallet_sync(setup_wallets, fast):
    used_count = [0, 0, 0, 0, 0]
    wallet_service = create_wallet_for_sync(used_count, ['test_imported_wallet_sync'])
    source_wallet_service = create_wallet_for_sync(used_count, ['test_imported_wallet_sync_origin'])

    address = source_wallet_service.get_internal_addr(0)
    wallet_service.import_private_key(0, source_wallet_service.get_wif(0, 1, 0))
    txid = binascii.unhexlify(jm_single().bc_interface.grab_coins(address, 1))

    sync_test_wallet(fast, wallet_service)

    assert wallet_service._utxos.have_utxo(txid, 0) == 0


@pytest.fixture(scope='module')
def setup_wallets():
    load_test_config()
    jm_single().bc_interface.tick_forward_chain_interval = 1
