import random

import pytest

from nucypher.blockchain.eth.constants import NULL_ADDRESS
from nucypher.crypto.powers import TransactingPower


def test_get_min_authorization(taco_application_agent, taco_application):
    result = taco_application_agent.get_min_authorization()
    assert result == taco_application.minimumAuthorization()


def test_get_min_seconds(taco_application_agent, taco_application):
    result = taco_application_agent.get_min_operator_seconds()
    assert result == taco_application.minOperatorSeconds()


def test_authorized_tokens(
    testerchain, taco_application, taco_application_agent, staking_providers
):
    provider_account = staking_providers[0]
    authorized_amount = taco_application_agent.get_authorized_stake(
        staking_provider=provider_account
    )
    assert authorized_amount >= taco_application.minimumAuthorization()


def test_staking_providers_and_operators_relationships(
    testerchain,
    accounts,
    taco_application_agent,
    threshold_staking,
    taco_application,
    deployer_account,
    get_random_checksum_address,
):
    staking_provider_account, operator_account, *other = accounts.unassigned_accounts
    threshold_staking.setRoles(staking_provider_account, sender=deployer_account)
    threshold_staking.authorizationIncreased(
        staking_provider_account,
        0,
        taco_application.minimumAuthorization(),
        sender=deployer_account,
    )

    # The staking provider hasn't bond an operator yet
    assert NULL_ADDRESS == taco_application_agent.get_operator_from_staking_provider(
        staking_provider=staking_provider_account
    )

    tpower = TransactingPower(
        account=staking_provider_account,
        signer=accounts.get_account_signer(staking_provider_account),
    )
    _txhash = taco_application_agent.bond_operator(
        transacting_power=tpower,
        staking_provider=staking_provider_account,
        operator=operator_account,
    )

    # We can check the staker-worker relation from both sides
    assert (
        operator_account
        == taco_application_agent.get_operator_from_staking_provider(
            staking_provider=staking_provider_account
        )
    )
    assert (
        staking_provider_account
        == taco_application_agent.get_staking_provider_from_operator(
            operator_address=operator_account
        )
    )

    # No staker-worker relationship
    assert NULL_ADDRESS == taco_application_agent.get_operator_from_staking_provider(
        staking_provider=get_random_checksum_address()
    )


def test_get_staker_population(taco_application_agent, staking_providers):
    # Apart from all the providers in the fixture, we also added a new provider above
    assert (
        taco_application_agent.get_staking_providers_population()
        == len(staking_providers) + 1
    )


@pytest.mark.usefixtures("staking_providers", "ursulas")
@pytest.mark.parametrize(
    "duration", [0, 60 * 60 * 24, 60 * 60 * 24 * 182, 60 * 60 * 24 * 365]
)
def test_sample_staking_providers(taco_application_agent, duration):
    all_staking_providers = list(taco_application_agent.get_staking_providers())
    providers_population = taco_application_agent.get_staking_providers_population()

    assert len(all_staking_providers) == providers_population

    with pytest.raises(taco_application_agent.NotEnoughStakingProviders):
        taco_application_agent.get_staking_provider_reservoir().draw(
            providers_population + 1
        )  # One more than we have deployed

    providers = taco_application_agent.get_staking_provider_reservoir(
        duration=duration
    ).draw(3)
    assert len(providers) == 3  # Three...
    assert len(set(providers)) == 3  # ...unique addresses

    # Same but with pagination
    providers = taco_application_agent.get_staking_provider_reservoir(
        pagination_size=1, duration=duration
    ).draw(3)
    assert len(providers) == 3
    assert len(set(providers)) == 3
    assert len(set(providers).intersection(all_staking_providers)) == 3

    # repeat for opposite blockchain light setting
    light = taco_application_agent.blockchain.is_light
    taco_application_agent.blockchain.is_light = not light
    providers = taco_application_agent.get_staking_provider_reservoir(
        duration=duration
    ).draw(3)
    assert len(providers) == 3
    assert len(set(providers)) == 3
    assert len(set(providers).intersection(all_staking_providers)) == 3
    taco_application_agent.blockchain.is_light = light

    # Use exclusion list
    exclude_providers = random.choices(all_staking_providers, k=3)  # exclude 3 ursulas
    providers = taco_application_agent.get_staking_provider_reservoir(
        without=exclude_providers, pagination_size=1
    ).draw(3)
    assert len(providers) == 3
    assert len(set(providers)) == 3
    assert len(set(providers).intersection(all_staking_providers)) == 3
    assert len(set(providers).intersection(exclude_providers)) == 0


def test_get_staking_provider_info(
    taco_application_agent, ursulas, get_random_checksum_address
):
    # existing staker
    staking_provider, operator_address = (
        ursulas[0].checksum_address,
        ursulas[0].operator_address,
    )
    info = taco_application_agent.get_staking_provider_info(
        staking_provider=staking_provider
    )
    assert info.operator_start_timestamp > 0
    assert info.operator == operator_address
    assert info.operator_confirmed is True

    # non-existent staker
    info = taco_application_agent.get_staking_provider_info(
        get_random_checksum_address()
    )
    assert info.operator_start_timestamp == 0
    assert info.operator == NULL_ADDRESS
    assert info.operator_confirmed is False
