# 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, skip, expectedFailure

from click.testing import CliRunner
from pyqrllib.dilithium import Dilithium
from pyqrllib.kyber import Kyber
from pyqrllib.pyqrllib import bin2hstr

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)

    @skip("long")
    def test_wallet_gen_default_height(self):
        self.runner.invoke(qrl_cli, ["wallet_gen"])
        wallet = open_wallet()
        self.assertEqual(wallet["addresses"][0]["height"], 12)

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

    def test_wallet_gen_different_hash_function(self):
        self.runner.invoke(qrl_cli, ["wallet_gen", "--height=4", "--hash_function=sha2_256"])
        wallet = open_wallet()
        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):
        self.runner.invoke(qrl_cli, ["wallet_gen", "--height=4", "--encrypt"], input='password\npassword\n')
        wallet = open_wallet()
        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=1"])
        self.assertEqual(result.output.strip(), 'Wallet index not found')

    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=1", "--skip-confirmation"], input='y\n')
        self.assertEqual(result.output.strip(), 'Wallet index not found')

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

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

        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_nodeStateResp_serialized = b'\n/\n\x160.61.3+ngbd52b1a.dirty\x10\x03\x18\x02 D(\xa7\x010\x83!B\tExcession'
        m_nodeStateResp = qrl_pb2.GetNodeStateResp()
        m_nodeStateResp.ParseFromString(m_nodeStateResp_serialized)

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

        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"])
        self.assertEqual(result.exit_code, 0)
        the_output = json.loads(result.output)
        self.assertEqual(the_output["info"]["networkId"], "Excession")

    def test_tx_sign(self):
        result = self.runner.invoke(qrl_cli, ["tx_sign", "--src=0", "--txblob={}".format(bin2hstr_unsigned_tx)])
        self.assertEqual(result.exit_code, 0)

        # works with src=Qaddr as well
        wallet = open_wallet()
        result = self.runner.invoke(qrl_cli, ["tx_sign", "--src={}".format(wallet["addresses"][0]["address"]),
                                              "--txblob={}".format(bin2hstr_unsigned_tx)])
        self.assertEqual(result.exit_code, 0)

    def test_tx_sign_invalid_input(self):
        # It shouldn't work on invalid txblobs
        result = self.runner.invoke(qrl_cli, ["tx_sign", "--src=0", "--txblob={}".format(bin2hstr_unsigned_tx[2:])])
        self.assertNotEqual(result.exit_code, 0)

    @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):
        """Test that this command works as advertised."""

        # 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_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"]

        # 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"
        ])
        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_pushTransactionResp = mock.MagicMock(name='this should be pushTransactionResp', error_code=mock_error_code)

        attrs = {"name": "this should be stub.PushTransaction",
                 "PushTransaction.return_value": mock_pushTransactionResp}
        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_pushTransactionResp = mock.MagicMock(name='this should be pushTransactionResp', error_code=mock_error_code)

        attrs = {"name": "this should be stub.PushTransaction",
                 "PushTransaction.return_value": mock_pushTransactionResp}
        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_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

        wallet = open_wallet()

        # Simplest use case
        result = self.runner.invoke(qrl_cli, ["-r", "tx_transfer", "--src=0", "--master=", "--dst={}".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())

        # Should work with src=Qaddress as well
        result = self.runner.invoke(qrl_cli,
                                    ["-r", "tx_transfer", "--src={}".format(wallet["addresses"][0]["address"]),
                                     "--master=",
                                     "--dst={}".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,
                                    ["-r", "tx_transfer", "--src={}".format(wallet["addresses"][0]["address"]),
                                     "--master={}".format(qaddr_2), "--dst={}".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,
                                    ["-r", "tx_transfer", "--src=0", "--master=", "--dst={}".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, ["-r", "tx_transfer", "--src=0", "--master=", "--dst={}".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_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

        # What if I use a ots_key_index larger than the wallet's tree height?
        result = self.runner.invoke(qrl_cli, ["-r", "tx_transfer", "--src=0", "--master=", "--dst={}".format(qaddr_1),
                                              "--amounts=1", "--fee=0", "--ots_key_index=17"])
        self.assertEqual(result.exit_code, 1)
        self.assertIn('Error validating arguments', 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,
                                    ["-r", "tx_transfer", "--src=0", "--master=", "--dst={}".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_pushTransactionResp = mock.MagicMock(name='mock pushTransactionResp')
        m_pushTransactionResp.error_code = 'This was a test'

        attrs = {"name": "this should be qrl_pb2_grpc.PublicAPIStub(channel)",
                 "PushTransaction.return_value": m_pushTransactionResp}
        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,
                                    ["-r", "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_pushTransactionResp = mock.MagicMock(name='mock pushTransactionResp')
        m_pushTransactionResp.error_code = 'This was a test'

        attrs = {"name": "this should be qrl_pb2_grpc.PublicAPIStub(channel)",
                 "PushTransaction.return_value": m_pushTransactionResp}
        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,
                                    ["-r", "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
                                    )
        self.assertEqual(result.exit_code, 1)
        self.assertIn('must be shorter than', result.output.strip())

        # An outrageous decimal precision shouldn't work either
        # Currently this causes the CLI to hang, as it tries to do 10^1000000000
        # result = self.runner.invoke(qrl_cli,
        #                             ["-r", "tx_token", "--src=0", "--master=", "--symbol=TST", "--name=TEST",
        #                              "--owner={}".format(owner_address), "--decimals=1000000000", "--fee=0",
        #                              "--ots_key_index=0"],
        #                             input=typed_in_input
        #                             )
        # self.assertEqual(result.exit_code, 1)

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

        attrs = {"name": "this should be qrl_pb2_grpc.PublicAPIStub(channel)",
                 "PushTransaction.return_value": m_pushTransactionResp}
        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,
                                    ["-r", "tx_transfertoken", "--src=0", "--master=",
                                     "--token_txhash={}".format(txhash), "--dst={}".format(qaddr_1), "--amounts=10",
                                     "--decimals=10", "--fee=0", "--ots_key_index=0"],
                                    )
        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_pushTransactionResp = mock.MagicMock(name='mock pushTransactionResp')
        m_pushTransactionResp.error_code = 'This was a test'

        attrs = {"name": "this should be qrl_pb2_grpc.PublicAPIStub(channel)",
                 "PushTransaction.return_value": m_pushTransactionResp}
        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,
                                    ["-r", "tx_transfertoken", "--src=0", "--master=",
                                     "--token_txhash={}".format(txhash[3:]), "--dst={}".format(qaddr_1), "--amounts=10",
                                     "--decimals=10", "--fee=0", "--ots_key_index=0"],
                                    )
        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,
        #                             ["-r", "tx_transfertoken", "--src=0", "--master=",
        #                              "--token_txhash={}".format(txhash), "--dst={}".format(qaddr_1), "--amounts=10",
        #                              "--decimals=999", "--fee=0", "--ots_key_index=0"],
        #                             )
        # 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_addressStateResp = mock.MagicMock(name='this should be the addressStateResp')
        m_addressStateResp.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_addressStateResp

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

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

        self.assertIn('Hash: {}\nBalance: 10'.format(qaddr_1), result.output)
        self.assertIn('Hash: {}\nBalance: 100'.format(qaddr_2), result.output)

    @mock.patch('qrl.cli.qrl_pb2_grpc.PublicAPIStub', autospec=True)
    def test_token_list_invalid_input(self, mock_stub):
        m_addressStateResp = mock.MagicMock(name='this should be the addressStateResp')
        m_addressStateResp.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_addressStateResp

        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, ["-r", "token_list", "--owner={}".format(qaddr_1[:-1])])

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

    @mock.patch('qrl.cli.qrl_pb2_grpc.PublicAPIStub', autospec=True)
    def test_tx_latticepk(self, mock_stub):
        m_pushTransactionResp = mock.MagicMock(name='my big fake pushTransactionResp', error_code=3)
        mock_stub_instance = mock.MagicMock(name='this should be qrl_pb2_grpc.PublicAPIStub(channel)')
        mock_stub_instance.PushTransaction.return_value = m_pushTransactionResp

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

        kyber_pk = bin2hstr(Kyber().getPK())
        dilithium_pk = bin2hstr(Dilithium().getPK())

        result = self.runner.invoke(qrl_cli,
                                    ["-r", "tx_latticepk", "--src=0", "--master=",
                                     "--kyber-pk={}".format(kyber_pk), "--dilithium-pk={}".format(dilithium_pk),
                                     "--fee=0", "--ots_key_index=0"])
        print(result.exit_code)
        self.assertEqual(result.exit_code, 0)
        self.assertEqual(result.output.strip(), '3')

    @expectedFailure
    @mock.patch('qrl.cli.qrl_pb2_grpc.PublicAPIStub', autospec=True)
    def test_tx_latticepk_invalid_input(self, mock_stub):
        m_pushTransactionResp = mock.MagicMock(name='my big fake pushTransactionResp', error_code=3)
        mock_stub_instance = mock.MagicMock(name='this should be qrl_pb2_grpc.PublicAPIStub(channel)')
        mock_stub_instance.PushTransaction.return_value = m_pushTransactionResp

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

        kyber_pk = bin2hstr(Kyber().getPK())
        dilithium_pk = bin2hstr(Dilithium().getPK())

        # What if we have malformed kyber pks?
        result = self.runner.invoke(qrl_cli,
                                    ["-r", "tx_latticepk", "--src=0", "--master=",
                                     "--kyber-pk={}".format(kyber_pk[1:]), "--dilithium-pk={}".format(dilithium_pk),
                                     "--fee=0", "--ots_key_index=0"])
        self.assertEqual(result.exit_code, -1)

        # What if we have malformed dilithium pks?
        result = self.runner.invoke(qrl_cli,
                                    ["-r", "tx_latticepk", "--src=0", "--master=",
                                     "--kyber-pk={}".format(kyber_pk), "--dilithium-pk={}".format(dilithium_pk[1:]),
                                     "--fee=0", "--ots_key_index=0"])
        self.assertEqual(result.exit_code, -1)
