import json

import retrying

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


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

    def test_sell_btc_not_enough_amount(self):
        signup_response = self.signin_with_new_user()
        authentication_headers = signup_response.authentication_headers
        bip32_private_key = signup_response.bip32_private_key
        self.assertEqual(
            {'error_code': 'NOT_ENOUGH_BTC_AMOUNT', 'arg': ''},
            json.loads(self.sell_btc(
                authentication_headers, bip32_private_key,
                satoshi_amount=Satoshi(self._SATOSHI // 2),
                expected_create_response=400,
                expected_pay_response=400).content
            )
        )

    @moving_coins
    def test_sell_btc(self):
        satoshi_to_be_sold = self._SATOSHI // 2
        remaining_satoshi = self._SATOSHI - satoshi_to_be_sold
        signup_response = self.signin_with_new_user()
        authentication_headers = signup_response.authentication_headers
        self.verify_limits(authentication_headers)
        bip32_private_key = signup_response.bip32_private_key
        tx_id = self.ensure_receive_satoshi(self._SATOSHI, authentication_headers)
        ask_id = self.sell_btc(
            authentication_headers, bip32_private_key,
            satoshi_amount=Satoshi(satoshi_to_be_sold))

        @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(
                remaining_satoshi, 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()

        activities = self.get_activities(authentication_headers, 0, False)

        expected = (
                        {
                            'activity_type': v1_pb2.ACTIVITY_SELL,
                            'satoshi': satoshi_to_be_sold,
                            'activity_id': '__SELL__{}'.format(ask_id)
                        },
                        {
                            'activity_type': v1_pb2.ACTIVITY_RECV,
                            'satoshi': self._SATOSHI,
                            'activity_id': '__BTC__{}'.format(tx_id)
                        }
                )
        # This must not fail
        self.get_activity(
            '__SELL__{}'.format(ask_id),
            authentication_headers
        )

        self.assertEqual(len(expected), len(activities))
        sell_limits = 0
        for cur_expected, cur_activity in zip(expected, activities):
            for k, v in cur_expected.items():
                self.assertEqual(v, getattr(cur_activity, k))
            if cur_activity.activity_type == v1_pb2.ACTIVITY_SELL:
                sell_limits -= cur_activity.fiat_amount
        self.verify_limits(authentication_headers, sell_limits=sell_limits)

    @moving_coins
    def test_sell_btc_with_refresh(self):
        satoshi_to_be_sold = self._SATOSHI // 2
        remaining_satoshi = self._SATOSHI - satoshi_to_be_sold
        signup_response = self.signin_with_new_user()
        authentication_headers = signup_response.authentication_headers
        bip32_private_key = signup_response.bip32_private_key
        tx_id = self.ensure_receive_satoshi(self._SATOSHI, authentication_headers)
        ask_id = self.sell_btc(
            authentication_headers, bip32_private_key,
            satoshi_amount=Satoshi(satoshi_to_be_sold),
            refresh_ask=True)

        @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(
                remaining_satoshi, 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()

        activities = self.get_activities(authentication_headers, 0, False)

        expected = (
            {
                'activity_type': v1_pb2.ACTIVITY_SELL,
                'satoshi': satoshi_to_be_sold,
                'activity_id': '__SELL__{}'.format(ask_id)
            },
            {
                'activity_type': v1_pb2.ACTIVITY_RECV,
                'satoshi': self._SATOSHI,
                'activity_id': '__BTC__{}'.format(tx_id)
            }
        )
        self.assertEqual(len(expected), len(activities))
        for cur_expected, cur_activity in zip(expected, activities):
            for k, v in cur_expected.items():
                self.assertEqual(v, getattr(cur_activity, k))

    @moving_coins
    def test_sell_btc_fiat(self):
        fiat_amount = ioc_quoting.quoting_client.get_fiat_sell_price(self._SATOSHI // 2, 'EUR') * \
                      self._SATOSHI // (2 * 10**8)

        remaining_satoshi = self._SATOSHI // 2
        signup_response = self.signin_with_new_user()
        authentication_headers = signup_response.authentication_headers
        bip32_private_key = signup_response.bip32_private_key
        tx_id = self.ensure_receive_satoshi(self._SATOSHI, authentication_headers)
        ask_id = self.sell_btc(
            authentication_headers, bip32_private_key,
            fiat_amount=fiat_amount)

        @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(
                remaining_satoshi, 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()

        activities = self.get_activities(authentication_headers, 0, False)

        expected = (
            {
                'activity_type': v1_pb2.ACTIVITY_SELL,
                'fiat_amount': fiat_amount,
                'activity_id': '__SELL__{}'.format(ask_id)
            },
            {
                'activity_type': v1_pb2.ACTIVITY_RECV,
                'satoshi': self._SATOSHI,
                'activity_id': '__BTC__{}'.format(tx_id)
            }
        )
        self.assertEqual(len(expected), len(activities))
        for cur_expected, cur_activity in zip(expected, activities):
            for k, v in cur_expected.items():
                self.assertEqual(v, getattr(cur_activity, k))

    @moving_coins
    def test_sell_btc_fiat_with_refresh(self):
        fiat_amount = ioc_quoting.quoting_client.get_fiat_sell_price(self._SATOSHI // 2, 'EUR') * \
                      self._SATOSHI // (2 * 10 ** 8)

        remaining_satoshi = self._SATOSHI // 2
        signup_response = self.signin_with_new_user()
        authentication_headers = signup_response.authentication_headers
        bip32_private_key = signup_response.bip32_private_key
        tx_id = self.ensure_receive_satoshi(self._SATOSHI, authentication_headers)
        ask_id = self.sell_btc(
            authentication_headers, bip32_private_key,
            fiat_amount=fiat_amount, refresh_ask=True)

        @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(
                remaining_satoshi, 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()

        activities = self.get_activities(authentication_headers, 0, False)

        expected = (
            {
                'activity_type': v1_pb2.ACTIVITY_SELL,
                'fiat_amount': fiat_amount,
                'activity_id': '__SELL__{}'.format(ask_id)
            },
            {
                'activity_type': v1_pb2.ACTIVITY_RECV,
                'satoshi': self._SATOSHI,
                'activity_id': '__BTC__{}'.format(tx_id)
            }
        )
        self.assertEqual(len(expected), len(activities))
        for cur_expected, cur_activity in zip(expected, activities):
            for k, v in cur_expected.items():
                self.assertEqual(v, getattr(cur_activity, k))

    @moving_coins
    def test_sell_all(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(Satoshi(self._SATOSHI // 2), authentication_headers)
        self.sell_btc(authentication_headers, bip32_private_key)

        @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,
                msg='Wallet has {} satoshi'.format(cur_wallet_info.confirmed_satoshi_amount))
            self.assertEqual(0, cur_wallet_info.unconfirmed_satoshi_amount)

        _f()
