from unittest import TestCase
from contracting.client import ContractingClient
from contracting.stdlib.bridge.time import Timedelta, DAYS, WEEKS, Datetime
from datetime import datetime as dt, timedelta as td


class TestPendingMasters(TestCase):
    def setUp(self):
        self.client = ContractingClient()
        self.client.flush()

        f = open('./contracts/currency.s.py')
        self.client.submit(f.read(), 'currency')
        f.close()

        f = open('./contracts/election_house.s.py')
        self.client.submit(f.read(), 'election_house')
        f.close()

        f = open('./contracts/stamp_cost.s.py')
        self.client.submit(f.read(), 'stamp_cost', owner='election_house', constructor_args={'initial_rate': 20_000})
        f.close()

        f = open('./contracts/members.s.py')
        self.client.submit(f.read(), 'masternodes', owner='election_house', constructor_args={'initial_members':
                                                                                              ['stux', 'raghu']})
        f.close()

        f = open('./contracts/elect_members.s.py')
        self.client.submit(f.read(), 'elect_members', constructor_args={
            'policy': 'masternodes'
        })
        f.close()

        self.elect_members = self.client.get_contract(name='elect_members')
        self.currency = self.client.get_contract(name='currency')
        self.masternodes = self.client.get_contract(name='masternodes')

        self.stamp_cost = self.client.get_contract(name='stamp_cost')
        self.election_house = self.client.get_contract(name='election_house')
        self.election_house.register_policy(contract='stamp_cost')
        self.election_house.register_policy(contract='masternodes')

    def tearDown(self):
        self.client.flush()

    def test_register(self):
        self.currency.approve(signer='stu', amount=100_000, to='elect_members')
        self.elect_members.register(signer='stu')
        q = self.elect_members.candidate_state['votes', 'stu']

        self.assertEqual(q, 0)
        self.assertEqual(self.currency.balances['elect_members'], 100_000)
        self.assertEqual(self.elect_members.candidate_state['registered', 'stu'], True)

    def test_double_register_raises_assert(self):
        self.currency.approve(signer='stu', amount=100_000, to='elect_members')
        self.elect_members.register(signer='stu')
        self.currency.approve(signer='stu', amount=100_000, to='elect_members')

        with self.assertRaises(AssertionError):
            self.elect_members.register(signer='stu')

    def test_unregister_returns_currency(self):
        b1 = self.currency.balances['stu']
        self.currency.approve(signer='stu', amount=100_000, to='elect_members')
        self.elect_members.register(signer='stu')

        self.assertEqual(self.currency.balances['stu'], b1 - 100_000)

        self.elect_members.unregister(signer='stu')

        self.assertEqual(self.currency.balances['stu'], b1)

    def test_unregister_if_in_masternodes_throws_assert(self):
        self.currency.approve(signer='stu', amount=100_000, to='elect_members')
        self.elect_members.register(signer='stu')

        self.masternodes.S['masternodes'] = ['stu', 'raghu']

        with self.assertRaises(AssertionError):
            self.elect_members.unregister()

    def test_unregister_if_not_registered_throws_assert(self):
        with self.assertRaises(AssertionError):
            self.elect_members.unregister()

    def test_vote_for_someone_not_registered_throws_assertion_error(self):
        with self.assertRaises(AssertionError):
            self.elect_members.vote_candidate(address='stu')

    def test_vote_for_someone_registered_deducts_tau_and_adds_vote(self):
        # Give joe money
        self.currency.transfer(signer='stu', amount=100_000, to='joe')

        # Joe Allows Spending
        self.currency.approve(signer='joe', amount=100_000, to='elect_members')

        self.elect_members.register(signer='joe')

        self.currency.approve(signer='stu', amount=10_000, to='elect_members')

        env = {'now': Datetime._from_datetime(dt.today())}

        stu_bal = self.currency.balances['stu']

        self.elect_members.vote_candidate(signer='stu', address='joe', environment=env)

        self.assertEqual(self.currency.balances['stu'], stu_bal - 1)
        self.assertEqual(self.elect_members.candidate_state['votes', 'joe'], 1)
        self.assertEqual(self.currency.balances['blackhole'], 1)
        self.assertEqual(self.elect_members.candidate_state['last_voted', 'stu'], env['now'])

    def test_voting_again_too_soon_throws_assertion_error(self):
        # Give joe money
        self.currency.transfer(signer='stu', amount=100_000, to='joe')

        # Joe Allows Spending
        self.currency.approve(signer='joe', amount=100_000, to='elect_members')

        self.elect_members.register(signer='joe')

        self.currency.approve(signer='stu', amount=10_000, to='elect_members')

        env = {'now': Datetime._from_datetime(dt.today())}

        self.elect_members.vote_candidate(signer='stu', address='joe', environment=env)

        with self.assertRaises(AssertionError):
            self.elect_members.vote_candidate(signer='stu', address='joe', environment=env)

    def test_voting_again_after_waiting_one_day_works(self):
        # Give joe money
        self.currency.transfer(signer='stu', amount=100_000, to='joe')

        # Joe Allows Spending
        self.currency.approve(signer='joe', amount=100_000, to='elect_members')

        self.elect_members.register(signer='joe')

        self.currency.approve(signer='stu', amount=10_000, to='elect_members')

        stu_bal = self.currency.balances['stu']

        env = {'now': Datetime._from_datetime(dt.today())}

        self.elect_members.vote_candidate(signer='stu', address='joe', environment=env)

        env = {'now': Datetime._from_datetime(dt.today() + td(days=7))}

        self.elect_members.vote_candidate(signer='stu', address='joe', environment=env)

        self.assertEqual(self.currency.balances['stu'], stu_bal - 2)
        self.assertEqual(self.elect_members.candidate_state['votes', 'joe'], 2)

        self.assertEqual(self.currency.balances['blackhole'], 2)

        self.assertEqual(self.elect_members.candidate_state['last_voted', 'stu'], env['now'])

    def test_top_masternode_returns_none_if_no_candidates(self):
        self.assertIsNone(self.elect_members.top_member())

    def test_top_masternode_returns_joe_if_registered_but_no_votes(self):
        self.currency.transfer(signer='stu', amount=100_000, to='joe')  # Give joe money
        self.currency.approve(signer='joe', amount=100_000, to='elect_members')  # Joe Allows Spending
        self.elect_members.register(signer='joe')  # Register Joe

        self.assertEqual(self.elect_members.top_member(), 'joe')  # Joe is the current top spot

    def test_top_masternode_returns_bob_if_joe_and_bob_registered_but_bob_has_votes(self):
        self.currency.transfer(signer='stu', amount=100_000, to='joe')  # Give joe money
        self.currency.approve(signer='joe', amount=100_000, to='elect_members')  # Joe Allows Spending
        self.elect_members.register(signer='joe')  # Register Joe

        self.currency.transfer(signer='stu', amount=100_000, to='bob')  # Give Bob money
        self.currency.approve(signer='bob', amount=100_000, to='elect_members')  # Bob Allows Spending
        self.elect_members.register(signer='bob')  # Register Bob

        self.currency.approve(signer='stu', amount=10_000, to='elect_members')  # Stu approves spending to vote
        env = {'now': Datetime._from_datetime(dt.today())}
        self.elect_members.vote_candidate(signer='stu', address='bob', environment=env)  # Stu votes for Bob

        self.assertEqual(self.elect_members.top_member(), 'bob')  # bob is the current top spot

    def test_top_masternode_returns_joe_if_joe_and_bob_registered_but_joe_first_and_no_votes(self):
        self.currency.transfer(signer='stu', amount=100_000, to='joe')  # Give joe money
        self.currency.approve(signer='joe', amount=100_000, to='elect_members')  # Joe Allows Spending
        self.elect_members.register(signer='joe')  # Register Joe

        self.currency.transfer(signer='stu', amount=100_000, to='bob')  # Give Bob money
        self.currency.approve(signer='bob', amount=100_000, to='elect_members')  # Bob Allows Spending
        self.elect_members.register(signer='bob')  # Register Bob

        self.assertEqual(self.elect_members.top_member(), 'joe')  # Joe is the current top spot

    def test_pop_top_fails_if_not_masternodes_contract(self):
        with self.assertRaises(AssertionError):
            self.elect_members.pop_top()

    def test_pop_top_doesnt_fail_if_masternode_contract(self):
        self.elect_members.pop_top(signer='masternodes')

    def test_pop_top_deletes_bob_if_pop_is_top_masternode(self):
        self.currency.transfer(signer='stu', amount=100_000, to='joe')  # Give joe money
        self.currency.approve(signer='joe', amount=100_000, to='elect_members')  # Joe Allows Spending
        self.elect_members.register(signer='joe')  # Register Joe

        self.currency.transfer(signer='stu', amount=100_000, to='bob')  # Give Bob money
        self.currency.approve(signer='bob', amount=100_000, to='elect_members')  # Bob Allows Spending
        self.elect_members.register(signer='bob')  # Register Bob

        self.currency.approve(signer='stu', amount=10_000, to='elect_members')  # Stu approves spending to vote
        env = {'now': Datetime._from_datetime(dt.today())}
        self.elect_members.vote_candidate(signer='stu', address='bob', environment=env)  # Stu votes for Bob

        self.assertEqual(self.elect_members.top_member(), 'bob')  # bob is the current top spot

        self.assertIsNotNone(self.elect_members.candidate_state['votes', 'bob'])

        self.elect_members.pop_top(signer='masternodes')

        self.assertIsNone(self.elect_members.candidate_state['votes', 'bob'])

    def test_pop_top_returns_none_if_noone_registered(self):
        self.assertIsNone(self.elect_members.pop_top(signer='masternodes'))

    def test_voting_no_confidence_against_non_committee_member_fails(self):
        with self.assertRaises(AssertionError):
            self.elect_members.vote_no_confidence(address='whoknows')

    def test_vote_no_confidence_for_someone_registered_deducts_tau_and_adds_vote(self):
        self.currency.approve(signer='stu', amount=10_000, to='elect_members')

        stu_bal = self.currency.balances['stu']

        env = {'now': Datetime._from_datetime(dt.today())}

        self.elect_members.vote_no_confidence(signer='stu', address='raghu', environment=env) # Raghu is seeded in contract

        self.assertEqual(self.currency.balances['stu'], stu_bal - 1)
        self.assertEqual(self.elect_members.no_confidence_state['votes', 'raghu'], 1)
        self.assertEqual(self.currency.balances['blackhole'], 1)
        self.assertEqual(self.elect_members.no_confidence_state['last_voted', 'stu'], env['now'])

    def test_voting_no_confidence_again_too_soon_throws_assertion_error(self):
        self.currency.approve(signer='stu', amount=10_000, to='elect_members')

        env = {'now': Datetime._from_datetime(dt.today())}

        self.elect_members.vote_no_confidence(signer='stu', address='raghu', environment=env)

        with self.assertRaises(AssertionError):
            self.elect_members.vote_no_confidence(signer='stu', address='raghu', environment=env)

    def test_voting_no_confidence_again_after_waiting_one_day_works(self):
        self.currency.approve(signer='stu', amount=10_000, to='elect_members')

        stu_bal = self.currency.balances['stu']

        env = {'now': Datetime._from_datetime(dt.today())}

        self.elect_members.vote_no_confidence(signer='stu', address='raghu', environment=env)

        env = {'now': Datetime._from_datetime(dt.today() + td(days=7))}

        self.elect_members.vote_no_confidence(signer='stu', address='raghu', environment=env)

        self.assertEqual(self.currency.balances['stu'], stu_bal - 2)
        self.assertEqual(self.elect_members.no_confidence_state['votes', 'raghu'], 2)

        self.assertEqual(self.currency.balances['blackhole'], 2)

        self.assertEqual(self.elect_members.no_confidence_state['last_voted', 'stu'], env['now'])

    def test_last_masternode_returns_none_if_no_candidates(self):
        self.assertIsNone(self.elect_members.last_member())

    def test_last_masternode_returns_none_if_no_votes(self):
        self.assertEqual(self.elect_members.last_member(), None)  # Joe is the current top spot

    def test_relinquish_fails_if_not_in_masternodes(self):
        with self.assertRaises(AssertionError):
            self.elect_members.relinquish(signer='joebob')

    def test_relinquish_adds_ctx_signer_if_in_masternodes(self):
        self.elect_members.relinquish(signer='raghu')

        self.assertEqual('raghu', self.elect_members.to_be_relinquished.get())

    def test_last_masternode_returns_relinquished_if_there_is_one_to_be_relinquished(self):
        self.elect_members.relinquish(signer='raghu')

        self.assertEqual(self.elect_members.last_member(), 'raghu')

    def test_error_if_someone_tries_to_relinquish_when_another_exists(self):
        self.elect_members.relinquish(signer='raghu')
        with self.assertRaises(AssertionError):
            self.elect_members.relinquish(signer='stux')

    def test_last_masternode_returns_masternode_with_most_votes_if_none_in_relinquished(self):
        self.currency.approve(signer='stu', amount=10_000, to='elect_members')  # Stu approves spending to vote
        env = {'now': Datetime._from_datetime(dt.today())}
        self.elect_members.vote_no_confidence(signer='stu', address='raghu', environment=env)  # Stu votes for Bob

        self.assertEqual(self.elect_members.last_member(), 'raghu')  # bob is the current top spot

    def test_last_masternode_returns_first_in_if_tie(self):
        self.currency.approve(signer='stu', amount=10_000, to='elect_members')

        env = {'now': Datetime._from_datetime(dt.today())}

        self.elect_members.vote_no_confidence(signer='stu', address='stux', environment=env)

        env = {'now': Datetime._from_datetime(dt.today() + td(days=7))}

        self.elect_members.vote_no_confidence(signer='stu', address='raghu', environment=env)

        self.assertEqual(self.elect_members.last_member(), 'stux')

    def test_last_masternode_returns_least_popular_if_multiple_votes(self):
        self.currency.approve(signer='stu', amount=10_000, to='elect_members')

        env = {'now': Datetime._from_datetime(dt.today())}
        self.elect_members.vote_no_confidence(signer='stu', address='stux', environment=env)

        env = {'now': Datetime._from_datetime(dt.today() + td(days=7))}
        self.elect_members.vote_no_confidence(signer='stu', address='raghu', environment=env)

        env = {'now': Datetime._from_datetime(dt.today() + td(days=14))}
        self.elect_members.vote_no_confidence(signer='stu', address='stux', environment=env)

        self.assertEqual(self.elect_members.last_member(), 'stux')

    def test_pop_last_fails_if_not_masternodes_contract(self):
        with self.assertRaises(AssertionError):
            self.elect_members.pop_last()

    def test_pop_last_doesnt_fail_if_masternodes_contract(self):
        self.elect_members.pop_last(signer='masternodes')

    def test_pop_last_deletes_stux_if_is_last_masternode_and_no_relinquished(self):
        self.currency.approve(signer='stu', amount=10_000, to='elect_members')

        env = {'now': Datetime._from_datetime(dt.today())}
        self.elect_members.vote_no_confidence(signer='stu', address='stux', environment=env)

        self.assertIsNotNone(self.elect_members.no_confidence_state['votes', 'stux'])
        self.elect_members.pop_last(signer='masternodes')
        self.assertIsNone(self.elect_members.no_confidence_state['votes', 'stux'])

    def test_pop_last_deletes_raghu_if_stux_voted_but_raghu_relinquished(self):
        self.currency.approve(signer='stu', amount=10_000, to='elect_members')

        env = {'now': Datetime._from_datetime(dt.today())}
        self.elect_members.vote_no_confidence(signer='stu', address='stux', environment=env)

        self.elect_members.relinquish(signer='raghu')

        self.assertIsNotNone(self.elect_members.no_confidence_state['votes', 'stux'])
        self.assertIn('raghu', self.elect_members.to_be_relinquished.get())

        self.elect_members.pop_last(signer='masternodes')

        self.assertIsNone(self.elect_members.to_be_relinquished.get())
        self.assertIsNotNone(self.elect_members.no_confidence_state['votes', 'stux'])

    def test_pop_last_deletes_raghu_from_no_confidence_hash_if_relinquished(self):
        self.currency.approve(signer='stu', amount=10_000, to='elect_members')

        env = {'now': Datetime._from_datetime(dt.today())}
        self.elect_members.vote_no_confidence(signer='stu', address='raghu', environment=env)

        self.elect_members.relinquish(signer='raghu')

        self.assertIsNotNone(self.elect_members.no_confidence_state['votes', 'raghu'])
        self.assertIn('raghu', self.elect_members.to_be_relinquished.get())

        self.elect_members.pop_last(signer='masternodes')

        self.assertIsNone(self.elect_members.to_be_relinquished.get())
        self.assertEqual(self.elect_members.no_confidence_state['votes', 'raghu'], 0)

    def test_no_confidence_pop_last_prevents_unregistering(self):
        # Give Raghu money
        self.currency.transfer(signer='stu', amount=100_000, to='raghu')

        # Raghu Allows Spending
        self.currency.approve(signer='raghu', amount=100_000, to='elect_members')

        self.elect_members.register(signer='raghu')

        self.currency.approve(signer='stu', amount=10_000, to='elect_members')

        self.elect_members.vote_no_confidence(signer='stu', address='raghu')

        self.elect_members.pop_last(signer='masternodes')

        self.assertFalse(self.elect_members.candidate_state['registered', 'raghu'])

        with self.assertRaises(AssertionError):
            self.elect_members.unregister(signer='raghu')

    def test_relinquish_pop_last_allows_unregistering(self):
        # Give Raghu money
        self.currency.transfer(signer='stu', amount=100_000, to='raghu')

        # Raghu Allows Spending
        self.currency.approve(signer='raghu', amount=100_000, to='elect_members')

        self.elect_members.register(signer='raghu')

        self.currency.approve(signer='stu', amount=10_000, to='elect_members')

        self.elect_members.vote_no_confidence(signer='stu', address='raghu')
        self.elect_members.relinquish(signer='raghu')
        self.elect_members.pop_last(signer='masternodes')

        self.assertTrue(self.elect_members.candidate_state['registered', 'raghu'])
        self.masternodes.quick_write('S', 'members', ['stu'])
        self.elect_members.unregister(signer='raghu')

    def test_force_removal_fails_if_not_masternodes(self):
        with self.assertRaises(AssertionError):
            self.elect_members.force_removal(address='stux')

    def test_force_removal_unregisters_address(self):
        # Give Raghu money
        self.currency.transfer(signer='stu', amount=100_000, to='stux')

        # Raghu Allows Spending
        self.currency.approve(signer='stux', amount=100_000, to='elect_members')

        self.elect_members.register(signer='stux')
        self.elect_members.force_removal(signer='masternodes', address='stux')
        self.assertFalse(self.elect_members.candidate_state['registered', 'stux'])

