import datetime
import json

import retrying

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


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

    def test_buy_btc_and_then_withdraw(self):
        self._test_buy_btc(True)

    def test_buy_btc_and_then_sell(self):
        self._test_buy_btc(False)

    @moving_coins
    def _test_buy_btc(self, withdraw_btc: bool):
        self.ensure_deposit_wallet()
        print('Deposit ok')
        external_reference_payment_method_id = 'lambda-bff-sdk-test-{}'.format(datetime.datetime.now())
        signup_response = self.signin_with_new_user()
        authentication_headers = signup_response.authentication_headers
        self.verify_limits(authentication_headers)
        bid_id_tokenization = self.buy_btc(
            signup_response.signup_message.conioCredentials.externalUserID,
            authentication_headers, external_reference_payment_method_id,
            satoshi_amount=self._SATOSHI, refresh_bid=True)
        cur_wallet_info = self.get_wallet_info(authentication_headers)
        self.assertEqual(
            self._SATOSHI, 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)

        bid_id_tokenized_payment_method = self.buy_btc(
            signup_response.signup_message.conioCredentials.externalUserID,
            authentication_headers, external_reference_payment_method_id,
            satoshi_amount=Satoshi(self._SATOSHI // 2), refresh_bid=True)
        cur_wallet_info = self.get_wallet_info(authentication_headers)
        self.assertEqual(
            self._SATOSHI * 3 // 2, 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)

        activities = self.get_activities(authentication_headers, 0, False)
        expected = (
            {
                'activity_type': v1_pb2.ACTIVITY_BUY,
                'satoshi': self._SATOSHI // 2,
                'activity_id': '__BID__{}'.format(bid_id_tokenized_payment_method)
            },
            {
                'activity_type': v1_pb2.ACTIVITY_BUY,
                'satoshi': self._SATOSHI,
                'activity_id': '__BID__{}'.format(bid_id_tokenization)
            }
        )
        # This must not fail
        self.get_activity('__BID__{}'.format(bid_id_tokenization), authentication_headers)
        self.assertEqual(len(expected), len(activities))
        buy_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))
            buy_limits -= cur_activity.fiat_amount
        self.verify_limits(authentication_headers, buy_limits=buy_limits)
        if withdraw_btc:
            print(self.withdraw_btc(
                signup_response.bip32_private_key,
                authentication_headers,
                signup_response.signup_message.cryptoRequest.email,
                self._SATOSHI // 2
            ))
        else:
            print(self.sell_btc(
                authentication_headers,
                signup_response.bip32_private_key,
                satoshi_amount=Satoshi(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))
            cur_wallet_info_after_sending_btc = self.get_wallet_info(authentication_headers)
            print('Got {} satoshi'.format(cur_wallet_info_after_sending_btc.confirmed_satoshi_amount))
            self.assertAlmostEqual(
                float(self._SATOSHI),
                cur_wallet_info_after_sending_btc.confirmed_satoshi_amount,
                delta=70000,
                msg='Wallet has {} satoshi'.format(cur_wallet_info_after_sending_btc.confirmed_satoshi_amount))
            self.assertEqual(0, cur_wallet_info_after_sending_btc.unconfirmed_satoshi_amount)
        _f()

        user = ioc_vault.vault_client.get_user_info_by_attributes(
                    external_reference=signup_response.signup_message.conioCredentials.externalUserID
        )
        payment_methods = self.get_credit_card_payment_methods(user.cards_integration_id)
        self.assertEqual(1, len(payment_methods))
        self.assertEqual([external_reference_payment_method_id], payment_methods[0].external_references)
        another_external_reference_payment_method_id = 'lambda-bff-sdk-test-{}'.format(datetime.datetime.now())
        self.buy_btc(
            signup_response.signup_message.conioCredentials.externalUserID,
            authentication_headers, another_external_reference_payment_method_id,
            satoshi_amount=Satoshi(self._SATOSHI // 3), refresh_bid=True)
        payment_methods = self.get_credit_card_payment_methods(user.cards_integration_id)
        self.assertEqual(2, len(payment_methods))
        self.assertEqual(
            {external_reference_payment_method_id, another_external_reference_payment_method_id},
            set(map(lambda d: d.external_references[0], payment_methods))
        )
        cur_wallet_info_after_a_buy_with_different_payment_method = self.get_wallet_info(authentication_headers)
        self.assertAlmostEqual(
            float(self._SATOSHI * 4 // 3),
            cur_wallet_info_after_a_buy_with_different_payment_method.confirmed_satoshi_amount,
            delta=70000,
            msg='Wallet has {} satoshi'.format(cur_wallet_info_after_a_buy_with_different_payment_method.confirmed_satoshi_amount))
        self.assertEqual(0, cur_wallet_info_after_a_buy_with_different_payment_method.unconfirmed_satoshi_amount)
        # This must not fail (again, here we have the BTC transaction)
        self.get_activity('__BID__{}'.format(bid_id_tokenization), authentication_headers)

    def test_buy_btc_limits_exceeded(self):
        external_reference_payment_method_id = 'lambda-bff-sdk-test-{}'.format(datetime.datetime.now())
        signup_response = self.signin_with_new_user()
        authentication_headers = signup_response.authentication_headers
        failed_create = self.buy_btc(
            signup_response.signup_message.conioCredentials.externalUserID,
            authentication_headers, external_reference_payment_method_id,
            satoshi_amount=Satoshi(10000000000),
            expected_create_response=400,
            expected_pay_response=400)
        self.assertEqual({'error_code': 'TRADING_LIMITS_EXCEEDED', 'arg': ''}, json.loads(failed_create.content))

    def test_buy_btc_limits_too_low(self):
        external_reference_payment_method_id = 'lambda-bff-sdk-test-{}'.format(datetime.datetime.now())
        signup_response = self.signin_with_new_user()
        authentication_headers = signup_response.authentication_headers
        failed_create = self.buy_btc(
            signup_response.signup_message.conioCredentials.externalUserID,
            authentication_headers, external_reference_payment_method_id,
            satoshi_amount=Satoshi(1000),
            expected_create_response=422)
        data = json.loads(failed_create.content)
        self.assertEqual('FIAT_AMOUNT_TOO_LOW', data['error_code'])
        self.assertNotEqual('', data['arg'])
        # This must NOT fail
        float(data['arg'])
