import json

import bitcoin
import retrying

from conio_sdk.generated_protobuf import v1_pb2
from test.integration import moving_coins
from test.integration.v1.base_integration_test import BaseV1IntegrationTest, Satoshi


class TestBtcWithdrawal(BaseV1IntegrationTest):
    _SATOSHI = Satoshi(12345600)

    @moving_coins
    def test_withdraw_btc(self):
        signup_response = self.signin_with_new_user()
        authentication_headers = signup_response.authentication_headers
        bip32_private_key = signup_response.bip32_private_key
        self.ensure_receive_satoshi(self._SATOSHI, authentication_headers)
        self.withdraw_btc(
            bip32_private_key, authentication_headers,
            signup_response.signup_message.cryptoRequest.email,
            satoshi_amount=self._SATOSHI // 2)

        @retrying.retry(
                wait_exponential_multiplier=100,
                wait_exponential_max=1000, stop_max_delay=60000,
                retry_on_exception=lambda e: isinstance(e, AssertionError)
        )
        def _f():
            print('Attempting to get {} satoshi'.format(self._SATOSHI // 2))
            cur_wallet_info = self.get_wallet_info(authentication_headers)
            self.assertGreater(
                self._SATOSHI // 2, cur_wallet_info.confirmed_satoshi_amount,
                msg='Wallet has {} satoshi'.format(cur_wallet_info.confirmed_satoshi_amount))
            self.assertLess(0, cur_wallet_info.confirmed_satoshi_amount)
            self.assertEqual(0, cur_wallet_info.unconfirmed_satoshi_amount)
        _f()

        resp = self.withdraw_btc(
            bip32_private_key, authentication_headers,
            signup_response.signup_message.cryptoRequest.email,
            satoshi_amount=self._SATOSHI,
            expected_response=400)
        self.assertEqual({'error_code': 'NOT_ENOUGH_BTC_AMOUNT', 'arg': ''}, json.loads(resp.content))

    @moving_coins
    def test_withdraw_all_btc(self):
        signup_response = self.signin_with_new_user()
        authentication_headers = signup_response.authentication_headers
        bip32_private_key = signup_response.bip32_private_key
        self.ensure_receive_satoshi(self._SATOSHI, authentication_headers)

        def _f():
            cur_wallet_info = self.get_wallet_info(authentication_headers)
            self.assertEqual(self._SATOSHI, cur_wallet_info.confirmed_satoshi_amount)
            self.assertEqual(0, cur_wallet_info.unconfirmed_satoshi_amount)

        _f()

        self.withdraw_btc(
            bip32_private_key, authentication_headers,
            signup_response.signup_message.cryptoRequest.email)

        @retrying.retry(
            wait_exponential_multiplier=100,
            wait_exponential_max=1000, stop_max_delay=60000,
            retry_on_exception=lambda e: isinstance(e, AssertionError)
        )
        def _f():
            print('Attempting to get 0 satoshi')
            cur_wallet_info = self.get_wallet_info(authentication_headers)
            self.assertEqual(0, cur_wallet_info.confirmed_satoshi_amount)
            self.assertEqual(0, cur_wallet_info.unconfirmed_satoshi_amount)

        _f()

    @moving_coins
    def test_get_btc_withdrawal_fees_all_fees(self):
        signup_response = self.signin_with_new_user()
        authentication_headers = signup_response.authentication_headers
        self.ensure_receive_satoshi(self._SATOSHI, authentication_headers)

        fees = self.get_btc_withdrawal_fees(
            authentication_headers,
            bitcoin.privtoaddr(bitcoin.random_key(), magicbyte=111),
            satoshi_amount=Satoshi(self._SATOSHI // 2)
        )
        self.assertEqual(self._SATOSHI // 2, fees.amount)
        self.assertEqual(5, len(fees.available_fees))

        prev_fee = None
        all_same_fee_per_byte = all_same_speed = all_same_absolute_fee = all_same_amount = all_same_avg_time = True
        for available_fee in fees.available_fees:
            if prev_fee:
                self.assertLessEqual(available_fee.fee_per_byte, prev_fee.fee_per_byte)
                self.assertGreaterEqual(available_fee.speed, prev_fee.speed)
                self.assertLessEqual(available_fee.absolute_fee, prev_fee.absolute_fee)
                self.assertGreaterEqual(available_fee.amount, prev_fee.amount)
                self.assertGreaterEqual(
                    (available_fee.min_time + available_fee.max_time) / 2,
                    (prev_fee.min_time + prev_fee.max_time) / 2)
                all_same_fee_per_byte = all_same_fee_per_byte and prev_fee.fee_per_byte == available_fee.fee_per_byte
                all_same_speed = all_same_speed and prev_fee.speed == available_fee.speed
                all_same_absolute_fee = all_same_absolute_fee and prev_fee.absolute_fee == available_fee.absolute_fee
                all_same_amount = all_same_amount and prev_fee.amount == available_fee.amount
                all_same_avg_time = all_same_avg_time and (available_fee.min_time + available_fee.max_time) == (prev_fee.min_time + prev_fee.max_time)
            else:
                prev_fee = available_fee

        self.assertFalse(all_same_fee_per_byte)
        self.assertFalse(all_same_speed)
        self.assertFalse(all_same_absolute_fee)
        self.assertTrue(all_same_amount)
        self.assertFalse(all_same_avg_time)

    @moving_coins
    def test_get_btc_withdrawal_fees_all_amounts_all_fees(self):
        signup_response = self.signin_with_new_user()
        authentication_headers = signup_response.authentication_headers
        self.ensure_receive_satoshi(self._SATOSHI, authentication_headers)

        fees = self.get_btc_withdrawal_fees(
            authentication_headers,
            bitcoin.privtoaddr(bitcoin.random_key(), magicbyte=111),
            # satoshi_amount=Satoshi(self._SATOSHI // 2)
        )
        self.assertAlmostEqual(float(self._SATOSHI), fees.amount, delta=60000)
        self.assertEqual(5, len(fees.available_fees))

        prev_fee = None
        all_same_fee_per_byte = all_same_speed = all_same_absolute_fee = all_same_amount = all_same_avg_time = True
        for available_fee in fees.available_fees:
            if prev_fee:
                self.assertLessEqual(available_fee.fee_per_byte, prev_fee.fee_per_byte)
                self.assertGreaterEqual(available_fee.speed, prev_fee.speed)
                self.assertLessEqual(available_fee.absolute_fee, prev_fee.absolute_fee)
                self.assertGreaterEqual(available_fee.amount, prev_fee.amount)
                self.assertGreaterEqual(
                    (available_fee.min_time + available_fee.max_time) / 2,
                    (prev_fee.min_time + prev_fee.max_time) / 2)
                all_same_fee_per_byte = all_same_fee_per_byte and prev_fee.fee_per_byte == available_fee.fee_per_byte
                all_same_speed = all_same_speed and prev_fee.speed == available_fee.speed
                all_same_absolute_fee = all_same_absolute_fee and prev_fee.absolute_fee == available_fee.absolute_fee
                all_same_amount = all_same_amount and prev_fee.amount == available_fee.amount
                all_same_avg_time = all_same_avg_time and (available_fee.min_time + available_fee.max_time) == (
                            prev_fee.min_time + prev_fee.max_time)
            else:
                prev_fee = available_fee

        self.assertFalse(all_same_fee_per_byte)
        self.assertFalse(all_same_speed)
        self.assertFalse(all_same_absolute_fee)
        self.assertFalse(all_same_amount)
        self.assertFalse(all_same_avg_time)

    @moving_coins
    def test_get_btc_withdrawal_fees(self):
        signup_response = self.signin_with_new_user()
        authentication_headers = signup_response.authentication_headers
        self.ensure_receive_satoshi(self._SATOSHI, authentication_headers)

        fees = self.get_btc_withdrawal_fees(
            authentication_headers,
            bitcoin.privtoaddr(bitcoin.random_key(), magicbyte=111),
            transaction_speed_type=v1_pb2.TRANSACTION_SPEED_TYPE_3,
            satoshi_amount=Satoshi(self._SATOSHI // 2)
        )
        self.assertEqual(self._SATOSHI // 2, fees.amount)
        self.assertEqual(0, len(fees.available_fees))

    @moving_coins
    def test_get_btc_withdrawal_fees_all_amounts(self):
        signup_response = self.signin_with_new_user()
        authentication_headers = signup_response.authentication_headers
        self.ensure_receive_satoshi(self._SATOSHI, authentication_headers)

        fees = self.get_btc_withdrawal_fees(
            authentication_headers,
            bitcoin.privtoaddr(bitcoin.random_key(), magicbyte=111),
            transaction_speed_type=v1_pb2.TRANSACTION_SPEED_TYPE_3,
            # satoshi_amount=Satoshi(self._SATOSHI // 2)
        )
        self.assertAlmostEqual(float(self._SATOSHI), fees.amount, delta=60000)
        self.assertEqual(0, len(fees.available_fees))

    @moving_coins
    def test_attempt_to_alter_2fa_withdraw_btc(self):
        signup_response = self.signin_with_new_user()
        authentication_headers = signup_response.authentication_headers
        self.ensure_receive_satoshi(self._SATOSHI, authentication_headers)
        self.attempt_to_alter_2fa_withdraw_btc(
            authentication_headers,
            signup_response.signup_message.cryptoRequest.email,
            satoshi_amount=self._SATOSHI // 2)
