import os
import unittest
from unittest import mock

import bitcoin
from aws_lambda_tools.common.user_type import ConioUser
from conio_domain.wallet import WalletCryptoCurrency
from core_wallet_client.client.client import CoreWalletClient
from requests import HTTPError, Response
from vault_client.client.client_definition import VaultClient
from vault_client.generated_protobuf import vault_pb2

from conio_sdk.common import exceptions
from conio_sdk.services.conio.wallet.bitcoin_wallet_service import WalletEncryptedData, EncryptedData
from conio_sdk.services.conio.wallet.core_wallet_service import CoreWalletService


class TestCoreWalletService(unittest.TestCase):
    def setUp(self):
        self.core_wallet_client = mock.create_autospec(CoreWalletClient)
        self.vault_client = mock.create_autospec(VaultClient)
        self.user = mock.create_autospec(ConioUser)
        self.sut = CoreWalletService(self.core_wallet_client, self.vault_client)

    def test_new_user(self):
        wallet_encrypted_data = WalletEncryptedData(
            encrypted_mnemonic=EncryptedData(os.urandom(16)),
            encrypted_seed=EncryptedData(os.urandom(16)),
            encrypted_private_key=EncryptedData(os.urandom(16)),
            bip32_public_key=bitcoin.bip32_privtopub(
                bitcoin.bip32_master_key(os.urandom(16), vbytes=bitcoin.TESTNET_PRIVATE)
            ),
            wallet_id=None
        )
        self.user.get_wallet.return_value = None
        response = Response()
        response.status_code = 404
        self.core_wallet_client.get_wallet_id_from_user_master_public_key.side_effect = HTTPError(response=response)
        self.core_wallet_client.create_wallet.return_value = 'a wallet id'
        self.core_wallet_client.get_signature_data.return_value = \
            mock.Mock(bip32_public_key=wallet_encrypted_data.bip32_public_key)
        result = self.sut.create_wallet_if_not_exists(self.user, wallet_encrypted_data)

        self.assertEqual(wallet_encrypted_data.encrypted_mnemonic, result.encrypted_mnemonic)
        self.assertEqual(wallet_encrypted_data.encrypted_seed, result.encrypted_seed)
        self.assertEqual(wallet_encrypted_data.encrypted_private_key, result.encrypted_private_key)
        self.assertEqual(wallet_encrypted_data.bip32_public_key, result.bip32_public_key)

        self.user.get_wallet.assert_called_once_with(WalletCryptoCurrency.BITCOIN)
        self.core_wallet_client.get_wallet_id_from_user_master_public_key.assert_called_once_with(
            wallet_encrypted_data.bip32_public_key
        )
        self.core_wallet_client.create_wallet.assert_called_once_with(wallet_encrypted_data.bip32_public_key)
        self.vault_client.save_user_info_v2.assert_called_once_with(
                {
                    'wallets': [
                        {
                            'reference_id': self.core_wallet_client.create_wallet.return_value,
                            'encrypted_seed': wallet_encrypted_data.encrypted_seed,
                            'encrypted_private_key': wallet_encrypted_data.encrypted_private_key,
                            'encrypted_mnemonic': wallet_encrypted_data.encrypted_mnemonic,
                            'crypto_currency': vault_pb2.BITCOIN
                        }
                    ]}, self.user.reference_key_id)

    def test_wallet_already_exists(self):
        wallet_encrypted_data = WalletEncryptedData(
            encrypted_mnemonic=EncryptedData(os.urandom(16)),
            encrypted_seed=EncryptedData(os.urandom(16)),
            encrypted_private_key=EncryptedData(os.urandom(16)),
            bip32_public_key=bitcoin.bip32_privtopub(
                bitcoin.bip32_master_key(os.urandom(16), vbytes=bitcoin.TESTNET_PRIVATE)
            ),
            wallet_id=None
        )
        self.user.get_wallet.return_value = {
            'encrypted_mnemonic': wallet_encrypted_data.encrypted_mnemonic,
            'encrypted_seed': wallet_encrypted_data.encrypted_seed,
            'encrypted_private_key': wallet_encrypted_data.encrypted_private_key,
            'reference_id': 'a wallet id'
        }
        self.core_wallet_client.get_signature_data.return_value = \
            mock.Mock(bip32_public_key=wallet_encrypted_data.bip32_public_key)

        result = self.sut.create_wallet_if_not_exists(self.user, wallet_encrypted_data)
        self.assertEqual(result.encrypted_mnemonic, wallet_encrypted_data.encrypted_mnemonic)
        self.assertEqual(result.encrypted_seed, wallet_encrypted_data.encrypted_seed)
        self.assertEqual(result.encrypted_private_key, wallet_encrypted_data.encrypted_private_key)
        self.assertEqual(result.bip32_public_key, wallet_encrypted_data.bip32_public_key)
        self.assertEqual('a wallet id', result.wallet_id)

        self.core_wallet_client.get_signature_data.assert_called_once_with('a wallet id')

    def test_wallet_already_owned_by_another_user(self):
        wallet_encrypted_data = WalletEncryptedData(
            encrypted_mnemonic=EncryptedData(os.urandom(16)),
            encrypted_seed=EncryptedData(os.urandom(16)),
            encrypted_private_key=EncryptedData(os.urandom(16)),
            bip32_public_key=bitcoin.bip32_privtopub(
                bitcoin.bip32_master_key(os.urandom(16), vbytes=bitcoin.TESTNET_PRIVATE)
            ),
            wallet_id=None
        )
        self.user.get_wallet.return_value = None
        # self.core_wallet_client.get_wallet_id_from_user_master_public_key.return_value = None
        self.core_wallet_client.create_wallet.return_value = 'a wallet id'
        self.core_wallet_client.get_signature_data.return_value = \
            mock.Mock(bip32_public_key=wallet_encrypted_data.bip32_public_key)
        with self.assertRaises(exceptions.WalletAlreadyOwnedByAnotherUserException):
            self.sut.create_wallet_if_not_exists(self.user, wallet_encrypted_data)

        self.user.get_wallet.assert_called_once_with(WalletCryptoCurrency.BITCOIN)
        self.core_wallet_client.get_wallet_id_from_user_master_public_key.assert_called_once_with(
            wallet_encrypted_data.bip32_public_key
        )
        self.core_wallet_client.create_wallet.assert_not_called()
        self.vault_client.save_user_info_v2.assert_not_called()
