

from decimal import Decimal, InvalidOperation

import pytest

from nucypher.blockchain.eth.token import NU


def test_NU():

    # Alternate construction
    assert NU(1, 'NU') == NU('1.0', 'NU') == NU(1.0, 'NU')

    # Arithmetic

    # NUs
    one_nu = NU(1, 'NU')
    zero_nu = NU(0, 'NU')
    one_hundred_nu = NU(100, 'NU')
    two_hundred_nu = NU(200, 'NU')
    three_hundred_nu = NU(300, 'NU')

    # Nits
    one_nu_wei = NU(1, 'NuNit')
    three_nu_wei = NU(3, 'NuNit')
    assert three_nu_wei.to_tokens() == Decimal('3E-18')
    assert one_nu_wei.to_tokens() == Decimal('1E-18')

    # Base Operations
    assert one_hundred_nu < two_hundred_nu < three_hundred_nu
    assert one_hundred_nu <= two_hundred_nu <= three_hundred_nu

    assert three_hundred_nu > two_hundred_nu > one_hundred_nu
    assert three_hundred_nu >= two_hundred_nu >= one_hundred_nu

    assert (one_hundred_nu + two_hundred_nu) == three_hundred_nu
    assert (three_hundred_nu - two_hundred_nu) == one_hundred_nu

    difference = one_nu - one_nu_wei
    assert not difference == zero_nu

    actual = float(difference.to_tokens())
    expected = 0.999999999999999999
    assert actual == expected

    # 3.14 NU is 3_140_000_000_000_000_000 NuNit
    pi_nuweis = NU(3.14, 'NU')
    assert NU('3.14', 'NU') == pi_nuweis.to_units() == NU(3_140_000_000_000_000_000, 'NuNit')

    # Mixed type operations
    difference = NU('3.14159265', 'NU') - NU(1.1, 'NU')
    assert difference == NU('2.04159265', 'NU')

    result = difference + one_nu_wei
    assert result == NU(2041592650000000001, 'NuNit')

    # Similar to stake read + metadata operations in Staker
    collection = [one_hundred_nu, two_hundred_nu, three_hundred_nu]
    assert sum(collection) == NU('600', 'NU') == NU(600, 'NU') == NU(600.0, 'NU') == NU(600e+18, 'NuNit')

    #
    # Fractional Inputs
    #

    # A decimal amount of NuNit (i.e., a fraction of a NuNit)
    pi_nuweis = NU('3.14', 'NuNit')
    assert pi_nuweis == three_nu_wei  # Floor

    # A decimal amount of NU, which amounts to NuNit with decimals
    pi_nus = NU('3.14159265358979323846', 'NU')
    assert pi_nus == NU(3141592653589793238, 'NuNit')  # Floor

    # Positive Infinity
    with pytest.raises(NU.InvalidAmount):
        _inf = NU(float('infinity'), 'NU')

    # Negative Infinity
    with pytest.raises(NU.InvalidAmount):
        _neg_inf = NU(float('-infinity'), 'NU')

    # Not a Number
    with pytest.raises(InvalidOperation):
        _nan = NU(float('NaN'), 'NU')

    # Rounding NUs
    assert round(pi_nus, 2) == NU("3.14", "NU")
    assert round(pi_nus, 1) == NU("3.1", "NU")
    assert round(pi_nus, 0) == round(pi_nus) == NU("3", "NU")
