# coding=utf-8
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
import json
import os
import shutil
import tempfile
from unittest import TestCase, mock

from click.testing import CliRunner

from qrl.cli import qrl as qrl_cli
from qrl.core import config
from qrl.core.misc import logger
from qrl.generated import qrl_pb2

logger.initialize_default()

unsigned_tx = b"\nOQ010200be640405bb61d104e329cc94b6807b6e713ef0dea80aa5aa73abff8dd88946b41172569c\x1aC\x01\x02\x00\x80\x9dg/U\xb1N\xf2_\x0e~j%\xb2\x15\x05\xa7y\x19\x8f\xc0>\x05`\x90\xe3>\xaa\x9a(\xd3\xc7U\x91\xbab\x90{\xaa^\xadQ\xca\xbf\xd3\xbc\xd9\x93\xf0:D\xca\xd8v\x97\x08\xa8x\x9c-\n4\xd6e:,\n'\x01\x04\x00\xd9\xf1\xef\xe5\xb2r\xe0B\xdc\xc8\xefi\x0f\x0e\x90\xca\x8b\x0bn\xdb\xa0\xd2o\x81\xe7\xaf\xf1*gT\xb2\x17\x88\x16\x9f\x7f\x12\x01\x00"  # noqa
bin2hstr_unsigned_tx = '1a4301030038ea6375069f8272cc1a6601b3c76c21519455603d370036b97c779ada3565854e3983bd564298c49ae2e7fa6e28d4b954d8cd59398f1225b08d6144854aee0e3a8c010a2701050000a31343e48abce29464f063b30827ecf2c552743cab13d4f3b457b55410b548a0f8bca60a270105009ca50ed86497e6b2cfcca8c525191c741220eacf79a697e078bf9b53a9899b17b3f839d10a27010500e135decb3328a27e51c47064df2dbbe799e79812291cc5e7cfad08a82a62d64e4fa813aa120f8094ebdc0380a8d6b90780bcc1960b'  # noqa
bin2hstr_unsigned_tx2 = '0a2701050000a31343e48abce29464f063b30827ecf2c552743cab13d4f3b457b55410b548a0f8bca61a4301030038ea6375069f8272cc1a6601b3c76c21519455603d370036b97c779ada3565854e3983bd564298c49ae2e7fa6e28d4b954d8cd59398f1225b08d6144854aee0e3a2c0a270105009ca50ed86497e6b2cfcca8c525191c741220eacf79a697e078bf9b53a9899b17b3f839d1120100'  # noqa
bin2hstr_signed_tx = ' '  # noqa

qaddr_1 = "Q01050000a31343e48abce29464f063b30827ecf2c552743cab13d4f3b457b55410b548a0f8bca6"
qaddr_2 = "Q0105009ca50ed86497e6b2cfcca8c525191c741220eacf79a697e078bf9b53a9899b17b3f839d1"
qaddr_3 = "Q010500e135decb3328a27e51c47064df2dbbe799e79812291cc5e7cfad08a82a62d64e4fa813aa"
qaddr_4 = "Q0105001fa3a2038c0b4b947cccbf589d53fc7749f0a04f118ec1ffadedd1b4db37e83c513aa259"


def open_wallet(filename="wallet.json"):
    with open(filename) as f:
        return json.load(f)


@mock.patch('qrl.cli.grpc.insecure_channel', new=mock.MagicMock())
class TestCLI_Wallet_Gen(TestCase):
    def __init__(self, *args, **kwargs):
        super(TestCLI_Wallet_Gen, self).__init__(*args, **kwargs)

    def setUp(self):
        self.runner = CliRunner(env={"LC_ALL": "C.UTF-8", "LANG": "C.UTF-8"})
        self.prev_dir = os.getcwd()
        self.temp_dir = tempfile.mkdtemp()

        os.chdir(self.temp_dir)

    def tearDown(self):
        del self.runner  # running this test suite often results in leaked pipe handles. This could mitigate that.
        os.chdir(self.prev_dir)
        shutil.rmtree(self.temp_dir)

    def wallet_gen_default_height(self):
        result = self.runner.invoke(qrl_cli, ["wallet_gen"])
        wallet = open_wallet()
        self.assertIn(self.temp_dir, result.output)
        self.assertEqual(wallet["addresses"][0]["height"], 12)

    def test_wallet_gen_different_height(self):
        result = self.runner.invoke(qrl_cli, ["wallet_gen", "--height=4"])
        wallet = open_wallet()
        self.assertIn(self.temp_dir, result.output)
        self.assertEqual(wallet["addresses"][0]["height"], 4)

    def test_wallet_gen_different_hash_function(self):
        result = self.runner.invoke(qrl_cli, ["wallet_gen", "--height=4", "--hash_function=sha2_256"])
        wallet = open_wallet()
        self.assertIn(self.temp_dir, result.output)
        self.assertEqual(wallet["addresses"][0]["hashFunction"], "sha2_256")

    def test_wallet_gen_json(self):
        result = self.runner.invoke(qrl_cli, ["--json", "wallet_gen", "--height=4"])
        self.assertTrue(json.loads(result.output))  # Throws an exception if output is not valid JSON

    def test_wallet_gen_encrypt(self):
        result = self.runner.invoke(qrl_cli, ["wallet_gen", "--height=4", "--encrypt"], input='password\npassword\n')
        wallet = open_wallet()
        self.assertIn(self.temp_dir, result.output)
        self.assertTrue(wallet["encrypted"])


@mock.patch('qrl.cli.grpc.insecure_channel', new=mock.MagicMock())
class TestCLI(TestCase):
    def __init__(self, *args, **kwargs):
        super(TestCLI, self).__init__(*args, **kwargs)

    def setUp(self):
        self.runner = CliRunner(env={"LC_ALL": "C.UTF-8", "LANG": "C.UTF-8"})
        self.prev_dir = os.getcwd()
        self.temp_dir = tempfile.mkdtemp()
        os.chdir(self.temp_dir)
        self.runner.invoke(qrl_cli, ["wallet_gen", "--height=4"])

    def tearDown(self):
        os.chdir(self.prev_dir)
        shutil.rmtree(self.temp_dir)

    def test_wallet_ls(self):
        result = self.runner.invoke(qrl_cli, ["wallet_ls"])
        wallet = open_wallet()
        self.assertIn(wallet["addresses"][0]["address"], result.output)
        self.assertIn(self.temp_dir, result.output)  # You should know which wallet you've opened.

    def test_wallet_ls_verbose(self):
        result = self.runner.invoke(qrl_cli, ["-v", "wallet_ls"])
        wallet = open_wallet()
        self.assertIn(wallet["addresses"][0]["hashFunction"], result.output)

    def test_wallet_ls_empty(self):
        os.remove("wallet.json")
        result = self.runner.invoke(qrl_cli, ["wallet_ls"])
        self.assertIn("No wallet found", result.output)

    def test_wallet_ls_json(self):
        result = self.runner.invoke(qrl_cli, ["--json", "wallet_ls"])
        wallet = open_wallet()
        self.assertTrue(json.loads(result.output))  # Throws an exception if output is not valid JSON
        self.assertIn(wallet["addresses"][0]["address"], result.output)
        self.assertIn(self.temp_dir, result.output)  # You should know which wallet you've opened.

    def test_wallet_add(self):
        result = self.runner.invoke(qrl_cli, ["wallet_add", "--height=4"])
        wallet = open_wallet()
        self.assertIn(wallet["addresses"][1]["address"], result.output)
        self.assertEqual(wallet["addresses"][1]["height"], 4)

    def test_wallet_add_inherit_encryption_status(self):
        self.runner.invoke(qrl_cli, ["wallet_encrypt"], input='password\npassword\n')
        self.runner.invoke(qrl_cli, ["wallet_add", "--height=4"], input='password\n')
        wallet = open_wallet()
        self.assertTrue(wallet["encrypted"])

    def test_wallet_add_different_hash_function(self):
        self.runner.invoke(qrl_cli, ["wallet_add", "--height=4", "--hash_function=shake256"])
        wallet = open_wallet()
        self.assertEqual(wallet["addresses"][0]["hashFunction"], "shake128")
        self.assertEqual(wallet["addresses"][1]["hashFunction"], "shake256")

    def test_wallet_recover_hexseed(self):
        os.rename("wallet.json", "wallet_orig.json")
        wallet_orig = open_wallet("wallet_orig.json")
        self.runner.invoke(qrl_cli, ["wallet_recover", "--seed-type=hexseed"],
                           input='\n'.join([wallet_orig["addresses"][0]["hexseed"], 'y']))
        wallet_recovered = open_wallet()
        self.assertEqual(wallet_recovered, wallet_orig)

    def test_wallet_recover_hexseed_invalid_input(self):
        os.rename("wallet.json", "wallet_orig.json")
        wallet_orig = open_wallet("wallet_orig.json")
        result = self.runner.invoke(qrl_cli, ["wallet_recover", "--seed-type=hexseed"],
                                    input='\n'.join([wallet_orig["addresses"][0]["hexseed"] + "deadbeef", 'y']))
        self.assertIn('Hexseed must be of only', result.output)
        self.assertFalse(os.path.exists("wallet.json"))

        # If the recovered address is already in the wallet, it should react in this way.
        self.runner.invoke(qrl_cli, ["wallet_recover", "--seed-type=hexseed"],
                           input='\n'.join([wallet_orig["addresses"][0]["hexseed"], 'y']))
        result = self.runner.invoke(qrl_cli, ["wallet_recover", "--seed-type=hexseed"],
                                    input='\n'.join([wallet_orig["addresses"][0]["hexseed"], 'y']))
        self.assertEqual(result.exit_code, 0)
        self.assertIn('Wallet Address is already in the wallet list', result.output.strip())

    def test_wallet_recover_mnemonic(self):
        os.rename("wallet.json", "wallet_orig.json")
        wallet_orig = open_wallet("wallet_orig.json")
        self.runner.invoke(qrl_cli, ["wallet_recover", "--seed-type=mnemonic"],
                           input='\n'.join([wallet_orig["addresses"][0]["mnemonic"], 'y']))
        wallet_recovered = open_wallet()
        self.assertEqual(wallet_recovered, wallet_orig)

    def test_wallet_recover_mnemonic_invalid_input(self):
        os.rename("wallet.json", "wallet_orig.json")
        wallet_orig = open_wallet("wallet_orig.json")
        result = self.runner.invoke(qrl_cli, ["wallet_recover", "--seed-type=mnemonic"],
                                    input='\n'.join([wallet_orig["addresses"][0]["mnemonic"] + " bad", 'y']))
        self.assertIn('Mnemonic seed must contain only 34 words', result.output)
        self.assertFalse(os.path.exists("wallet.json"))

        # If the recovered address is already in the wallet, it should react in this way.
        self.runner.invoke(qrl_cli, ["wallet_recover", "--seed-type=mnemonic"],
                           input='\n'.join([wallet_orig["addresses"][0]["mnemonic"], 'y']))
        result = self.runner.invoke(qrl_cli, ["wallet_recover", "--seed-type=mnemonic"],
                                    input='\n'.join([wallet_orig["addresses"][0]["mnemonic"], 'y']))
        self.assertEqual(result.exit_code, 0)
        self.assertIn('Wallet Address is already in the wallet list', result.output.strip())

    def test_wallet_secret(self):
        wallet = open_wallet()
        result = self.runner.invoke(qrl_cli, ["wallet_secret", "--wallet-idx=0"])
        self.assertIn(wallet["addresses"][0]["address"], result.output)
        self.assertIn(wallet["addresses"][0]["mnemonic"], result.output)
        self.assertIn(wallet["addresses"][0]["hexseed"], result.output)

    def test_wallet_secret_encrypted(self):
        wallet = open_wallet()
        self.runner.invoke(qrl_cli, ["wallet_encrypt"], input='password\npassword\n')
        result = self.runner.invoke(qrl_cli, ["wallet_secret", "--wallet-idx=0"], input='password\npassword\n')
        self.assertIn(wallet["addresses"][0]["address"], result.output)
        self.assertIn(wallet["addresses"][0]["mnemonic"], result.output)
        self.assertIn(wallet["addresses"][0]["hexseed"], result.output)

    def test_wallet_secret_encrypt_decrypt_wrong(self):
        wallet = open_wallet()
        self.runner.invoke(qrl_cli, ["wallet_encrypt"], input='password\npassword\n')
        result = self.runner.invoke(qrl_cli, ["wallet_secret", "--wallet-idx=0"], input='password\npassword\n')
        self.assertIn(wallet["addresses"][0]["address"], result.output)
        self.assertIn(wallet["addresses"][0]["mnemonic"], result.output)
        self.assertIn(wallet["addresses"][0]["hexseed"], result.output)

        result = self.runner.invoke(qrl_cli, ["wallet_decrypt"], input='wrong_password\nwrong_password\n')
        self.assertEquals(1, result.exit_code)
        # TODO: Add appropriate error matching

    def test_wallet_secret_invalid_input(self):
        result = self.runner.invoke(qrl_cli, ["wallet_secret", "--wallet-idx=2"])
        self.assertEqual(result.output.strip(), 'Wallet index not found 2')

    def test_wallet_rm(self):
        self.runner.invoke(qrl_cli, ["wallet_add", "--height=4"])
        wallet = open_wallet()
        result = self.runner.invoke(qrl_cli, ["wallet_rm", "--wallet-idx=1", "--skip-confirmation"], input='y\n')
        self.assertNotIn(wallet["addresses"][1]["address"], result.output)
        result = self.runner.invoke(qrl_cli, ["wallet_rm", "--wallet-idx=0"], input='y\n')
        self.assertIn("No wallet found", result.output)

    def test_wallet_rm_invalid_input(self):
        result = self.runner.invoke(qrl_cli, ["wallet_rm", "--wallet-idx=2", "--skip-confirmation"], input='y\n')
        self.assertEqual(result.output.strip(), 'Wallet index not found 2')

    @mock.patch('qrl.cli.qrl_pb2_grpc.PublicAPIStub', autospec=True)
    def test_state(self, mock_stub):
        m_node_state_resp_serialized_to_string = b'\n/\n\x160.61.3+ngbd52b1a.dirty\x10\x03\x18\x02 D(\xa7\x010\x83!B\tExcession'
        m_node_state_resp = qrl_pb2.GetNodeStateResp()
        m_node_state_resp.ParseFromString(m_node_state_resp_serialized_to_string)

        mock_stub_instance = mock.MagicMock(name='this should be qrl_pb2_grpc.PublicAPIStub(channel)')
        mock_stub_instance.GetNodeState.return_value = m_node_state_resp

        mock_stub.name = 'this should be qrl_pb2_grpc.PublicAPIStub'
        mock_stub.return_value = mock_stub_instance

        result = self.runner.invoke(qrl_cli, ["state"])
        self.assertEqual(result.exit_code, 0)
        self.assertIn('Excession', result.output)

    @mock.patch('qrl.cli.qrl_pb2_grpc.PublicAPIStub', autospec=True)
    def test_state_json(self, mock_stub):
        m_node_state_resp_serialized = b'\n/\n\x160.61.3+ngbd52b1a.dirty\x10\x03\x18\x02 D(\xa7\x010\x83!B\tExcession'
        m_node_state_resp = qrl_pb2.GetNodeStateResp()
        m_node_state_resp.ParseFromString(m_node_state_resp_serialized)

        mock_stub_instance = mock.MagicMock(name='this should be qrl_pb2_grpc.PublicAPIStub(channel)')
        mock_stub_instance.GetNodeState.return_value = m_node_state_resp

        mock_stub.name = 'this should be qrl_pb2_grpc.PublicAPIStub'
        mock_stub.return_value = mock_stub_instance

        result = self.runner.invoke(qrl_cli, ["--json", "state"])
        print(result.output)
        print(result.exc_info)
        self.assertEqual(result.exit_code, 0)

        the_output = json.loads(result.output)
        self.assertEqual(the_output["info"]["networkId"], "Excession")

    @mock.patch('qrl.cli.config', autospec=True)
    @mock.patch('qrl.cli.Transaction', autospec=True)
    @mock.patch('qrl.cli.qrl_pb2_grpc.PublicAPIStub', autospec=True)
    def test_slave_tx_generate(self, mock_stub, mock_transaction, mock_config):
        # Mock out xmss_tree_height so this test runs faster!
        # But first, we must copy all attributes from the real config into the mock.
        # autospec does not do this for us, only the specification.
        mock_config.configure_mock(**config.__dict__)
        mock_config.dev.xmss_tree_height = 4

        mock_tx_attrs = {"name": "a mock tx object", "to_json.return_value": "{}"}
        mock_tx = mock.MagicMock(**mock_tx_attrs)

        mock_transaction.name = 'mock Transaction class'
        mock_transaction.from_pbdata.return_value = mock_tx

        mock_slave_txn_resp = mock.MagicMock(name='mock slaveTxnResp')
        mock_slave_txn_resp.extended_transaction_unsigned.tx = bin2hstr_unsigned_tx

        mock_stub_instance = mock.MagicMock(name='this should be qrl_pb2_grpc.PublicAPIStub(channel)')
        mock_stub_instance.GetSlaveTxn.return_value = mock_slave_txn_resp

        mock_stub.name = 'this should be qrl_pb2_grpc.PublicAPIStub'
        mock_stub.return_value = mock_stub_instance

        wallet = open_wallet()
        master_address = wallet["addresses"][0]["address"]

        # Simplest use case
        result = self.runner.invoke(qrl_cli, [
            "slave_tx_generate", "--src=0", "--master={}".format(master_address),
            "--number_of_slaves=1", "--access_type=0", "--fee=0"
        ])
        print(result.output)
        print(result.exc_info)
        self.assertEqual(result.exit_code, 0)
        self.assertTrue(os.path.exists('slaves.json'))

        # Does it work with 5 slaves?
        os.remove('slaves.json')
        result = self.runner.invoke(qrl_cli, [
            "slave_tx_generate", "--src=0", "--master={}".format(master_address),
            "--number_of_slaves=5", "--access_type=0", "--fee=0"
        ])
        self.assertEqual(result.exit_code, 0)
        self.assertTrue(os.path.exists('slaves.json'))

        # access_type could be 0 or 1. test 1.
        os.remove('slaves.json')
        result = self.runner.invoke(qrl_cli, [
            "slave_tx_generate", "--src=0", "--master={}".format(master_address),
            "--number_of_slaves=5", "--access_type=1", "--fee=0"
        ])
        self.assertEqual(result.exit_code, 0)
        self.assertTrue(os.path.exists('slaves.json'))

    @mock.patch('qrl.cli.config', autospec=True)
    @mock.patch('qrl.cli.Transaction', autospec=True)
    @mock.patch('qrl.cli.qrl_pb2_grpc.PublicAPIStub', autospec=True)
    def test_slave_tx_generate_invalid_input(self, mock_stub, mock_Transaction, mock_config):
        mock_config.configure_mock(**config.__dict__)
        mock_config.dev.xmss_tree_height = 4

        mock_tx_attrs = {"name": "a mock tx object", "to_json.return_value": "{}"}
        mock_tx = mock.MagicMock(**mock_tx_attrs)

        mock_Transaction.name = 'mock Transaction class'
        mock_Transaction.from_pbdata.return_value = mock_tx

        mock_slaveTxnResp = mock.MagicMock(name='mock slaveTxnResp')
        mock_slaveTxnResp.extended_transaction_unsigned.tx = bin2hstr_unsigned_tx

        mock_stub_instance = mock.MagicMock(name='this should be qrl_pb2_grpc.PublicAPIStub(channel)')
        mock_stub_instance.GetSlaveTxn.return_value = mock_slaveTxnResp

        mock_stub.name = 'this should be qrl_pb2_grpc.PublicAPIStub'
        mock_stub.return_value = mock_stub_instance

        wallet = open_wallet()
        master_address = wallet["addresses"][0]["address"]

        # It shouldn't allow > 100 slaves.
        result = self.runner.invoke(qrl_cli, [
            "slave_tx_generate", "--src=0", "--master={}".format(master_address),
            "--number_of_slaves=101", "--access_type=0", "--fee=0"
        ])
        self.assertNotEqual(result.exit_code, 0)
        self.assertFalse(os.path.exists('slaves.json'))

        # # Access types other than 0, 1 shouldn't work
        # result = self.runner.invoke(qrl_cli, [
        #     "slave_tx_generate", "--src=0", "--master={}".format(master_address),
        #     "--number_of_slaves=1", "--access_type=2", "--fee=0"
        # ])
        # self.assertNotEqual(result.exit_code, 0)
        # self.assertFalse(os.path.exists('slaves.json'))

        # Negative fees shoudn't work.
        result = self.runner.invoke(qrl_cli, [
            "slave_tx_generate", "--src=0", "--master={}".format(master_address),
            "--number_of_slaves=1", "--access_type=0", "--fee=-1"
        ])
        self.assertNotEqual(result.exit_code, 0)
        self.assertFalse(os.path.exists('slaves.json'))

    def test_tx_inspect(self):
        result = self.runner.invoke(qrl_cli, ["tx_inspect", "--txblob", bin2hstr_unsigned_tx])
        self.assertTrue(json.loads(result.output))

    def test_tx_inspect_invalid_input(self):
        result = self.runner.invoke(qrl_cli, ["tx_inspect", "--txblob", bin2hstr_unsigned_tx[2:]])
        self.assertNotEqual(result.exit_code, 0)
        self.assertIn('is not valid', result.output)

    @mock.patch('qrl.cli.qrl_pb2_grpc.PublicAPIStub', autospec=True)
    def test_tx_push(self, mock_stub):
        mock_error_code = 'Error? What error? This is a test'
        mock_push_transaction_resp = mock.MagicMock(name='this should be pushTransactionResp',
                                                    error_code=mock_error_code)

        attrs = {"name": "this should be stub.PushTransaction",
                 "PushTransaction.return_value": mock_push_transaction_resp}
        mock_stub_instance = mock.MagicMock(**attrs)

        mock_stub.name = 'this should be qrl_pb2_grpc.PublicAPIStub'
        mock_stub.return_value = mock_stub_instance

        result = self.runner.invoke(qrl_cli, ["tx_push", "--txblob", bin2hstr_signed_tx])
        self.assertIn(mock_error_code, result.output)

    @mock.patch('qrl.cli.qrl_pb2_grpc.PublicAPIStub', autospec=True)
    def test_tx_push_invalid_input(self, mock_stub):
        mock_error_code = 'Error? What error? This is a test'
        mock_push_transaction_resp = mock.MagicMock(name='this should be pushTransactionResp',
                                                    error_code=mock_error_code)

        attrs = {"name": "this should be stub.PushTransaction",
                 "PushTransaction.return_value": mock_push_transaction_resp}
        mock_stub_instance = mock.MagicMock(**attrs)

        mock_stub.name = 'this should be qrl_pb2_grpc.PublicAPIStub'
        mock_stub.return_value = mock_stub_instance

        # txblob should already be signed
        result = self.runner.invoke(qrl_cli, ["tx_push", "--txblob", bin2hstr_unsigned_tx])
        self.assertEqual(result.exit_code, 1)
        self.assertIn('Signature missing', result.output)

        # It should choke on invalid txblobs
        result = self.runner.invoke(qrl_cli, ["tx_push", "--txblob", bin2hstr_signed_tx[2:]])
        self.assertEqual(result.exit_code, 1)
        self.assertEqual(result.output.strip(), 'tx blob is not valid')

    @mock.patch('qrl.cli.qrl_pb2_grpc.PublicAPIStub', autospec=True)
    def test_tx_transfer(self, mock_stub):
        tx_pbdata_serialized_to_string = b"\n\x02\\n\x1aC\x01\x02\x00\x80\x9dg/U\xb1N\xf2_\x0e~j%\xb2\x15\x05\xa7y\x19\x8f\xc0>\x05`\x90\xe3>\xaa\x9a(\xd3\xc7U\x91\xbab\x90{\xaa^\xadQ\xca\xbf\xd3\xbc\xd9\x93\xf0:D\xca\xd8v\x97\x08\xa8x\x9c-\n4\xd6e:,\n'\x01\x06\x00\x95O\x16\xafx\xe3\x94Y\rc\x7f\x10D\xcd\x9f\xaf<\xc7\xf4\xa2\x93f\xaa\r\x8c\xa3 \xe40\x0bZ\xfe\xb0xy\xbb\x12\x01\x00"  # noqa
        mock_tx = qrl_pb2.Transaction()
        mock_tx.ParseFromString(tx_pbdata_serialized_to_string)

        m_transfer_coins_resp = mock.MagicMock(name='a fake transferCoinsResp')
        m_transfer_coins_resp.extended_transaction_unsigned.tx = mock_tx
        m_push_transaction_resp = mock.MagicMock(name='a fake pushTransactionResp', error_code=3)

        attrs = {
            "name": "my fake stub",
            "TransferCoins.return_value": m_transfer_coins_resp,
            "PushTransaction.return_value": m_push_transaction_resp
        }
        mock_stub_instance = mock.MagicMock(**attrs)

        mock_stub.name = 'a fake qrl_pb2_grpc.PublicAPIStub'
        mock_stub.return_value = mock_stub_instance

        wallet = open_wallet()

        # Simplest use case
        result = self.runner.invoke(qrl_cli, [
            "tx_transfer",
            "--src=0",
            "--master=",
            "--dsts={}".format(qaddr_1),
            "--amounts=1",
            "--fee=0",
            "--ots_key_index=0"
        ])
        self.assertEqual(result.exit_code, 0)
        print(result.output)
        self.assertIn('a fake pushTransactionResp', result.output.strip())

        # Should work with src=Qaddress as well
        result = self.runner.invoke(qrl_cli, [
            "tx_transfer",
            "--src={}".format(wallet["addresses"][0]["address"]),
            "--master=",
            "--dsts={}".format(qaddr_1),
            "--amounts=1",
            "--fee=0",
            "--ots_key_index=0"
        ])
        self.assertEqual(result.exit_code, 0)
        self.assertIn('a fake pushTransactionResp', result.output.strip())

        # Master should also work with a Qaddress.
        result = self.runner.invoke(qrl_cli, [
            "tx_transfer",
            "--src={}".format(wallet["addresses"][0]["address"]),
            "--master={}".format(qaddr_2),
            "--dsts={}".format(qaddr_1),
            "--amounts=1",
            "--fee=0",
            "--ots_key_index=0"
        ])
        self.assertEqual(result.exit_code, 0)
        self.assertIn('a fake pushTransactionResp', result.output.strip())

        # Multiple dsts should work too
        dsts = [qaddr_1, qaddr_2, qaddr_3]
        amounts = ["1", "2", "3"]
        result = self.runner.invoke(qrl_cli, [
            "tx_transfer",
            "--src=0",
            "--master=",
            "--dsts={}".format(" ".join(dsts)),
            "--amounts={}".format(" ".join(amounts)),
            "--fee=0",
            "--ots_key_index=0"
        ])
        self.assertEqual(result.exit_code, 0)
        self.assertIn('a fake pushTransactionResp', result.output.strip())

    @mock.patch('qrl.cli.qrl_pb2_grpc.PublicAPIStub', autospec=True)
    def test_tx_transfer_encrypted_wallet(self, mock_stub):
        tx_pbdata_serialized_to_string = b"\n\x02\\n\x1aC\x01\x02\x00\x80\x9dg/U\xb1N\xf2_\x0e~j%\xb2\x15\x05\xa7y\x19\x8f\xc0>\x05`\x90\xe3>\xaa\x9a(\xd3\xc7U\x91\xbab\x90{\xaa^\xadQ\xca\xbf\xd3\xbc\xd9\x93\xf0:D\xca\xd8v\x97\x08\xa8x\x9c-\n4\xd6e:,\n'\x01\x06\x00\x95O\x16\xafx\xe3\x94Y\rc\x7f\x10D\xcd\x9f\xaf<\xc7\xf4\xa2\x93f\xaa\r\x8c\xa3 \xe40\x0bZ\xfe\xb0xy\xbb\x12\x01\x00"  # noqa
        mock_tx = qrl_pb2.Transaction()
        mock_tx.ParseFromString(tx_pbdata_serialized_to_string)

        m_transferCoinsResp = mock.MagicMock(name='a fake transferCoinsResp')
        m_transferCoinsResp.extended_transaction_unsigned.tx = mock_tx

        m_pushTransactionResp = mock.MagicMock(name='a fake pushTransactionResp', error_code=3)

        attrs = {
            "name": "my fake stub",
            "TransferCoins.return_value": m_transferCoinsResp,
            "PushTransaction.return_value": m_pushTransactionResp
        }
        mock_stub_instance = mock.MagicMock(**attrs)

        mock_stub.name = 'a fake qrl_pb2_grpc.PublicAPIStub'
        mock_stub.return_value = mock_stub_instance

        # Simplest use case
        self.runner.invoke(qrl_cli, ["wallet_encrypt"], input='password\npassword\n')
        result = self.runner.invoke(qrl_cli, ["tx_transfer", "--src=0", "--master=", "--dsts={}".format(qaddr_1),
                                              "--amounts=1", "--fee=0", "--ots_key_index=0"],
                                    input='password\n')
        self.assertEqual(result.exit_code, 0)
        self.assertIn('a fake pushTransactionResp', result.output.strip())

    @mock.patch('qrl.cli.qrl_pb2_grpc.PublicAPIStub', autospec=True)
    def test_tx_transfer_invalid_input(self, mock_stub):
        tx_pbdata_serialized_to_string = b"\n\x02\\n\x1aC\x01\x02\x00\x80\x9dg/U\xb1N\xf2_\x0e~j%\xb2\x15\x05\xa7y\x19\x8f\xc0>\x05`\x90\xe3>\xaa\x9a(\xd3\xc7U\x91\xbab\x90{\xaa^\xadQ\xca\xbf\xd3\xbc\xd9\x93\xf0:D\xca\xd8v\x97\x08\xa8x\x9c-\n4\xd6e:,\n'\x01\x06\x00\x95O\x16\xafx\xe3\x94Y\rc\x7f\x10D\xcd\x9f\xaf<\xc7\xf4\xa2\x93f\xaa\r\x8c\xa3 \xe40\x0bZ\xfe\xb0xy\xbb\x12\x01\x00"  # noqa
        mock_tx = qrl_pb2.Transaction()
        mock_tx.ParseFromString(tx_pbdata_serialized_to_string)

        m_transfer_coins_resp = mock.MagicMock(name='a fake transferCoinsResp')
        m_transfer_coins_resp.extended_transaction_unsigned.tx = mock_tx

        m_push_transaction_resp = mock.MagicMock(name='a fake pushTransactionResp', error_code=3)

        attrs = {
            "name": "my fake stub",
            "TransferCoins.return_value": m_transfer_coins_resp,
            "PushTransaction.return_value": m_push_transaction_resp
        }
        mock_stub_instance = mock.MagicMock(**attrs)

        mock_stub.name = 'a fake qrl_pb2_grpc.PublicAPIStub'
        mock_stub.return_value = mock_stub_instance

        # What if I use a ots_key_index larger than the wallet's tree height?
        result = self.runner.invoke(qrl_cli, [
            "tx_transfer",
            "--src=0",
            "--master=",
            "--dsts={}".format(qaddr_1),
            "--amounts=1",
            "--fee=0",
            "--ots_key_index=16"], input='16')
        self.assertEqual(result.exit_code, 1)
        self.assertIn('OTS key index must be between 0 and 15', result.output.strip())

        # dsts and amounts with different lengths should fail
        dsts = [qaddr_1, qaddr_2, qaddr_3]
        amounts = ["1", "2"]
        result = self.runner.invoke(qrl_cli, [
            "tx_transfer",
            "--src=0",
            "--master=",
            "--dsts={}".format(" ".join(dsts)),
            "--amounts={}".format(" ".join(amounts)),
            "--fee=0",
            "--ots_key_index=0"
        ])
        self.assertEqual(result.exit_code, 1)
        self.assertIn('dsts and amounts should be the same length', result.output.strip())

    @mock.patch('qrl.cli.qrl_pb2_grpc.PublicAPIStub', autospec=True)
    def test_tx_token(self, mock_stub):
        m_push_transaction_resp = mock.MagicMock(name='mock pushTransactionResp')
        m_push_transaction_resp.error_code = 'This was a test'

        attrs = {"name": "this should be qrl_pb2_grpc.PublicAPIStub(channel)",
                 "PushTransaction.return_value": m_push_transaction_resp}
        mock_stub_instance = mock.MagicMock(**attrs)

        mock_stub.name = "this should be qrl_pb2_grpc.PublicAPIStub"
        mock_stub.return_value = mock_stub_instance

        wallet = open_wallet()
        owner_address = wallet["addresses"][0]["address"]
        typed_in_input = '\n'.join([owner_address, '100']) + '\n'
        result = self.runner.invoke(qrl_cli, [
            "tx_token",
            "--src=0",
            "--master=",
            "--symbol=TST",
            "--name=TEST",
            "--owner={}".format(owner_address),
            "--decimals=1",
            "--fee=0",
            "--ots_key_index=0"
        ], input=typed_in_input)
        self.assertIn('This was a test', result.output)

    @mock.patch('qrl.cli.qrl_pb2_grpc.PublicAPIStub', autospec=True)
    def test_tx_token_invalid_input(self, mock_stub):
        m_push_transaction_resp = mock.MagicMock(name='mock pushTransactionResp')
        m_push_transaction_resp.error_code = 'This was a test'

        attrs = {"name": "this should be qrl_pb2_grpc.PublicAPIStub(channel)",
                 "PushTransaction.return_value": m_push_transaction_resp}
        mock_stub_instance = mock.MagicMock(**attrs)

        mock_stub.name = "this should be qrl_pb2_grpc.PublicAPIStub"
        mock_stub.return_value = mock_stub_instance

        wallet = open_wallet()
        owner_address = wallet["addresses"][0]["address"]
        typed_in_input = '\n'.join([owner_address, '100']) + '\n'

        # Weird token names and symbols shouldn't work
        result = self.runner.invoke(qrl_cli,
                                    [
                                        "tx_token",
                                        "--src=0",
                                        "--master=",
                                        "--symbol=thequickbrownfoxjumpsoverthelazydog",
                                        "--name=Seriouslyimgonnastarttalkingandneverendbecausethatsjusthowidothings "
                                        "can i have spaces in here what about |nny characters",
                                        "--owner={}".format(owner_address),
                                        "--decimals=1",
                                        "--fee=0",
                                        "--ots_key_index=0"
                                    ],
                                    input=typed_in_input
                                    )

        print(result.output)
        self.assertEqual(result.exit_code, 1)
        self.assertIn('must be shorter than', result.output.strip())

        # An outrageous decimal precision shouldn't work either
        result = self.runner.invoke(qrl_cli,
                                    [
                                        "tx_token",
                                        "--src=0",
                                        "--master=",
                                        "--symbol=TST",
                                        "--name=TEST",
                                        "--owner={}".format(owner_address),
                                        "--decimals=1000",
                                        "--fee=0",
                                        "--ots_key_index=0"
                                    ],
                                    input=typed_in_input
                                    )

        print(result.output)
        print(result.exc_info)
        self.assertEqual(result.exit_code, 1)

    @mock.patch('qrl.cli.qrl_pb2_grpc.PublicAPIStub', autospec=True)
    def test_tx_transfertoken(self, mock_stub):
        m_push_transaction_resp = mock.MagicMock(name='mock pushTransactionResp')
        m_push_transaction_resp.error_code = 'This was a test'

        attrs = {"name": "this should be qrl_pb2_grpc.PublicAPIStub(channel)",
                 "PushTransaction.return_value": m_push_transaction_resp}
        mock_stub_instance = mock.MagicMock(**attrs)

        mock_stub.name = "this should be qrl_pb2_grpc.PublicAPIStub"
        mock_stub.return_value = mock_stub_instance

        txhash = '267d9c6e192c78b192b6e835411d30f7cb605ffe9632d668489c579e4230f3c6'  # from sample tx_token run

        result = self.runner.invoke(qrl_cli,
                                    [
                                        "tx_transfertoken",
                                        "--src=0",
                                        "--master=",
                                        "--token_txhash={}".format(txhash),
                                        "--dsts={}".format(qaddr_1),
                                        "--amounts=10",
                                        "--decimals=10",
                                        "--fee=0",
                                        "--ots_key_index=0"
                                    ])

        print(result.output)
        self.assertEqual(result.exit_code, 0)
        self.assertEqual(result.output.strip(), 'This was a test')

    @mock.patch('qrl.cli.qrl_pb2_grpc.PublicAPIStub', autospec=True)
    def test_tx_transfertoken_invalid_input(self, mock_stub):
        m_push_transaction_resp = mock.MagicMock(name='mock pushTransactionResp')
        m_push_transaction_resp.error_code = 'This was a test'

        attrs = {"name": "this should be qrl_pb2_grpc.PublicAPIStub(channel)",
                 "PushTransaction.return_value": m_push_transaction_resp}
        mock_stub_instance = mock.MagicMock(**attrs)

        mock_stub.name = "this should be qrl_pb2_grpc.PublicAPIStub"
        mock_stub.return_value = mock_stub_instance

        txhash = '267d9c6e192c78b192b6e835411d30f7cb605ffe9632d668489c579e4230f3c6'  # from sample tx_token run

        # Invalid txhash shouldn't work.
        result = self.runner.invoke(qrl_cli,
                                    [
                                        "tx_transfertoken",
                                        "--src=0",
                                        "--master=",
                                        "--token_txhash={}".format(txhash[3:]),
                                        "--dsts={}".format(qaddr_1),
                                        "--amounts=10",
                                        "--decimals=10",
                                        "--fee=0",
                                        "--ots_key_index=0"],
                                    )

        print(result.output)
        self.assertEqual(result.exit_code, 1)
        self.assertIn('hex string is expected to have an even number of characters', result.output.strip())

        # If decimals is different from the original token_txhash definition, it shouldn't work either.
        # But maybe this is for integration tests.
        # result = self.runner.invoke(qrl_cli,
        #                             ["tx_transfertoken", "--src=0", "--master=",
        #                              "--token_txhash={}".format(txhash), "--dsts={}".format(qaddr_1), "--amounts=10",
        #                              "--decimals=999", "--fee=0", "--ots_key_index=1"],
        #                             )
        # self.assertEqual(result.exit_code, 1)
        # self.assertNotIn('This was a test', result.output.strip())

    @mock.patch('qrl.cli.qrl_pb2_grpc.PublicAPIStub', autospec=True)
    def test_token_list(self, mock_stub):
        m_address_state_resp = mock.MagicMock(name='this should be the addressStateResp')
        m_address_state_resp.state.tokens = {'aabb00': 10, 'ccbb11': 100}
        transaction = mock.MagicMock()
        transaction.token = mock.MagicMock()
        transaction.token.name = b"NAME"
        transaction.token.symbol = b"SYM"
        tx_extended = mock.MagicMock(tx=transaction)
        get_object_resp = mock.MagicMock(transaction=tx_extended)

        mock_stub_instance = mock.MagicMock(name='this should be qrl_pb2_grpc.PublicAPIStub(channel)')
        mock_stub_instance.GetAddressState.return_value = m_address_state_resp
        mock_stub_instance.GetObject.return_value = get_object_resp

        mock_stub.name = 'this should be qrl_pb2_grpc.PublicAPIStub'
        mock_stub.return_value = mock_stub_instance

        result = self.runner.invoke(qrl_cli, ["token_list", "--owner={}".format(qaddr_1)])

        self.assertIn('Hash: {}\nSymbol: {}\nName: {}\nBalance: 10'.format('aabb00',
                                                                           transaction.token.symbol.decode(),
                                                                           transaction.token.name.decode()), result.output)
        self.assertIn('Hash: {}\nSymbol: {}\nName: {}\nBalance: 100'.format('ccbb11',
                                                                            transaction.token.symbol.decode(),
                                                                            transaction.token.name.decode()), result.output)

    @mock.patch('qrl.cli.qrl_pb2_grpc.PublicAPIStub', autospec=True)
    def test_token_list_invalid_input(self, mock_stub):
        m_address_state_resp = mock.MagicMock(name='this should be the addressStateResp')
        m_address_state_resp.state.tokens = {qaddr_1: 10, qaddr_2: 100}

        mock_stub_instance = mock.MagicMock(name='this should be qrl_pb2_grpc.PublicAPIStub(channel)')
        mock_stub_instance.GetAddressState.return_value = m_address_state_resp

        mock_stub.name = 'this should be qrl_pb2_grpc.PublicAPIStub'
        mock_stub.return_value = mock_stub_instance

        # Malformed Qaddress should fail!
        result = self.runner.invoke(qrl_cli, ["token_list", "--owner={}".format(qaddr_1[:-1])])

        self.assertEqual(1, result.exit_code)
        self.assertIn('hex string is expected to have an even number of characters', result.output.strip())
