import test_config as config
from simnet import Simnet
from tools import timethis
import time
import grpc
from grpc._channel import _Rendezvous

DELAY = 5


def network_up():
    simnet = Simnet(address='localhost:10001', name='simnet', number_of_nodes=config.NUMBER_OF_NODES, port_forwarding_conf=20000)
    simnet.simnet_work_env()
    simnet.kernel_new(autostart=True)

    nodes = []
    for index in range(0, config.NUMBER_OF_NODES):
        node = simnet.lnd_new('node-{}'.format(index), autostart=True)
        nodes.append(node)

    return simnet, nodes


def network_down(simnet, nodes):
    for node in nodes:
        try:
            simnet.lnd_destroy(node, autostop=True)
        except _Rendezvous:
            print('Node is already stoped')

    simnet.kernel_destroy(autostop=True)
    simnet.simnet_destroy()


def wallet_balance(simnet, nodes):
    simnet.core.send_to_address(nodes[0].bitcoin_address, 10,
                                unlock_wallet=True)
    for index in range(0, len(nodes)):
        if index == len(nodes) - 1:
            simnet.core.send_to_address(nodes[index].bitcoin_address, 10,
                                    generate_blocks=True, delay=True)
        else:
            simnet.core.send_to_address(nodes[index].bitcoin_address, 10)


def channel_balance(nodes):
    for index in range(0, len(config.CHANNELS)):
        if index == 0:
            nodes[config.CHANNELS[index][0]].connect(nodes[config.CHANNELS[index][1]])
            time.sleep(DELAY)
            response = nodes[config.CHANNELS[index][0]].open_channel_sync(lnd=nodes[config.CHANNELS[index][1]],
                                                                          local_funding_amount=config.CHANNELS[index][2])
            config.CHANNELS[index][3] = response.funding_txid
        else:
            nodes[config.CHANNELS[index][0]].connect(nodes[config.CHANNELS[index][1]])
            time.sleep(DELAY)
            response = nodes[config.CHANNELS[index][0]].open_channel_sync(lnd=nodes[config.CHANNELS[index][1]],
                                                                   local_funding_amount=config.CHANNELS[index][2],
                                                                   generate_blocks=True, delay=True)
            config.CHANNELS[index][3] = response.funding_txid


def check_transactions(nodes, transactions):
    balances = []
    print('Will check transactions')
    for i in range(0, config.NUMBER_OF_NODES):
        try:
            balance = nodes[i].channel_balance()
        except _Rendezvous:
            balance = 0
        balances.append(balance)

    for transaction in transactions:
        try:
            print('Before transaction from {} to {} for {} Satoshi: '.format(transaction[0], transaction[1], transaction[2]))
            print(nodes[transaction[0]].channel_balance(), balances[transaction[0]])
            print(nodes[transaction[1]].channel_balance(), balances[transaction[1]])
            print('Channels of {}'.format(transaction[0]))
            print(nodes[transaction[0]].list_channels())
            print('Channels of {}'.format(transaction[1]))
            print(nodes[transaction[1]].list_channels())
            nodes[transaction[0]].multiple_payment(nodes[transaction[1]], transaction[2], quantity=1)
            time.sleep(1)
            print('After transaction:')
            print(nodes[transaction[0]].channel_balance())
            print(nodes[transaction[1]].channel_balance())
            if transaction[3]:
                assert nodes[transaction[0]].channel_balance() == balances[transaction[0]] - transaction[2]
                assert nodes[transaction[1]].channel_balance() == balances[transaction[1]] + transaction[2]
                balances[transaction[0]] = nodes[transaction[0]].channel_balance()
                balances[transaction[1]] = nodes[transaction[1]].channel_balance()
            else:
                assert nodes[transaction[0]].channel_balance() == balances[transaction[0]]
                assert nodes[transaction[1]].channel_balance() == balances[transaction[1]]
                raise Exception('Method has passed but shouldn\'t')
        except _Rendezvous:
            print("DEBUG:", transaction)
            if transaction[3]:
                raise Exception('Method hasn\'t passed but shoul\'t')
            print('_Rendezvous')


@timethis
def tests_1(nodes):
    print('------------------         1         ------------------')
    print('Starting first pack of tests: different transactions')

    check_transactions(nodes, config.FIRST_TRANSACTIONS)


@timethis
def tests_2(nodes):
    print('------------------         2         ------------------')
    print('Starting second pack of tests: create channels between nodes from isolated subsets')

    print(config.ADITIONAL_CHANNELS)
    for channel in config.ADITIONAL_CHANNELS:
        print('Connectiong node {} to node {} with channel of {} Satoshi'.format(channel[0],
                                                                                 channel[1], channel[2]))
        nodes[channel[0]].connect(nodes[channel[1]])
        time.sleep(DELAY)
        nodes[channel[0]].open_channel_sync(lnd=nodes[channel[1]], local_funding_amount=channel[2],
                                            generate_blocks=True, delay=True)

    check_transactions(nodes, config.SECOND_TRANSACTIONS)


@timethis
def tests_3(nodes):
    print('------------------         3         ------------------')
    print('Starting third pack of tests: delete channel between node 6 and 7 and between 0 and 2')

    nodes[config.CHANNELS[0][0]].close_channel_sync(funding_txid=config.CHANNELS[0][3], force=True,
                                                    generate_blocks=True, delay=True)
    nodes[config.CHANNELS[6][0]].close_channel_sync(funding_txid=config.CHANNELS[6][3], force=True,
                                                    generate_blocks=True, delay=True)

    check_transactions(nodes, config.THIRD_TRANSACTIONS)


@timethis
def tests_4(nodes, simnet):
    print('------------------         4         ------------------')
    print('Starting forth pack of tests: delete node 0')

    simnet.lnd_destroy(nodes[0], autostop=True)

    check_transactions(nodes, config.FORTH_TRANSACTIONS)


@timethis
def tests_5(nodes, simnet):
    print('------------------         5         ------------------')
    print('Starting fifth pack of tests: delete node 2')

    simnet.lnd_destroy(nodes[2], autostop=True)

    check_transactions(nodes, config.FIFTH_TRANSACTIONS)


@timethis
def tests_6(nodes, simnet):
    print('------------------         6         ------------------')
    print('Starting sixth pack of tests: create node 0 and open 3 channels to it')

    node = simnet.lnd_new('node-{}'.format(0), autostart=True)
    print('Node 0 is:')
    print(node)
    print(node.list_peers())
    print('Node 1 is:')
    print(nodes[1])
    print('Node 4 is:')
    print(nodes[4])
    print('Node 7 is:')
    print(nodes[7])
    nodes[1].connect(node)
    time.sleep(DELAY)
    nodes[1].list_peers()
    nodes[1].open_channel_sync(lnd=node, local_funding_amount=int(1e8), generate_blocks=True, delay=True)

    nodes[4].connect(node)
    time.sleep(DELAY)
    nodes[4].list_peers()
    nodes[4].open_channel_sync(lnd=node, local_funding_amount=int(1e8), generate_blocks=True, delay=True)

    nodes[7].connect(node)
    time.sleep(DELAY)
    nodes[7].list_peers()
    nodes[7].open_channel_sync(lnd=node, local_funding_amount=int(1e8), generate_blocks=True, delay=True)

    nodes[0] = node

    check_transactions(nodes, config.SIXTH_TRANSACTIONS)

    return nodes


def run():
    simnet, nodes = network_up()
    simnet.core.generate(num_blocks=400, delay=True)
    wallet_balance(simnet, nodes)
    channel_balance(nodes)
    tests_1(nodes)
    tests_2(nodes)
    tests_3(nodes)
    tests_4(nodes, simnet)
    tests_5(nodes, simnet)
    nodes = tests_6(nodes, simnet)
    network_down(simnet, nodes)


if __name__ == '__main__':
    run()
