import asyncio

import pytest

from celestia.node_api import Client
from celestia.node_api.blob import Blob


@pytest.mark.asyncio
async def test_account_address(node_provider, bridge_addresses):
    bridge, auth_token = await node_provider('bridge-0')
    client = Client(port=bridge.port['26658/tcp'])
    async with client.connect(auth_token) as api:
        address = await api.state.account_address()
        assert address in bridge_addresses


@pytest.mark.asyncio
async def test_account_balance(node_provider):
    bridge, auth_token = await node_provider('bridge-0')
    client = Client(port=bridge.port['26658/tcp'])
    async with client.connect(auth_token) as api:
        balance = await api.state.balance()
        assert balance.amount > 100000000000000
        assert balance.denom == 'utia'
        address = await api.state.account_address()
        address_balance = await api.state.balance_for_address(address)
        assert address_balance == balance


@pytest.mark.asyncio
async def test_transfer(node_provider):
    bridge1, auth_token1 = await node_provider('bridge-0')
    client1 = Client(port=bridge1.port['26658/tcp'])
    bridge2, auth_token2 = await node_provider('bridge-1')
    client2 = Client(port=bridge2.port['26658/tcp'])
    bridge3, auth_token3 = await node_provider('bridge-2')
    client3 = Client(port=bridge3.port['26658/tcp'])

    async with client3.connect(auth_token3) as api:
        address3 = await api.state.account_address()
        start_balance3 = await api.state.balance()
        await api.blob.submit(Blob(b'abc', b'client3'))

    async with client2.connect(auth_token2) as api:
        address2 = await api.state.account_address()
        start_balance2 = await api.state.balance()
        await api.blob.submit(Blob(b'abc', b'client2'))

    async with client1.connect(auth_token1) as api:
        start_balance1 = await api.state.balance()
        await api.blob.submit(Blob(b'abc', b'client1'))
        await asyncio.sleep(5)
        new_balance3 = await api.state.balance_for_address(address3)
        new_balance2 = await api.state.balance_for_address(address2)
        new_balance1 = await api.state.balance()

        assert new_balance3.amount < start_balance3.amount
        assert new_balance2.amount < start_balance2.amount
        assert new_balance1.amount < start_balance1.amount

        await api.state.transfer(address3, 20)
        await api.state.transfer(address2, 20)

        await asyncio.sleep(5)
        assert (await api.state.balance()).amount < new_balance1.amount - 40
        assert (await api.state.balance_for_address(address2)).amount == new_balance2.amount + 20
        assert (await api.state.balance_for_address(address3)).amount == new_balance3.amount + 20

        await api.state.grant_fee(address2, 10000)
        await api.state.revoke_grant_fee(address2)


@pytest.mark.asyncio
async def test_undelegate(node_provider, validator_addresses):
    bridge, auth_token = await node_provider('bridge-0')
    client = Client(port=bridge.port['26658/tcp'])
    validator1 = validator_addresses[0]
    try:
        async with client.connect(auth_token) as api:
            await api.blob.submit(Blob(b'abc', b'client3'))

            try:
                amount_before_delegation = (
                    await api.state.query_delegation(validator1)).delegation_response.balance.amount
            except ValueError:
                amount_before_delegation = 0

            await api.state.delegate(validator1, 10000)
            await api.state.undelegate(validator1, 1000)
            assert (await api.state.query_delegation(
                validator1)).delegation_response.balance.amount == amount_before_delegation + 9000

            query_unbonding1 = await api.state.query_unbonding(validator1)
            undelegate = await api.state.undelegate(validator1, 9000)
            query_unbonding2 = await api.state.query_unbonding(validator1)
            assert len(query_unbonding1.unbond.entries) + 1 == len(query_unbonding2.unbond.entries)

            await api.state.cancel_unbonding_delegation(validator1, 3000, undelegate.height)
            query_unbonding3 = await api.state.query_unbonding(validator1)
            assert query_unbonding2.unbond.entries[-1].balance - 3000 == query_unbonding3.unbond.entries[-1].balance

            query_delegation = await api.state.query_delegation(validator1)
            with pytest.raises(ValueError):
                await api.state.undelegate(validator1, query_delegation.delegation_response.balance.amount + 1000)
            await api.state.undelegate(validator1, query_delegation.delegation_response.balance.amount)

    except ValueError as e:
        if 'too many unbonding delegation entries for' in e.args[0]:
            print(""" The unbound pool is full. To test the functions undelegate, query_unbonding,
                  cancel_unbonding_delegation the network needs to be recreated
                  """
                  )


@pytest.mark.asyncio
async def test_delegating(node_provider, validator_addresses):
    bridge, auth_token = await node_provider('bridge-0')
    client = Client(port=bridge.port['26658/tcp'])
    validator1, validator2, validator3 = validator_addresses
    try:
        async with client.connect(auth_token) as api:
            await api.blob.submit(Blob(b'abc', b'client3'))

            try:
                amount_validator1_before_delegation = (
                    await api.state.query_delegation(validator1)).delegation_response.balance.amount
            except ValueError:
                amount_validator1_before_delegation = 0
            try:
                amount_validator2_before_delegation = (
                    await api.state.query_delegation(validator2)).delegation_response.balance.amount
            except ValueError:
                amount_validator2_before_delegation = 0

            await api.state.delegate(validator1, 10000)
            assert (await api.state.query_delegation(
                validator1)).delegation_response.balance.amount == amount_validator1_before_delegation + 10000

            await api.state.begin_redelegate(validator1, validator2, 10000)
            assert (await api.state.query_delegation(
                validator1)).delegation_response.balance.amount == amount_validator1_before_delegation
            assert (await api.state.query_delegation(
                validator2)).delegation_response.balance.amount == amount_validator2_before_delegation + 10000

            querry = await api.state.query_redelegations(validator1, validator2)
            assert querry.redelegation_responses[0].entries[-1].balance == 10000

            with pytest.raises(ValueError):
                await api.state.begin_redelegate(validator1, validator1, 3000)
            with pytest.raises(ValueError):
                await api.state.query_redelegations(validator1, validator1)
            with pytest.raises(ValueError):
                await api.state.query_redelegations(validator2, validator1)

    except ValueError as e:
        if 'too many redelegation entries for (delegator, src-validator, dst-validator)' in e.args[0]:
            print(""" The redelegation entries is full. To test the functions query_delegation, delegate,
                  begin_redelegate the network needs to be recreated
                  """
                  )
