import ast
import hashlib
import json
import logging
import os

# import math currently unused module
import random
import re
import sys
import time
from datetime import datetime, timedelta, timezone
from typing import Any

import click
from prettytable import PrettyTable

from nectar import exceptions
from nectar.account import Account, Accounts
from nectar.amount import Amount
from nectar.asciichart import AsciiChart
from nectar.asset import Asset
from nectar.block import Block
from nectar.blockchain import Blockchain
from nectar.comment import Comment
from nectar.community import Communities, Community
from nectar.hive import Hive
from nectar.imageuploader import ImageUploader
from nectar.instance import set_shared_blockchain_instance, shared_blockchain_instance
from nectar.market import Market
from nectar.memo import Memo
from nectar.message import Message
from nectar.nodelist import NodeList
from nectar.price import Price
from nectar.profile import Profile
from nectar.rc import RC
from nectar.transactionbuilder import TransactionBuilder
from nectar.utils import (
    construct_authorperm,
    create_new_password,
    derive_beneficiaries,
    derive_permlink,
    derive_tags,
    formatTimeString,
    generate_password,
    import_coldcard_wif,
    import_custom_json,
    import_pubkeys,
    make_patch,
    seperate_yaml_dict_from_body,
)
from nectar.version import version as __version__
from nectar.vote import AccountVotes, ActiveVotes, Vote
from nectar.wallet import Wallet
from nectar.witness import Witness, WitnessesRankedByVote, WitnessesVotedByAccount
from nectarbase import operations
from nectargraphenebase.account import (
    Mnemonic,
    MnemonicKey,
    PasswordKey,
    PrivateKey,
)
from nectargraphenebase.base58 import Base58

click.disable_unicode_literals_warning = True
log = logging.getLogger(__name__)

availableConfigurationKeys = [
    "default_account",
    "default_vote_weight",
    "nodes",
    "password_storage",
    "client_id",
    "default_canonical_url",
    "default_path",
    "use_tor",
]


def prompt_callback(ctx: Any, param: Any, value: str) -> bool:
    if value in ["yes", "y", "ye"]:
        return True
    else:
        print("Please write yes, ye or y to confirm!")
        ctx.abort()
        # This line is unreachable but needed for type checker
        return False


def asset_callback(ctx: Any, param: Any, value: str) -> str:
    if value not in ["HIVE", "HBD", "TBD", "TESTS"]:
        print("Please choose HIVE/HBD (or TESTS/TBD for test assets) as asset!")
        ctx.abort()
        # This line is unreachable but needed for type checker
        return value
    else:
        return value


def prompt_flag_callback(ctx: Any, param: Any, value: bool) -> bool:
    if not value:
        ctx.abort()
        # This line is unreachable but needed for type checker
        return False
    return True


def is_keyring_available() -> bool:
    KEYRING_AVAILABLE = False
    try:
        import keyring  # type: ignore

        if not isinstance(keyring.get_keyring(), keyring.backends.fail.Keyring):  # type: ignore
            KEYRING_AVAILABLE = True
        else:
            KEYRING_AVAILABLE = False
    except ImportError:
        KEYRING_AVAILABLE = False
    return KEYRING_AVAILABLE


def unlock_wallet(hv, password=None, allow_wif=True):
    if hv.unsigned and hv.nobroadcast:
        return True
    if hv.use_ledger:
        return True
    if not hv.wallet.locked():
        return True
    if not hv.wallet.store.is_encrypted():
        return True
    password_storage = hv.config["password_storage"]
    if not password and password_storage == "keyring" and is_keyring_available():
        import keyring  # type: ignore

        password = keyring.get_password("nectar", "wallet")
    if not password and password_storage == "environment" and "UNLOCK" in os.environ:
        password = os.environ.get("UNLOCK")
    if bool(password):
        hv.wallet.unlock(password)
    else:
        if allow_wif:
            password = click.prompt(
                "Password to unlock wallet or posting/active wif",
                confirmation_prompt=False,
                hide_input=True,
            )
        else:
            password = click.prompt(
                "Password to unlock wallet", confirmation_prompt=False, hide_input=True
            )
        if hv.wallet.is_encrypted():
            try:
                hv.wallet.unlock(password)
            except Exception:
                try:
                    from nectarstorage import InRamPlainKeyStore

                    hv.wallet.store = InRamPlainKeyStore()
                    hv.wallet.setKeys([password])
                    print("Wif accepted!")
                    return True
                except Exception:
                    if allow_wif:
                        raise exceptions.WrongMasterPasswordException(
                            "entered password is not a valid password/wif"
                        )
                    else:
                        raise exceptions.WrongMasterPasswordException(
                            "entered password is not a valid password"
                        )
        else:
            try:
                hv.wallet.setKeys([password])
                print("Wif accepted!")
                return True
            except Exception:
                try:
                    from nectarstorage import SqliteEncryptedKeyStore

                    hv.wallet.store = SqliteEncryptedKeyStore(config=hv.config)
                    hv.wallet.unlock(password)
                except Exception:
                    if allow_wif:
                        raise exceptions.WrongMasterPasswordException(
                            "entered password is not a valid password/wif"
                        )
                    else:
                        raise exceptions.WrongMasterPasswordException(
                            "entered password is not a valid password"
                        )

    if hv.wallet.locked():
        if password_storage == "keyring" or password_storage == "environment":
            print("Wallet could not be unlocked with %s!" % password_storage)
            password = click.prompt(
                "Password to unlock wallet", confirmation_prompt=False, hide_input=True
            )
            if bool(password):
                unlock_wallet(hv, password=password)
                if not hv.wallet.locked():
                    return True
        else:
            print("Wallet could not be unlocked!")
        return False
    else:
        print("Wallet Unlocked!")
        return True


def export_trx(tx, export):
    if export is not None:
        with open(export, "w", encoding="utf-8") as f:
            json.dump(tx, f)


@click.group(chain=True)
# @click.group(chain=True)
@click.option(
    "--node", "-n", default="", help="URL for public Hive API (e.g. https://api.hive.blog)"
)
@click.option("--offline", "-o", is_flag=True, default=False, help="Prevent connecting to network")
@click.option("--no-broadcast", "-d", is_flag=True, default=False, help="Do not broadcast")
@click.option(
    "--testnet",
    "-t",
    is_flag=True,
    default=False,
    help="Legacy compatibility flag (no-op, Hive only).",
)
# Note: The testnet flag is deliberately retained for backward compatibility but is unused in the implementation.
@click.option("--no-wallet", "-p", is_flag=True, default=False, help="Do not load the wallet")
@click.option(
    "--unsigned",
    "-x",
    is_flag=True,
    default=False,
    help="Nothing will be signed, changes the default value of expires to 3600",
)
# Hive is the only supported chain; no chain selection flags
@click.option(
    "--keys",
    "-k",
    help="JSON file that contains account keys, when set, the wallet cannot be used.",
)
@click.option(
    "--use-ledger",
    "-u",
    is_flag=True,
    default=False,
    help="Uses the ledger device Nano S for signing.",
)
@click.option(
    "--path", help="BIP32 path from which the keys are derived, when not set, default_path is used."
)
@click.option(
    "--expires",
    "-e",
    default=30,
    help="Delay in seconds until transactions are supposed to expire(defaults to 30)",
)
@click.option("--verbose", "-v", default=3, help="Verbosity")
@click.version_option(version=__version__)
def cli(
    node,
    offline,
    no_broadcast,
    testnet,  # noqa: ARG001
    no_wallet,
    unsigned,
    keys,
    use_ledger,
    path,
    expires,
    verbose,
):
    # Logging
    log = logging.getLogger(__name__)
    verbosity = ["critical", "error", "warn", "info", "debug"][int(min(verbose, 4))]
    log.setLevel(getattr(logging, verbosity.upper()))
    formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
    ch = logging.StreamHandler()
    ch.setLevel(getattr(logging, verbosity.upper()))
    ch.setFormatter(formatter)
    log.addHandler(ch)

    if unsigned and expires == 30:
        # Change expires to max duration when setting unsigned
        expires = 3600

    keys_list = []
    autoconnect = False
    if keys and keys != "":
        if not os.path.isfile(keys):
            raise Exception("File %s does not exist!" % keys)
        with open(keys) as fp:
            keyfile = fp.read()
        if keyfile.find("\0") > 0:
            with open(keys, encoding="utf-16") as fp:
                keyfile = fp.read()
        keyfile = ast.literal_eval(keyfile)
        for account in keyfile:
            for role in ["owner", "active", "posting", "memo"]:
                if role in keyfile[account]:
                    keys_list.append(keyfile[account][role])
    if len(keys_list) > 0:
        autoconnect = True
    debug = verbose > 0
    # Hive-only instance
    hv = Hive(
        node=node,
        nobroadcast=no_broadcast,
        keys=keys_list,
        offline=offline,
        nowallet=no_wallet,
        unsigned=unsigned,
        expiration=expires,
        use_ledger=use_ledger,
        path=path,
        debug=debug,
        num_retries=10,
        num_retries_call=5,
        timeout=30,
        autoconnect=autoconnect,
    )

    set_shared_blockchain_instance(hv)

    pass


@cli.command()
@click.argument("key")
@click.argument("value")
def set(key, value):
    """Set default_account, default_vote_weight or nodes

    set [key] [value]

    Examples:

    Set the default vote weight to 50 %:
    set default_vote_weight 50
    """
    hv = shared_blockchain_instance()
    if key == "default_account":
        if hv.rpc is not None:
            hv.rpc.rpcconnect()
        hv.set_default_account(value)
    elif key == "default_vote_weight":
        hv.set_default_vote_weight(value)
    elif key == "nodes" or key == "node":
        if bool(value) or value != "default":
            hv.set_default_nodes(value)
        else:
            hv.set_default_nodes("")
    elif key == "default_chain":
        hv.config["default_chain"] = value
    elif key == "password_storage":
        hv.config["password_storage"] = value
        if is_keyring_available() and value == "keyring":
            import keyring  # type: ignore

            password = click.prompt(
                "Password to unlock wallet (Will be stored in keyring)",
                confirmation_prompt=False,
                hide_input=True,
            )
            password = keyring.set_password("nectar", "wallet", password)
        elif is_keyring_available() and value != "keyring":
            import keyring  # type: ignore

            try:
                keyring.delete_password("nectar", "wallet")
            except keyring.errors.PasswordDeleteError:
                print("")
        if value == "environment":
            print(
                "The wallet password can be stored in the UNLOCK environment variable to skip password prompt!"
            )
    elif key == "client_id":
        hv.config["client_id"] = value
    elif key == "default_path":
        hv.config["default_path"] = value
    elif key == "default_canonical_url":
        hv.config["default_canonical_url"] = value
    elif key == "use_tor":
        hv.config["use_tor"] = value in ["true", "True"]
    else:
        print("wrong key")


@cli.command()
@click.option("--results", is_flag=True, default=False, help="Shows result of changing the node.")
def nextnode(results):
    """Uses the next node in list"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    hv.move_current_node_to_front()
    node = hv.get_default_nodes()
    offline = hv.offline
    if len(node) < 2 or isinstance(node, str):
        print("At least two nodes are needed!")
        return
    node = node[1:] + [node[0]]
    if not offline:
        hv.rpc.next()
        hv.get_blockchain_version()
    while not offline and node[0] != hv.rpc.url and len(node) > 1:
        node = node[1:] + [node[0]]
    hv.set_default_nodes(node)
    if not results:
        return

    t = PrettyTable(["Key", "Value"])
    t.align = "l"
    if not offline:
        t.add_row(["Node-Url", hv.rpc.url])
    else:
        t.add_row(["Node-Url", node[0]])
    if not offline:
        t.add_row(["Version", hv.get_blockchain_version()])
        t.add_row(["HIVE", hv.is_hive])
    else:
        t.add_row(["Version", "hive-nectar is in offline mode..."])
    print(t)


@cli.command()
@click.option("--sort", "-s", is_flag=True, default=False, help="Sort all nodes by ping value")
@click.option(
    "--remove", "-r", is_flag=True, default=False, help="Remove node with errors from list"
)
def pingnode(sort, remove):
    """Returns the answer time in milliseconds"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    nodes = hv.get_default_nodes()

    t = PrettyTable(["Node", "Answer time [ms]"])
    t.align = "l"
    if sort:
        sorted_node_list = []
        nodelist = NodeList()
        sorted_nodes = nodelist.get_node_answer_time(nodes)
        for node in sorted_nodes:
            t.add_row([node["url"], "%.2f" % (node["delay_ms"])])
            sorted_node_list.append(node["url"])
        print(t)
        hv.set_default_nodes(sorted_node_list)
    else:
        node = hv.rpc.url
        nodelist = NodeList()
        node_times = nodelist.get_node_answer_time([node], verbose=False)
        rpc_answer_time = node_times[0]["delay_ms"] / 1000 if node_times else 0
        rpc_time_str = "%.2f" % (rpc_answer_time * 1000)
        t.add_row([node, rpc_time_str])
        print(t)


@cli.command()
def about():
    """About hive-nectar"""
    print("")
    print("hive-nectar version: %s" % __version__)
    print("")
    print("By @thecrazygm")
    print("")


@cli.command()
@click.option("--version", is_flag=True, default=False, help="Returns only the raw version value")
@click.option("--url", is_flag=True, default=False, help="Returns only the raw url value")
def currentnode(version, url):
    """Sets the currently working node at the first place in the list"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    offline = hv.offline
    hv.move_current_node_to_front()
    node = hv.get_default_nodes()
    if version and not offline:
        print(hv.get_blockchain_version())
        return
    elif version and offline:
        print("Node is offline")
        return
    if url and not offline:
        print(hv.rpc.url)
        return
    t = PrettyTable(["Key", "Value"])
    t.align = "l"
    if not offline:
        t.add_row(["Node-Url", hv.rpc.url])
    else:
        t.add_row(["Node-Url", node[0]])
    if not offline:
        t.add_row(["Version", hv.get_blockchain_version()])
        t.add_row(["Chain", hv.get_blockchain_name()])
    else:
        t.add_row(["Version", "hive-nectar is in offline mode..."])
    print(t)


@cli.command()
@click.option("--show", "-s", is_flag=True, default=False, help="Prints the updated nodes")
# Hive-only node updates
@click.option(
    "--test",
    "-t",
    is_flag=True,
    default=False,
    help="Do change the node list, only print the newest nodes setup.",
)
@click.option("--only-https", is_flag=True, default=False, help="Use only https nodes.")
@click.option("--only-wss", is_flag=True, default=False, help="Use only websocket nodes.")
def updatenodes(show, test, only_https, only_wss):
    """Update the nodelist from @fullnodeupdate"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    t = PrettyTable(["node", "Version", "score"])
    t.align = "l"
    nodelist = NodeList()
    try:
        nodelist.update_nodes(blockchain_instance=hv)
        # Flags are mutually exclusive; at least one transport must be enabled
        if only_https and only_wss:
            raise click.UsageError("Use at most one of --only-https or --only-wss.")
        nodes = nodelist.get_hive_nodes(wss=not only_https, https=not only_wss)
        if not nodes:
            raise RuntimeError("No nodes matched the selected filters.")
        if hv.config["default_chain"] != "hive":
            hv.config["default_chain"] = "hive"
        if show or test:
            sorted_nodes = sorted(nodelist, key=lambda node: node["score"], reverse=True)
            for node in sorted_nodes:
                if node["url"] in nodes:
                    score = float("{:.1f}".format(node["score"]))
                    t.add_row([node["url"], node["version"], score])
            print(t)
        if not test:
            hv.set_default_nodes(nodes)
            hv.rpc.nodes.set_node_urls(nodes)
            hv.rpc.rpcconnect()
    except Exception:
        log.exception("Failed to update nodes")
        raise


@cli.command()
def config():
    """Shows local configuration"""
    hv = shared_blockchain_instance()
    t = PrettyTable(["Key", "Value"])
    t.align = "l"
    for key in hv.config:
        # hide internal config data
        if (
            key in availableConfigurationKeys
            and key != "nodes"
            and key != "node"
            and key != "use_tor"
        ):
            t.add_row([key, hv.config[key]])
    node = hv.get_default_nodes()
    blockchain = hv.config["default_chain"]
    nodes = json.dumps(node, indent=4)
    t.add_row(["default_chain", blockchain])
    t.add_row(["nodes", nodes])
    if "password_storage" not in availableConfigurationKeys:
        t.add_row(["password_storage", hv.config["password_storage"]])
    t.add_row(["data_dir", hv.config.data_dir])
    t.add_row(["use_tor", bool(hv.config["use_tor"])])
    print(t)


@cli.command()
@click.option("--wipe", is_flag=True, default=False, help="Wipe old wallet without prompt.")
def createwallet(wipe):
    """Create new wallet with a new password"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if hv.wallet.created() and not wipe:
        wipe_answer = click.prompt(
            "'Do you want to wipe your wallet? Are your sure? This is IRREVERSIBLE! If you dont have a backup you may lose access to your account! [y/n]",
            default="n",
        )
        if wipe_answer in ["y", "ye", "yes"]:
            hv.wallet.wipe(True)
        else:
            return
    elif wipe:
        hv.wallet.wipe(True)
    password = None
    password = click.prompt("New wallet password", confirmation_prompt=True, hide_input=True)
    if not bool(password):
        print("Password cannot be empty! Quitting...")
        return
    password_storage = hv.config["password_storage"]
    if password_storage == "keyring" and is_keyring_available():
        import keyring  # type: ignore

        password = keyring.set_password("nectar", "wallet", password)
    elif password_storage == "environment":
        print(
            "The new wallet password can be stored in the UNLOCK environment variable to skip password prompt!"
        )
    hv.wallet.wipe(True)
    hv.wallet.create(password)
    set_shared_blockchain_instance(hv)


@cli.command()
@click.option("--unlock", "-u", is_flag=True, default=False, help="Unlock wallet")
@click.option("--lock", "-l", is_flag=True, default=False, help="Lock wallet")
def walletinfo(unlock, lock):
    """Show info about wallet"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if lock:
        hv.wallet.lock()
    elif unlock:
        unlock_wallet(hv, allow_wif=False)

    t = PrettyTable(["Key", "Value"])
    t.align = "l"
    t.add_row(["created", hv.wallet.created()])
    t.add_row(["locked", hv.wallet.locked()])
    t.add_row(["Number of stored keys", len(hv.wallet.getPublicKeys())])
    t.add_row(["sql-file", hv.wallet.store.sqlite_file])
    password_storage = hv.config["password_storage"]
    t.add_row(["password_storage", password_storage])
    password = os.environ.get("UNLOCK")
    if password is not None:
        t.add_row(["UNLOCK env set", "yes"])
    else:
        t.add_row(["UNLOCK env set", "no"])
    if is_keyring_available():
        t.add_row(["keyring installed", "yes"])
    else:
        t.add_row(["keyring installed", "no"])

    if unlock:
        if unlock_wallet(hv, allow_wif=False):
            t.add_row(["Wallet unlock", "successful"])
        else:
            t.add_row(["Wallet unlock", "not working"])
    # t.add_row(["getPublicKeys", str(hv.wallet.getPublicKeys())])
    print(t)


@cli.command()
@click.option(
    "--unsafe-import-key",
    help="WIF key to parse (unsafe, unless shell history is deleted afterwards)",
    multiple=True,
)
def parsewif(unsafe_import_key):
    """Parse a WIF private key without importing"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if unsafe_import_key:
        for key in unsafe_import_key:
            try:
                pubkey = PrivateKey(key, prefix=hv.prefix).pubkey
                print(pubkey)
                account = hv.wallet.getAccountFromPublicKey(str(pubkey))
                account = Account(account, blockchain_instance=hv)
                key_type = hv.wallet.getKeyType(account, str(pubkey))
                print("Account: {} - {}".format(account["name"], key_type))
            except Exception as e:
                print(str(e))
    else:
        while True:
            wifkey = click.prompt("Enter private key", confirmation_prompt=False, hide_input=True)
            if not wifkey or wifkey == "quit" or wifkey == "exit":
                break
            try:
                pubkey = PrivateKey(wifkey, prefix=hv.prefix).pubkey
                print(pubkey)
                account = hv.wallet.getAccountFromPublicKey(str(pubkey))
                account = Account(account, blockchain_instance=hv)
                key_type = hv.wallet.getKeyType(account, str(pubkey))
                print("Account: {} - {}".format(account["name"], key_type))
            except Exception as e:
                print(str(e))
                continue


@cli.command()
@click.option(
    "--unsafe-import-key",
    help="Private key to import to wallet (unsafe, unless shell history is deleted afterwards)",
)
def addkey(unsafe_import_key):
    """Add key to wallet

    When no [OPTION] is given, a password prompt for unlocking the wallet
    and a prompt for entering the private key are shown.
    """
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if not unlock_wallet(hv, allow_wif=False):
        return
    if not unsafe_import_key:
        unsafe_import_key = click.prompt(
            "Enter private key", confirmation_prompt=False, hide_input=True
        )
    hv.wallet.addPrivateKey(unsafe_import_key)
    set_shared_blockchain_instance(hv)


@cli.command()
@click.option(
    "--confirm",
    prompt="Are your sure? This is IRREVERSIBLE! If you dont have a backup you may lose access to your account!",
    hide_input=False,
    callback=prompt_flag_callback,
    is_flag=True,
    confirmation_prompt=False,
    help="Please confirm!",
)
@click.argument("pub")
def delkey(confirm, pub):
    """Delete key from the wallet

    PUB is the public key from the private key
    which will be deleted from the wallet
    """
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if not unlock_wallet(hv, allow_wif=False):
        return
    hv.wallet.removePrivateKeyFromPublicKey(pub)
    set_shared_blockchain_instance(hv)


@cli.command()
@click.option(
    "--import-word-list",
    "-l",
    help="Imports a BIP39 wordlist and derives a private and public key",
    is_flag=True,
    default=False,
)
@click.option("--strength", help="Defines word list length for BIP39 (default = 256).", default=256)
@click.option("--passphrase", "-p", help="Sets a BIP39 passphrase", is_flag=True, default=False)
@click.option(
    "--path",
    "-m",
    help="Sets a path for BIP39 key creations. When path is set, network, role, account_keys, account and sequence is not used",
)
@click.option(
    "--network",
    "-n",
    help="Network index for BIP39 (default is 13 for Hive)",
    default=13,
)
@click.option(
    "--role",
    "-r",
    help="Defines which key role should be created (default = owner).",
    default="owner",
)
@click.option(
    "--account-keys",
    "-k",
    help="Derives four BIP39 keys for each role",
    is_flag=True,
    default=False,
)
@click.option(
    "--sequence", "-s", help="Sequence key number, when using BIP39 (default is 0)", default=0
)
@click.option("--account", "-a", help="sequence number for BIP39 key, default = 0")
@click.option(
    "--wif",
    "-w",
    help="Defines how many times the password is replaced by its WIF representation for password based keys (default = 0).",
)
@click.option(
    "--export-pub",
    "-u",
    help="Exports the public account keys to a json file for account creation or keychange",
)
@click.option("--export", "-e", help="The results are stored in a text file and will not be shown")
def keygen(
    import_word_list,
    strength,
    passphrase,
    path,
    network,
    role,
    account_keys,
    sequence,
    account,
    wif,
    export_pub,
    export,
):
    """Creates a new random BIP39 key and prints its derived private key and public key.
    The generated key is not stored. Can also be used to create new keys for an account.
    Can also be used to derive account keys from a password or BIP39 wordlist.
    """
    hv = shared_blockchain_instance()
    pub_json = {"owner": "", "active": "", "posting": "", "memo": ""}

    if not account_keys and len(role.split(",")) > 1:
        roles = role.split(",")
        account_keys = True
    else:
        roles = ["owner", "active", "posting", "memo"]
    if wif is not None:
        wif = int(wif)
    else:
        wif = 0

    if hv.use_ledger:
        if hv.rpc is not None:
            hv.rpc.rpcconnect()
        ledgertx = hv.new_tx()
        ledgertx.constructTx()
        if account is None:
            account = 0
        else:
            account = int(account)
        t = PrettyTable(["Key", "Value"])
        t_pub = PrettyTable(["Key", "Value"])
        t.align = "l"
        t_pub.align = "l"
        t.add_row(["Account sequence", account])
        t.add_row(["Key sequence", sequence])

        if account_keys and path is None:
            for r in roles:
                path = ledgertx.ledgertx.build_path(r, account, sequence)
                pubkey = ledgertx.ledgertx.get_pubkey(path, request_screen_approval=False)
                aprove_key = PrettyTable(["Approve %s Key" % r])
                aprove_key.align = "l"
                aprove_key.add_row([format(pubkey, "STM")])
                print(aprove_key)
                ledgertx.ledgertx.get_pubkey(path, request_screen_approval=True)
                t_pub.add_row(["%s Public Key" % r, format(pubkey, "STM")])
                t.add_row(["%s path" % r, path])
                pub_json[r] = format(pubkey, "STM")
        else:
            if path is None:
                path = ledgertx.ledgertx.build_path(role, account, sequence)
            t.add_row(["Key role", role])
            t.add_row(["path", path])
            pubkey = ledgertx.ledgertx.get_pubkey(path, request_screen_approval=False)
            aprove_key = PrettyTable(["Approve %s Key" % role])
            aprove_key.align = "l"
            aprove_key.add_row([format(pubkey, "STM")])
            print(aprove_key)
            ledgertx.ledgertx.get_pubkey(path, request_screen_approval=True)
            t_pub.add_row(["Public Key", format(pubkey, "STM")])
            pub_json[role] = format(pubkey, "STM")
    else:
        if account is None:
            account = 0
        else:
            account = int(account)
        if import_word_list:
            n_words = click.prompt("Enter word list length or complete word list")
            if len(n_words.split(" ")) > 0:
                word_list = n_words
            else:
                n_words = int(n_words)
                word_array = []
                word = None
                m = Mnemonic()
                while len(word_array) < n_words:
                    word = click.prompt(
                        "Enter %d. mnemnoric word" % (len(word_array) + 1), type=str
                    )
                    word = m.expand_word(word)
                    if m.check_word(word):
                        word_array.append(word)
                    print(" ".join(word_array))
                word_list = " ".join(word_array)
            if passphrase:
                passphrase = click.prompt(
                    "Enter passphrase", confirmation_prompt=True, hide_input=True
                )
            else:
                passphrase = ""
            mk = MnemonicKey(
                word_list=word_list,
                passphrase=passphrase,
                account_sequence=account,
                key_sequence=sequence,
            )
            if path is not None:
                mk.set_path(path)
            else:
                mk.set_path_BIP48(
                    network_index=network,
                    role=role,
                    account_sequence=account,
                    key_sequence=sequence,
                )
        else:
            mk = MnemonicKey(account_sequence=account, key_sequence=sequence)
            if path is not None:
                mk.set_path(path)
            else:
                mk.set_path_BIP48(
                    network_index=network,
                    role=role,
                    account_sequence=account,
                    key_sequence=sequence,
                )
            if passphrase:
                passphrase = click.prompt(
                    "Enter passphrase", confirmation_prompt=True, hide_input=True
                )
            else:
                passphrase = ""
            word_list = mk.generate_mnemonic(passphrase=passphrase, strength=strength)
        t = PrettyTable(["Key", "Value"])
        t_pub = PrettyTable(["Key", "Value"])
        t.align = "l"
        t_pub.align = "l"
        t.add_row(["Account sequence", account])
        t.add_row(["Key sequence", sequence])
        if account_keys and path is None:
            for r in roles:
                t.add_row(["%s Private Key" % r, str(mk.get_private())])
                mk.set_path_BIP48(
                    network_index=network, role=r, account_sequence=account, key_sequence=sequence
                )
                t_pub.add_row(["%s Public Key" % r, format(mk.get_public(), "STM")])
                t.add_row(["%s path" % r, mk.get_path()])
                pub_json[r] = format(mk.get_public(), "STM")
            if passphrase != "":
                t.add_row(["Passphrase", passphrase])
            t.add_row(["BIP39 wordlist", word_list])
        else:
            t.add_row(["Key role", role])
            t.add_row(["path", mk.get_path()])
            t.add_row(["BIP39 wordlist", word_list.lower()])
            if passphrase != "":
                t.add_row(["Passphrase", passphrase])
            t.add_row(["Private Key", str(mk.get_private())])
            t_pub.add_row(["Public Key", format(mk.get_public(), "STM")])
            pub_json[role] = format(mk.get_public(), "STM")
    if export_pub and export_pub != "":
        pub_json = json.dumps(pub_json, indent=4)
        with open(export_pub, "w") as fp:
            fp.write(pub_json)
        print("%s was sucessfully saved." % export_pub)
    if export and export != "":
        with open(export, "w") as fp:
            fp.write(str(t))
            fp.write("\n")
            fp.write(str(t_pub))
        print("%s was sucessfully saved." % export)
    else:
        print(t_pub)
        print(t)


@cli.command()
@click.option(
    "--role",
    "-r",
    help="Defines which key role should be created. When owner is not set as role and an cold card wif is imported, the Master Password is not shown. (default = owner,active,posting,memo when creating account keys).",
    default="owner,active,posting,memo",
)
@click.option("--account", "-a", help="account name for password based key generation")
@click.option(
    "--import-password",
    "-i",
    help="Imports a password and derives all four account keys",
    is_flag=True,
    default=False,
)
@click.option(
    "--import-coldcard",
    "-o",
    help="Text file with a BIP85 WIF generated by a coldcard. The imported WIF is used to derives all four account keys",
)
@click.option(
    "--wif",
    "-w",
    help="Defines how many times the password is replaced by its WIF representation for password based keys (default = 0 or 1 when importing a cold card wif).",
)
@click.option(
    "--export-pub",
    "-u",
    help="Exports the public account keys to a json file for account creation or keychange",
)
@click.option("--export", "-e", help="The results are stored in a text file and will not be shown")
def passwordgen(role, account, import_password, import_coldcard, wif, export_pub, export):
    """Creates a new password based key and prints its derived private key and public key.
    The generated key is not stored. The password is used to create new keys for an account.
    """
    hv = shared_blockchain_instance()
    if not account:
        account = hv.config["default_account"]
    if import_password:
        import_password = click.prompt("Enter password", confirmation_prompt=False, hide_input=True)
    elif import_coldcard is not None:
        import_password, path = import_coldcard_wif(import_coldcard)
    else:
        import_password = create_new_password(length=32)
    pub_json = {"owner": "", "active": "", "posting": "", "memo": ""}

    if len(role.split(",")) > 1:
        roles = role.split(",")
    elif role in ["owner", "active", "posting", "memo"]:
        roles = [role]
    else:
        roles = ["owner", "active", "posting", "memo"]
    if wif is not None:
        wif = int(wif)
    elif import_coldcard:
        wif = 1
    else:
        wif = 0

    password = generate_password(import_password, wif)
    t = PrettyTable(["Key", "Value"])
    t_pub = PrettyTable(["Key", "Value"])
    t.add_row(["Username", account])
    t_pub.add_row(["Username", account])
    if import_coldcard:
        t_pub.add_row(["cold card path", path])
    t.align = "l"
    t_pub.align = "l"
    for r in roles:
        pk = PasswordKey(account, password, role=r)
        t.add_row(["%s Private Key" % r, str(pk.get_private())])
        t_pub.add_row(["%s Public Key" % r, format(pk.get_public(), "STM")])
        pub_json[r] = format(pk.get_public(), "STM")
    if "owner" in roles or import_coldcard is None:
        t.add_row(["Backup (Master) Password", password])
    if wif > 0:
        t.add_row(["WIF itersions", wif])
        if "owner" in roles or import_coldcard is None:
            t.add_row(["Entered/created Password", import_password])

    if export_pub and export_pub != "":
        pub_json = json.dumps(pub_json, indent=4)
        with open(export_pub, "w") as fp:
            fp.write(pub_json)
        print("%s was sucessfully saved." % export_pub)
    if export and export != "":
        with open(export, "w") as fp:
            fp.write(str(t))
            fp.write("\n")
            fp.write(str(t_pub))
        print("%s was sucessfully saved." % export)
    else:
        print(t_pub)
        print(t)


@cli.command()
@click.option("--path", "-p", help="Set path (when using ledger)")
@click.option(
    "--ledger-approval",
    "-a",
    is_flag=True,
    default=False,
    help="When set, you can confirm the shown pubkey on your ledger.",
)
def listkeys(path, ledger_approval):
    """Show stored keys

    Can be used to receive and approve the pubkey obtained from the ledger
    """
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()

    if hv.use_ledger:
        if path is None:
            path = hv.config["default_path"]
        t = PrettyTable(["Available Key for %s" % path])
        t.align = "l"
        ledgertx = hv.new_tx()
        ledgertx.constructTx()
        pubkey = ledgertx.ledgertx.get_pubkey(path, request_screen_approval=False)
        t.add_row([str(pubkey)])
        if ledger_approval:
            print(t)
            ledgertx.ledgertx.get_pubkey(path, request_screen_approval=True)
    else:
        t = PrettyTable(["Available Key"])
        t.align = "l"
        for key in hv.wallet.getPublicKeys():
            t.add_row([key])
    print(t)


@cli.command()
@click.option("--role", "-r", help="When set, limits the shown keys for this role")
@click.option(
    "--max-account-index",
    "-a",
    help="Set maximum account index to check pubkeys (only when using ledger)",
    default=5,
)
@click.option(
    "--max-sequence",
    "-s",
    help="Set maximum key sequence to check pubkeys (only when using ledger)",
    default=2,
)
def listaccounts(role, max_account_index, max_sequence):
    """Show stored accounts

    Can be used with the ledger to obtain all accounts that uses pubkeys derived from this ledger
    """
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()

    if hv.use_ledger:
        t = PrettyTable(["Name", "Type", "Available Key", "Path"])
        t.align = "l"
        ledgertx = hv.new_tx()
        ledgertx.constructTx()
        key_found = False
        path = None
        current_account_index = 0
        current_key_index = 0
        role_list = ["owner", "active", "posting", "memo"]
        if role:
            role_list = [role]
        while not key_found and current_account_index < max_account_index:
            for perm in role_list:
                path = ledgertx.ledgertx.build_path(perm, current_account_index, current_key_index)
                current_pubkey = ledgertx.ledgertx.get_pubkey(path)
                account = hv.wallet.getAccountFromPublicKey(str(current_pubkey))
                if account is not None:
                    t.add_row([str(account), perm, str(current_pubkey), path])
            if current_key_index < max_sequence:
                current_key_index += 1
            else:
                current_key_index = 0
                current_account_index += 1
    else:
        t = PrettyTable(["Name", "Type", "Available Key"])
        t.align = "l"
        for account in hv.wallet.getAccounts():
            t.add_row([account["name"] or "n/a", account["type"] or "n/a", account["pubkey"]])
    print(t)


@cli.command()
@click.argument("post", nargs=1)
@click.option("--weight", "-w", help="Vote weight (from 0.1 to 100.0)")
@click.option("--account", "-a", help="Voter account name")
@click.option("--export", "-e", help="When set, transaction is stored in a file")
def upvote(post, account, weight, export):
    """Upvote a post/comment

    POST is @author/permlink
    """
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if not weight:
        weight = hv.config["default_vote_weight"]
    else:
        weight = float(weight)
        if weight > 100:
            raise ValueError("Maximum vote weight is 100.0!")
        elif weight < 0:
            raise ValueError("Minimum vote weight is 0!")

    if not account:
        account = hv.config["default_account"]
    if not unlock_wallet(hv):
        return
    try:
        post = Comment(post, blockchain_instance=hv)
        tx = post.upvote(weight, voter=account)
    except exceptions.VotingInvalidOnArchivedPost:
        print("Post/Comment is older than 7 days! Did not upvote.")
        tx = {}
    export_trx(tx, export)
    tx = json.dumps(tx, indent=4)
    print(tx)


@cli.command()
@click.argument("post", nargs=1)
@click.option("--account", "-a", help="Account name")
@click.option("--export", "-e", help="When set, transaction is stored in a file")
def delete(post, account, export):
    """delete a post/comment

    POST is @author/permlink
    """
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()

    if not account:
        account = hv.config["default_account"]
    if not unlock_wallet(hv):
        return
    try:
        post = Comment(post, blockchain_instance=hv)
        tx = post.delete(account=account)
    except exceptions.VotingInvalidOnArchivedPost:
        print("Could not delete post.")
        tx = {}
    export_trx(tx, export)
    tx = json.dumps(tx, indent=4)
    print(tx)


@cli.command()
@click.argument("post", nargs=1)
@click.option("--account", "-a", help="Downvoter account name")
@click.option("--weight", "-w", default=100, help="Downvote weight (from 0.1 to 100.0)")
@click.option("--export", "-e", help="When set, transaction is stored in a file")
def downvote(post, account, weight, export):
    """Downvote a post/comment

    POST is @author/permlink
    """
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()

    weight = float(weight)
    if weight > 100:
        raise ValueError("Maximum downvote weight is 100.0!")
    elif weight < 0:
        raise ValueError("Minimum downvote weight is 0!")

    if not account:
        account = hv.config["default_account"]
    if not unlock_wallet(hv):
        return
    try:
        post = Comment(post, blockchain_instance=hv)
        tx = post.downvote(weight, voter=account)
    except exceptions.VotingInvalidOnArchivedPost:
        print("Post/Comment is older than 7 days! Did not downvote.")
        tx = {}
    export_trx(tx, export)
    tx = json.dumps(tx, indent=4)
    print(tx)


@cli.command()
@click.argument("to", nargs=1)
@click.argument("amount", nargs=1)
@click.argument("asset", nargs=1, callback=asset_callback)
@click.argument("memo", nargs=1, required=False)
@click.option("--account", "-a", help="Transfer from this account")
@click.option("--export", "-e", help="When set, transaction is stored in a file")
def transfer(to, amount, asset, memo, account, export):
    """Transfer HBD or HIVE"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if not account:
        account = hv.config["default_account"]
    if not bool(memo):
        memo = ""
    if not unlock_wallet(hv):
        return
    acc = Account(account, blockchain_instance=hv)
    tx = acc.transfer(to, amount, asset, memo)
    export_trx(tx, export)
    tx = json.dumps(tx, indent=4)
    print(tx)


@cli.command()
@click.argument("amount", nargs=1)
@click.option("--account", "-a", help="Powerup from this account")
@click.option("--to", "-t", help="Powerup this account", default=None)
@click.option("--export", "-e", help="When set, transaction is stored in a file")
def powerup(amount, account, to, export):
    """Power up (vest HIVE into Hive Power)"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if not account:
        account = hv.config["default_account"]
    if not unlock_wallet(hv):
        return
    acc = Account(account, blockchain_instance=hv)
    try:
        amount = float(amount)
    except Exception:
        amount = str(amount)
    tx = acc.transfer_to_vesting(amount, to=to)
    export_trx(tx, export)
    tx = json.dumps(tx, indent=4)
    print(tx)


@cli.command()
@click.argument("amount", nargs=1)
@click.option("--account", "-a", help="Powerup from this account")
@click.option("--export", "-e", help="When set, transaction is stored in a file")
def powerdown(amount, account, export):
    """Power down (start withdrawing VESTS from Hive Power)

    amount is in VESTS
    """
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if not account:
        account = hv.config["default_account"]
    if not unlock_wallet(hv):
        return
    acc = Account(account, blockchain_instance=hv)
    try:
        amount = float(amount)
    except Exception:
        amount = str(amount)
    tx = acc.withdraw_vesting(amount)
    export_trx(tx, export)
    tx = json.dumps(tx, indent=4)
    print(tx)


@cli.command()
@click.argument("amount", nargs=1)
@click.argument("to_account", nargs=1)
@click.option("--account", "-a", help="Delegate from this account")
@click.option("--export", "-e", help="When set, transaction is stored in a file")
def delegate(amount, to_account, account, export):
    """Delegate (start delegating VESTS to another account)

    amount is in VESTS / HIVE Power
    """
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if not account:
        account = hv.config["default_account"]
    if not unlock_wallet(hv):
        return
    acc = Account(account, blockchain_instance=hv)
    if hv.token_symbol is not None:
        amount = Amount(amount, blockchain_instance=hv)
    else:
        amount = Amount(amount, blockchain_instance=hv)
    if isinstance(amount, Amount):
        if amount.symbol == hv.token_symbol:
            amount = hv.hp_to_vests(float(amount))

    tx = acc.delegate_vesting_shares(to_account, amount)
    export_trx(tx, export)
    tx = json.dumps(tx, indent=4)
    print(tx)


@cli.command()
@click.option("--account", "-a", help="List outgoing delegations from this account")
def listdelegations(account):
    """List all outgoing delegations from an account.

    The default account is used if no other account name is given as
        option to this command.
    """
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if not account:
        account = hv.config["default_account"]
    acc = Account(account, blockchain_instance=hv)
    pt = PrettyTable(["Delegatee", hv.vests_symbol, "%s Power" % (hv.token_symbol)])
    pt.align = "r"
    start_account = ""
    limit = 100
    stop = False
    while stop is False:
        delegations = acc.get_vesting_delegations(start_account=start_account, limit=limit)
        if len(delegations) < limit:
            stop = True
        if start_account != "" and len(delegations) > 0:
            # skip first entry if it was already part of the previous call
            delegations = delegations[1:]
        for deleg in delegations:
            vests = Amount(deleg["vesting_shares"], blockchain_instance=hv)
            token_power = "%.3f" % (hv.vests_to_token_power(vests))
            pt.add_row([deleg["delegatee"], str(vests), token_power])
            start_account = deleg["delegatee"]
    print(pt)


@cli.command()
@click.argument("to", nargs=1)
@click.option(
    "--percentage", default=100, help='The percent of the withdraw to go to the "to" account'
)
@click.option("--account", "-a", help="Powerup from this account")
@click.option(
    "--auto_vest",
    help="Set to true if the from account should receive the VESTS as"
    "VESTS, or false if it should receive them as HIVE.",
    is_flag=True,
)
@click.option("--export", "-e", help="When set, transaction is stored in a file")
def powerdownroute(to, percentage, account, auto_vest, export):
    """Setup a powerdown route"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if not account:
        account = hv.config["default_account"]
    if not unlock_wallet(hv):
        return
    acc = Account(account, blockchain_instance=hv)
    tx = acc.set_withdraw_vesting_route(to, percentage, auto_vest=auto_vest)
    export_trx(tx, export)
    tx = json.dumps(tx, indent=4)
    print(tx)


@cli.command()
@click.argument("new_recovery_account", nargs=1)
@click.option("--account", "-a", help="Change the recovery account from this account")
@click.option("--export", "-e", help="When set, transaction is stored in a file")
def changerecovery(new_recovery_account, account, export):
    """Changes the recovery account with the owner key (needs 30 days to be active)"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if not account:
        account = hv.config["default_account"]
    # if not unlock_wallet(hv):
    #    return
    new_recovery_account = Account(new_recovery_account, blockchain_instance=hv)
    account = Account(account, blockchain_instance=hv)
    op = operations.Change_recovery_account(
        **{
            "account_to_recover": account["name"],
            "new_recovery_account": new_recovery_account["name"],
            "extensions": [],
        }
    )

    tb = TransactionBuilder(blockchain_instance=hv)
    tb.appendOps([op])
    if hv.unsigned:
        tb.addSigningInformation(account["name"], "owner")
        tx = tb
    else:
        key = click.prompt(
            "Owner key for %s" % account["name"], confirmation_prompt=False, hide_input=True
        )
        owner_key = PrivateKey(wif=key)
        tb.appendWif(str(owner_key))
        tb.sign()
        tx = tb.broadcast()
    export_trx(tx, export)
    tx = json.dumps(tx, indent=4)
    print(tx)


@cli.command()
@click.argument("amount", nargs=1)
@click.option("--account", "-a", help="Powerup from this account")
@click.option("--export", "-e", help="When set, transaction is stored in a file")
def convert(amount, account, export):
    """Convert HBD to HIVE (takes ~1 week to settle)"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if not account:
        account = hv.config["default_account"]
    if not unlock_wallet(hv):
        return
    acc = Account(account, blockchain_instance=hv)
    try:
        amount = float(amount)
    except Exception:
        amount = str(amount)
    tx = acc.convert(amount)
    export_trx(tx, export)
    tx = json.dumps(tx, indent=4)
    print(tx)


@cli.command()
def changewalletpassphrase():
    """Change wallet password"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if not unlock_wallet(hv, allow_wif=False):
        return
    newpassword = None
    newpassword = click.prompt("New wallet password", confirmation_prompt=True, hide_input=True)
    if not bool(newpassword):
        print("Password cannot be empty! Quitting...")
        return
    password_storage = hv.config["password_storage"]
    if password_storage == "keyring" and is_keyring_available():
        import keyring  # type: ignore

        keyring.set_password("nectar", "wallet", newpassword)
    elif password_storage == "environment":
        print(
            "The new wallet password can be stored in the UNLOCK invironment variable to skip password prompt!"
        )
    hv.wallet.changePassphrase(newpassword)


@cli.command()
@click.argument("account", nargs=-1)
def power(account):
    """Shows vote power and bandwidth"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if len(account) == 0:
        if "default_account" in hv.config:
            account = [hv.config["default_account"]]
    for name in account:
        a = Account(name, blockchain_instance=hv)
        print("\n@%s" % a.name)
        a.print_info(use_table=True)


@cli.command()
@click.argument("account", nargs=-1)
def balance(account):
    """Shows balance"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if len(account) == 0:
        if "default_account" in hv.config:
            account = [hv.config["default_account"]]
    for name in account:
        a = Account(name, blockchain_instance=hv)
        print("\n@%s" % a.name)
        t = PrettyTable(["Account", hv.token_symbol, hv.backed_token_symbol, "VESTS"])
        t.align = "r"
        t.add_row(
            [
                "Available",
                str(a.balances["available"][0]),
                str(a.balances["available"][1]),
                str(a.balances["available"][2]),
            ]
        )
        t.add_row(
            [
                "Rewards",
                str(a.balances["rewards"][0]),
                str(a.balances["rewards"][1]),
                str(a.balances["rewards"][2]),
            ]
        )
        t.add_row(
            [
                "Savings",
                str(a.balances["savings"][0]),
                str(a.balances["savings"][1]),
                "N/A",
            ]
        )
        t.add_row(
            [
                "TOTAL",
                str(a.balances["total"][0]),
                str(a.balances["total"][1]),
                str(a.balances["total"][2]),
            ]
        )
        print(t)


@cli.command()
@click.argument("account", nargs=-1, required=False)
def interest(account):
    """Get information about interest payment"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if not account:
        if "default_account" in hv.config:
            account = [hv.config["default_account"]]

    t = PrettyTable(
        ["Account", "Last Interest Payment", "Next Payment", "Interest rate", "Interest"]
    )
    t.align = "r"
    for a in account:
        a = Account(a, blockchain_instance=hv)
        i = a.interest()
        t.add_row(
            [
                a["name"],
                i["last_payment"],
                "in %s" % (i["next_payment_duration"]),
                "%.1f%%" % i["interest_rate"],
                "{:.3f} {}".format(i["interest"], hv.backed_token_symbol),
            ]
        )
    print(t)


@cli.command()
@click.argument("follow-type")
@click.option("--account", "-a", help="Get follow list for this account")
@click.option("--limit", "-l", help="Liimts the returned accounts", default=100)
def followlist(follow_type, account, limit):
    """Get information about followed lists

    follow_type can be blog
    On Hive, follow type can also be one the following: blacklisted, follow_blacklist, muted, or follow_muted
    """
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if not account:
        if "default_account" in hv.config:
            account = hv.config["default_account"]
    account = Account(account, blockchain_instance=hv)
    if follow_type == "blog":
        name_list = account.get_following(limit=limit)
    else:
        name_list = account.get_follow_list(follow_type)
        if limit and len(name_list) > limit:
            name_list = name_list[:limit]
    t = PrettyTable(["index", "name"])
    t.align = "r"
    i = 0
    for name in name_list:
        i += 1
        t.add_row([str(i), name])
    print(t)


@cli.command()
@click.argument("account", nargs=-1, required=False)
def follower(account):
    """Get information about followers"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    accounts = list(account or [])
    if not accounts and "default_account" in hv.config:
        accounts = [hv.config["default_account"]]
    for a in accounts:
        a = Account(a, blockchain_instance=hv)
        print("\nFollowers statistics for @%s (please wait...)" % a.name)
        followers = a.get_followers(False)
        if isinstance(followers, list) and not isinstance(followers, Accounts):
            raise TypeError("Expected Accounts object when raw_name_list is False")
        followers.print_summarize_table(tag_type="Followers")


@cli.command()
@click.argument("account", nargs=-1, required=False)
def following(account):
    """Get information about following"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    accounts = list(account or [])
    if not accounts and "default_account" in hv.config:
        accounts = [hv.config["default_account"]]
    for a in accounts:
        a = Account(a, blockchain_instance=hv)
        print("\nFollowing statistics for @%s (please wait...)" % a.name)
        following = a.get_following(False)
        if isinstance(following, list) and not isinstance(following, Accounts):
            raise TypeError("Expected Accounts object when raw_name_list is False")
        following.print_summarize_table(tag_type="Following")


@cli.command()
@click.argument("account", nargs=-1, required=False)
def muter(account):
    """Get information about muter"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    accounts = list(account or [])
    if not accounts and "default_account" in hv.config:
        accounts = [hv.config["default_account"]]
    for a in accounts:
        a = Account(a, blockchain_instance=hv)
        print("\nMuters statistics for @%s (please wait...)" % a.name)
        muters = a.get_muters(False)
        if isinstance(muters, list) and not isinstance(muters, Accounts):
            raise TypeError("Expected Accounts object when raw_name_list is False")
        muters.print_summarize_table(tag_type="Muters")


@cli.command()
@click.argument("account", nargs=-1, required=False)
def muting(account):
    """Get information about muting"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    accounts = list(account or [])
    if not accounts and "default_account" in hv.config:
        accounts = [hv.config["default_account"]]
    for a in accounts:
        a = Account(a, blockchain_instance=hv)
        print("\nMuting statistics for @%s (please wait...)" % a.name)
        muting = a.get_mutings(False)
        if isinstance(muting, list) and not isinstance(muting, Accounts):
            raise TypeError("Expected Accounts object when raw_name_list is False")
        muting.print_summarize_table(tag_type="Muting")


@cli.command()
@click.argument("account", nargs=1, required=False)
@click.option("--limit", "-l", help="Limits shown notifications")
@click.option(
    "--all",
    "-a",
    help="Show all notifications (when not set, only unread are shown)",
    is_flag=True,
    default=False,
)
@click.option(
    "--mark_as_read",
    "-m",
    help="Broadcast a mark all as read custom json",
    is_flag=True,
    default=False,
)
@click.option("--replies", "-r", help="Show only replies", is_flag=True, default=False)
@click.option("--mentions", "-t", help="Show only mentions", is_flag=True, default=False)
@click.option("--follows", "-f", help="Show only follows", is_flag=True, default=False)
@click.option("--votes", "-v", help="Show only upvotes", is_flag=True, default=False)
@click.option("--reblogs", "-b", help="Show only reblogs", is_flag=True, default=False)
@click.option(
    "--reverse", "-s", help="Reverse sorting of notifications", is_flag=True, default=False
)
def notifications(
    account, limit, all, mark_as_read, replies, mentions, follows, votes, reblogs, reverse
):
    """Show notifications of an account"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if account is None or account == "":
        if "default_account" in hv.config:
            account = hv.config["default_account"]
    if mark_as_read and not unlock_wallet(hv):
        return
    if not replies and not mentions and not follows and not votes and not reblogs:
        show_all = True
    else:
        show_all = False
    account = Account(account, blockchain_instance=hv)
    t = PrettyTable(["Date", "Type", "Message"], hrules=0)
    t.align = "r"
    last_read = None
    if limit is not None:
        limit = int(limit)
    all_notifications = account.get_notifications(only_unread=not all, limit=limit)
    if reverse:
        all_notifications = all_notifications[::-1]
    for note in all_notifications:
        if not show_all:
            if note["type"] == "reblog" and not reblogs:
                continue
            elif note["type"] == "reply" and not replies:
                continue
            elif note["type"] == "reply_comment" and not replies:
                continue
            elif note["type"] == "mention" and not mentions:
                continue
            elif note["type"] == "follow" and not follows:
                continue
            elif note["type"] == "vote" and not votes:
                continue
        # Handle date field which might be string or datetime
        date_obj = note["date"]
        if isinstance(date_obj, str):
            # Parse ISO format date string
            from datetime import datetime

            date_obj = datetime.fromisoformat(date_obj.replace("Z", "+00:00"))
        t.add_row(
            [
                str(date_obj),
                note["type"],
                note["msg"],
            ]
        )
        last_read = date_obj
    print(t)
    if mark_as_read:
        account.mark_notifications_as_read(last_read=last_read)


@cli.command()
@click.argument("account", nargs=1, required=False)
def permissions(account):
    """Show permissions of an account"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if not account:
        if "default_account" in hv.config:
            account = hv.config["default_account"]
    account = Account(account, blockchain_instance=hv)
    t = PrettyTable(["Permission", "Threshold", "Key/Account"], hrules=0)
    t.align = "r"
    for permission in ["owner", "active", "posting"]:
        auths = []
        for type_ in ["account_auths", "key_auths"]:
            for authority in account[permission][type_]:
                auths.append("%s (%d)" % (authority[0], authority[1]))
        t.add_row(
            [
                permission,
                account[permission]["weight_threshold"],
                "\n".join(auths),
            ]
        )
    print(t)


@cli.command()
@click.argument("foreign_account", nargs=1, required=False)
@click.option(
    "--permission", default="posting", help='The permission to grant (defaults to "posting")'
)
@click.option("--account", "-a", help="The account to allow action for")
@click.option(
    "--weight",
    "-w",
    help="The weight to use instead of the (full) threshold. "
    "If the weight is smaller than the threshold, "
    "additional signatures are required",
)
@click.option(
    "--threshold",
    "-t",
    help="The permission's threshold that needs to be reached by signatures to be able to interact",
)
@click.option("--export", "-e", help="When set, transaction is stored in a file")
def allow(foreign_account, permission, account, weight, threshold, export):
    """Allow an account/key to interact with your account

    foreign_account: The account or key that will be allowed to interact with account.
        When not given, password will be asked, from which a public key is derived.
        This derived key will then interact with your account.
    """
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if not account:
        account = hv.config["default_account"]
    if not unlock_wallet(hv):
        return
    if permission not in ["posting", "active", "owner"]:
        print("Wrong permission, please use: posting, active or owner!")
        return
    acc = Account(account, blockchain_instance=hv)
    if not foreign_account:
        from nectargraphenebase.account import PasswordKey

        pwd = click.prompt("Password for Key Derivation", confirmation_prompt=True, hide_input=True)
        foreign_account = format(PasswordKey(account, pwd, permission).get_public(), hv.prefix)
    if threshold is not None:
        threshold = int(threshold)
    tx = acc.allow(foreign_account, weight=weight, permission=permission, threshold=threshold)
    export_trx(tx, export)
    tx = json.dumps(tx, indent=4)
    print(tx)


@cli.command()
@click.argument("foreign_account", nargs=1, required=False)
@click.option(
    "--permission", "-p", default="posting", help='The permission to grant (defaults to "posting")'
)
@click.option("--account", "-a", help="The account to disallow action for")
@click.option(
    "--weight",
    "-w",
    type=int,
    help="The weight to use instead of the (full) threshold. "
    "If the weight is smaller than the threshold, "
    "additional signatures are required",
)
@click.option(
    "--threshold",
    "-t",
    help="The permission's threshold that needs to be reached by signatures to be able to interact",
)
@click.option("--export", "-e", help="When set, transaction is stored in a file")
def disallow(foreign_account, permission, account, weight, threshold, export):
    """Remove allowance an account/key to interact with your account"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if not account:
        account = hv.config["default_account"]
    if not unlock_wallet(hv):
        return
    if permission not in ["posting", "active", "owner"]:
        print("Wrong permission, please use: posting, active or owner!")
        return
    if threshold is not None:
        threshold = int(threshold)
    acc = Account(account, blockchain_instance=hv)
    if not foreign_account:
        from nectargraphenebase.account import PasswordKey

        pwd = click.prompt("Password for Key Derivation", confirmation_prompt=True)
        foreign_account = format(PasswordKey(account, pwd, permission).get_public(), hv.prefix)
    elif isinstance(foreign_account, (list, tuple)):
        foreign_account = foreign_account[0]
    tx = acc.disallow(foreign_account, permission=permission, weight=weight, threshold=threshold)
    export_trx(tx, export)
    tx = json.dumps(tx, indent=4)
    print(tx)


@cli.command()
@click.argument("creator", nargs=1, required=True)
@click.option(
    "--fee",
    help="When fee is 0 (default) a subsidized account is claimed and can be created later with create_claimed_account",
    default=0.0,
)
@click.option(
    "--number",
    "-n",
    help="Number of subsidized accounts to be claimed (default = 1), when fee = 0 HIVE",
    default=1,
)
@click.option(
    "--export",
    "-e",
    help="When set, transaction is stored in a file (should be used with number = 1)",
)
def claimaccount(creator, fee, number, export):
    """Claim account for claimed account creation."""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if not creator:
        creator = hv.config["default_account"]
    if not unlock_wallet(hv):
        return
    creator = Account(creator, blockchain_instance=hv)
    fee = Amount("{:.3f} {}".format(float(fee), hv.token_symbol), blockchain_instance=hv)
    tx = None
    if float(fee) > 0:
        tx = hv.claim_account(creator, fee=fee)
    elif float(fee) == 0:
        rc = RC(blockchain_instance=hv)
        current_costs = rc.claim_account(tx_size=200)
        current_mana = creator.get_rc_manabar()["current_mana"]
        last_mana = current_mana
        cnt = 0
        print(
            "Current costs %.2f G RC - current mana %.2f G RC"
            % (current_costs / 1e9, current_mana / 1e9)
        )
        print("Account can claim %d accounts" % (int(current_mana / current_costs)))
        while current_costs + 10 < current_mana and cnt < number:
            if cnt > 0:
                print(
                    "Current costs %.2f G RC - current mana %.2f G RC"
                    % (current_costs / 1e9, current_mana / 1e9)
                )
                tx = json.dumps(tx, indent=4)
                print(tx)
            cnt += 1
            tx = hv.claim_account(creator, fee=fee)
            time.sleep(10)
            creator.refresh()
            current_mana = creator.get_rc_manabar()["current_mana"]
            print("Account claimed and %.2f G RC paid." % ((last_mana - current_mana) / 1e9))
            last_mana = current_mana
        if cnt == 0:
            print("Not enough RC for a claim!")
    else:
        tx = hv.claim_account(creator, fee=fee)
    if tx is not None:
        export_trx(tx, export)
        tx = json.dumps(tx, indent=4)
        print(tx)


@cli.command()
@click.argument("account", nargs=1, required=True)
@click.option(
    "--owner", help="Main owner public key - when not given, a passphrase is used to create keys."
)
@click.option(
    "--active", help="Active public key - when not given, a passphrase is used to create keys."
)
@click.option(
    "--posting", help="posting public key - when not given, a passphrase is used to create keys."
)
@click.option(
    "--memo", help="Memo public key - when not given, a passphrase is used to create keys."
)
@click.option("--import-pub", "-i", help="Load public keys from file.")
@click.option("--export", "-e", help="When set, transaction is stored in a file")
def changekeys(account, owner, active, posting, memo, import_pub, export):
    """Changes all keys for the specified account
    Keys are given in their public form.
    Asks for the owner key for broadcasting the op to the chain."""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    account = Account(account, blockchain_instance=hv)

    if import_pub and import_pub != "":
        owner, active, posting, memo = import_pubkeys(import_pub)

    if owner is None and active is None and memo is None and posting is None:
        raise ValueError("All pubkeys are None or empty!")
    if owner == "" or owner is None:
        owner = account["owner"]["key_auths"][0][0]
    if active == "" or active is None:
        active = account["active"]["key_auths"][0][0]
    if posting == "" or posting is None:
        posting = account["posting"]["key_auths"][0][0]
    if memo == "" or memo is None:
        memo = account["memo_key"]

    t = PrettyTable(["Key", "Value"])
    t.align = "l"
    t.add_row(["account", account["name"]])
    t.add_row(["new owner pubkey", str(owner)])
    t.add_row(["new active pubkey", str(active)])
    t.add_row(["new posting pubkey", str(posting)])
    t.add_row(["new memo pubkey", str(memo)])
    print(t)
    if not hv.unsigned:
        wif = click.prompt(
            "Owner key for %s" % account["name"], confirmation_prompt=False, hide_input=True
        )
        hv.wallet.setKeys([wif])

    tx = hv.update_account(
        account,
        owner_key=owner,
        active_key=active,
        posting_key=posting,
        memo_key=memo,
        password=None,
    )
    export_trx(tx, export)
    tx = json.dumps(tx, indent=4)
    print(tx)


@cli.command()
@click.argument("accountname", nargs=1, required=True)
@click.option("--account", "-a", help="Account that pays the fee or uses account tickets")
@click.option(
    "--owner", help="Main public owner key - when not given, a passphrase is used to create keys."
)
@click.option(
    "--active", help="Active public key - when not given, a passphrase is used to create keys."
)
@click.option(
    "--memo", help="Memo public key - when not given, a passphrase is used to create keys."
)
@click.option(
    "--posting", help="posting public key - when not given, a passphrase is used to create keys."
)
@click.option(
    "--wif",
    "-w",
    help="Defines how many times the password is replaced by its WIF representation for password based keys (default = 0).",
    default=0,
)
@click.option(
    "--create-claimed-account",
    "-c",
    help="Instead of paying the account creation fee a subsidized account is created.",
    is_flag=True,
    default=False,
)
@click.option("--import-pub", "-i", help="Load public keys from file.")
@click.option("--export", "-e", help="When set, transaction is stored in a file")
def newaccount(
    accountname,
    account,
    owner,
    active,
    memo,
    posting,
    wif,
    create_claimed_account,
    import_pub,
    export,
):
    """Create a new account
    Default setting is that a fee is payed for account creation
    Use --create-claimed-account for free account creation

    Please use keygen and set public keys
    """
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if not account:
        account = hv.config["default_account"]
    if not unlock_wallet(hv):
        return
    acc = Account(account, blockchain_instance=hv)
    if import_pub and import_pub != "":
        owner, active, posting, memo = import_pubkeys(import_pub)
        if create_claimed_account:
            tx = hv.create_claimed_account(
                accountname,
                creator=acc,
                owner_key=owner,
                active_key=active,
                memo_key=memo,
                posting_key=posting,
            )
        else:
            tx = hv.create_account(
                accountname,
                creator=acc,
                owner_key=owner,
                active_key=active,
                memo_key=memo,
                posting_key=posting,
            )
    elif owner is None or active is None or memo is None or posting is None:
        import_password = click.prompt(
            "Keys were not given - Passphrase is used to create keys\n New Account Passphrase",
            confirmation_prompt=True,
            hide_input=True,
        )
        if not import_password:
            print("You cannot chose an empty password")
            return
        password = generate_password(import_password, wif)
        if create_claimed_account:
            tx = hv.create_claimed_account(accountname, creator=acc, password=password)
        else:
            tx = hv.create_account(accountname, creator=acc, password=password)
    else:
        if create_claimed_account:
            tx = hv.create_claimed_account(
                accountname,
                creator=acc,
                owner_key=owner,
                active_key=active,
                memo_key=memo,
                posting_key=posting,
            )
        else:
            tx = hv.create_account(
                accountname,
                creator=acc,
                owner_key=owner,
                active_key=active,
                memo_key=memo,
                posting_key=posting,
            )
    export_trx(tx, export)
    tx = json.dumps(tx, indent=4)
    print(tx)


@cli.command()
@click.argument("variable", nargs=1, required=False)
@click.argument("value", nargs=1, required=False)
@click.option("--account", "-a", help="setprofile as this user")
@click.option("--pair", "-p", help='"Key=Value" pairs', multiple=True)
@click.option("--export", "-e", help="When set, transaction is stored in a file")
def setprofile(variable, value, account, pair, export):
    """Set a variable in an account\'s profile"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    keys = []
    values = []
    if pair:
        for p in pair:
            key, value = p.split("=")
            keys.append(key)
            values.append(value)
    if variable and value:
        keys.append(variable)
        values.append(value)

    profile = Profile(keys, values)

    if not account:
        account = hv.config["default_account"]
    if not unlock_wallet(hv):
        return
    acc = Account(account, blockchain_instance=hv)

    json_metadata = Profile(acc["json_metadata"] if acc["json_metadata"] else {})
    json_metadata.update(profile)
    tx = acc.update_account_profile(json_metadata)
    export_trx(tx, export)
    tx = json.dumps(tx, indent=4)
    print(tx)


@cli.command()
@click.argument("variable", nargs=-1, required=True)
@click.option("--account", "-a", help="delprofile as this user")
@click.option("--export", "-e", help="When set, transaction is stored in a file")
def delprofile(variable, account, export):
    """Delete a variable in an account's profile"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()

    if not account:
        account = hv.config["default_account"]
    if not unlock_wallet(hv):
        return
    acc = Account(account, blockchain_instance=hv)
    json_metadata = Profile(acc["json_metadata"])

    for var in variable:
        json_metadata.remove(var)

    tx = acc.update_account_profile(json_metadata)
    export_trx(tx, export)
    tx = json.dumps(tx, indent=4)
    print(tx)


@cli.command()
@click.argument("account", nargs=1, required=True)
@click.option(
    "--roles",
    "-r",
    help="Import specified keys (owner, active, posting, memo).",
    default=["active", "posting", "memo"],
)
@click.option(
    "--import-coldcard",
    "-i",
    help="Text file with a BIP85 WIF generated by a coldcard. The imported WIF is used as passphrase",
)
@click.option(
    "--wif",
    "-w",
    help="Defines how many times the password is replaced by its WIF representation for password based keys (default = 0 or 1 when importing a cold card wif).",
)
def importaccount(account, roles, import_coldcard, wif):
    """Import an account using a passphrase"""
    from nectargraphenebase.account import PasswordKey

    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if not unlock_wallet(hv):
        return
    account = Account(account, blockchain_instance=hv)
    imported = False
    if import_coldcard is None:
        password = click.prompt("Account Passphrase", confirmation_prompt=False, hide_input=True)
        if not password:
            print("You cannot chose an empty Passphrase")
            return
    else:
        password, path = import_coldcard_wif(import_coldcard)
    if wif is not None:
        wif = int(wif)
    elif import_coldcard is not None:
        wif = 1
    else:
        wif = 0

    password = generate_password(password, wif)

    if "owner" in roles:
        owner_key = PasswordKey(account["name"], password, role="owner")
        owner_pubkey = format(owner_key.get_public_key(), hv.prefix)
        if owner_pubkey in [x[0] for x in account["owner"]["key_auths"]]:
            print("Importing owner key!")
            owner_privkey = owner_key.get_private_key()
            hv.wallet.addPrivateKey(owner_privkey)
            imported = True

    if "active" in roles:
        active_key = PasswordKey(account["name"], password, role="active")
        active_pubkey = format(active_key.get_public_key(), hv.prefix)
        if active_pubkey in [x[0] for x in account["active"]["key_auths"]]:
            print("Importing active key!")
            active_privkey = active_key.get_private_key()
            hv.wallet.addPrivateKey(active_privkey)
            imported = True

    if "posting" in roles:
        posting_key = PasswordKey(account["name"], password, role="posting")
        posting_pubkey = format(posting_key.get_public_key(), hv.prefix)
        if posting_pubkey in [x[0] for x in account["posting"]["key_auths"]]:
            print("Importing posting key!")
            posting_privkey = posting_key.get_private_key()
            hv.wallet.addPrivateKey(posting_privkey)
            imported = True

    if "memo" in roles:
        memo_key = PasswordKey(account["name"], password, role="memo")
        memo_pubkey = format(memo_key.get_public_key(), hv.prefix)
        if memo_pubkey == account["memo_key"]:
            print("Importing memo key!")
            memo_privkey = memo_key.get_private_key()
            hv.wallet.addPrivateKey(memo_privkey)
            imported = True

    if not imported:
        print("No matching key(s) found. Password correct?")


@cli.command()
@click.option("--account", "-a", help="The account to updatememokey action for")
@click.option("--key", help="The new memo key")
@click.option("--export", "-e", help="When set, transaction is stored in a file")
def updatememokey(account, key, export):
    """Update an account\'s memo key"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if not account:
        account = hv.config["default_account"]
    if not unlock_wallet(hv):
        return
    acc = Account(account, blockchain_instance=hv)
    if not key:
        from nectargraphenebase.account import PasswordKey

        pwd = click.prompt(
            "Password for Memo Key Derivation", confirmation_prompt=True, hide_input=True
        )
        memo_key = PasswordKey(account, pwd, "memo")
        key = format(memo_key.get_public_key(), hv.prefix)
        memo_privkey = memo_key.get_private_key()
        if not hv.nobroadcast:
            hv.wallet.addPrivateKey(memo_privkey)
    tx = acc.update_memo_key(key)
    export_trx(tx, export)
    tx = json.dumps(tx, indent=4)
    print(tx)


@cli.command()
@click.argument("authorperm", nargs=1)
@click.argument("beneficiaries", nargs=-1)
@click.option("--export", "-e", help="When set, transaction is stored in a file")
def beneficiaries(authorperm, beneficiaries, export):
    """Set beneficaries"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    c = Comment(authorperm, blockchain_instance=hv)
    account = c["author"]

    if not account:
        account = hv.config["default_account"]
    if not unlock_wallet(hv):
        return

    options = {
        "author": c["author"],
        "permlink": c["permlink"],
        "max_accepted_payout": c["max_accepted_payout"],
        "allow_votes": c["allow_votes"],
        "allow_curation_rewards": c["allow_curation_rewards"],
    }
    if "percent_hbd" in c:
        options["percent_hbd"] = c["percent_hbd"]

    if isinstance(beneficiaries, tuple) and len(beneficiaries) == 1:
        beneficiaries = beneficiaries[0].split(",")
    beneficiaries_list_sorted = derive_beneficiaries(beneficiaries)
    for b in beneficiaries_list_sorted:
        Account(b["account"], blockchain_instance=hv)
    tx = hv.comment_options(options, authorperm, beneficiaries_list_sorted, account=account)
    export_trx(tx, export)
    tx = json.dumps(tx, indent=4)
    print(tx)


@cli.command()
@click.argument("message_file", nargs=1, required=False)
@click.option("--account", "-a", help="Account which should sign")
@click.option(
    "--verify", "-v", help="Verify a message instead of signing it", is_flag=True, default=False
)
def message(message_file, account, verify):
    """Sign and verify a message"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if not account:
        account = hv.config["default_account"]
    if message_file is not None:
        with open(message_file) as f:
            message = f.read()
    elif verify:
        print(
            "Please store the signed message into a text file and append the file path to hive-nectar message -v"
        )
        return
    else:
        message = input("Enter message: ")
    m = Message(message, blockchain_instance=hv)
    if verify:
        if m.verify():
            print("Could verify message!")
        else:
            print("Could not verify message!")
    else:
        if not unlock_wallet(hv):
            return
        signed = m.sign(account)
        out = signed if isinstance(signed, str) else json.dumps(signed, indent=4)
    if message_file is not None and not verify:
        with open(message_file, "w", encoding="utf-8") as f:
            f.write(out)
    elif not verify:
        print(out)


@cli.command()
@click.argument("memo", nargs=-1)
@click.option("--account", "-a", help="Account which decrypts the memo with its memo key")
@click.option(
    "--output", "-o", help="Output file name. Result is stored, when set instead of printed."
)
@click.option(
    "--info",
    "-i",
    help="Shows information about public keys and used nonce",
    is_flag=True,
    default=False,
)
@click.option("--text", "-t", help="Reads the text file content", is_flag=True, default=False)
@click.option("--binary", "-b", help="Reads the binary file content", is_flag=True, default=False)
def decrypt(memo, account, output, info, text, binary):
    """decrypt a (or more than one) decrypted memo/file with your memo key"""
    if text and binary:
        print("You cannot set text and binary!")
        return
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if not account:
        account = hv.config["default_account"]
    m = Memo(from_account=None, to_account=account, blockchain_instance=hv)

    if not unlock_wallet(hv):
        return
    for entry in memo:
        print("\n")
        if not binary and info:
            from_key, to_key, nonce = m.extract_decrypt_memo_data(entry)
            try:
                from_account = hv.wallet.getAccountFromPublicKey(str(from_key))
                to_account = hv.wallet.getAccountFromPublicKey(str(to_key))
                if from_account is not None:
                    print("from: %s" % str(from_account))
                else:
                    print("from: %s" % str(from_key))
                if to_account is not None:
                    print("to: %s" % str(to_account))
                else:
                    print("to: %s" % str(to_key))
                print("nonce: %s" % nonce)
            except Exception:
                print("from: %s" % str(from_key))
                print("to: %s" % str(to_key))
                print("nonce: %s" % nonce)
        if text:
            with open(entry) as f:
                message = f.read()
        elif binary:
            if output is None:
                output = entry + ".dec"
            ret = m.decrypt_binary(entry, output, buffer_size=2048)
            if info:
                if ret is None:
                    print("No information available for decrypt_binary result")
                    return
                t = PrettyTable(["Key", "Value"])
                t.align = "l"
                t.add_row(["file", entry])
                for key in ret:
                    t.add_row([key, ret[key]])
                print(t)
        else:
            message = entry
        if text:
            out = m.decrypt(message)
            if out is None:
                raise ValueError("Failed to decrypt message")
            if output is None:
                output = entry
            with open(output, "w", encoding="utf-8") as f:
                f.write(out)
        elif not binary:
            out = m.decrypt(message)
            if out is None:
                raise ValueError("Failed to decrypt message")
            if info:
                print("message: %s" % out)
            if output:
                with open(output, "w", encoding="utf-8") as f:
                    f.write(out)
            elif not info:
                print(out)


@cli.command()
@click.argument("receiver", nargs=1)
@click.argument("memo", nargs=-1)
@click.option("--account", "-a", help="Account which encrypts the memo with its memo key")
@click.option(
    "--output", "-o", help="Output file name. Result is stored, when set instead of printed."
)
@click.option("--text", "-t", help="Reads the text file content", is_flag=True, default=False)
@click.option("--binary", "-b", help="Reads the binary file content", is_flag=True, default=False)
def encrypt(receiver, memo, account, output, text, binary):
    """encrypt a (or more than one) memo text/file with the your memo key"""
    if text and binary:
        print("You cannot set text and binary!")
        return
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if not account:
        account = hv.config["default_account"]
    m = Memo(from_account=account, to_account=receiver, blockchain_instance=hv)
    if not unlock_wallet(hv):
        return
    for entry in memo:
        print("\n")
        if text:
            with open(entry) as f:
                message = f.read()
            if message[0] == "#":
                message = message[1:]
        elif binary:
            if output is None:
                output = entry + ".enc"
            m.encrypt_binary(entry, output, buffer_size=2048)
        else:
            message = entry
            if message[0] == "#":
                message = message[1:]

        if text:
            encrypted = m.encrypt(message)
            if encrypted is None:
                print("Failed to encrypt message")
                return
            out = encrypted.get("message") if isinstance(encrypted, dict) else encrypted
            if out is None:
                print("Failed to encrypt message")
                return
            if output is None:
                output = entry
            with open(output, "w", encoding="utf-8") as f:
                f.write(out)
        elif not binary:
            encrypted = m.encrypt(message)
            if encrypted is None:
                print("Failed to encrypt message")
                return
            out = encrypted.get("message") if isinstance(encrypted, dict) else encrypted
            if out is None:
                print("Failed to encrypt message")
                return
            if output is None:
                print(out)
            else:
                with open(output, "w", encoding="utf-8") as f:
                    f.write(out)


@cli.command()
@click.argument("image", nargs=1)
@click.option("--account", "-a", help="Account name")
@click.option("--image-name", "-n", help="Image name")
def uploadimage(image, account, image_name):
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if not account:
        account = hv.config["default_account"]
    if not unlock_wallet(hv):
        return
    iu = ImageUploader(blockchain_instance=hv)
    tx = iu.upload(image, account, image_name)
    if image_name is None:
        print("![](%s)" % tx["url"])
    else:
        print("![{}]({})".format(image_name, tx["url"]))


@cli.command()
@click.argument("permlink", nargs=-1)
@click.option("--account", "-a", help="Account are you posting from")
@click.option(
    "--save",
    "-s",
    help="Saves markdown in current directoy as date_permlink.md",
    is_flag=True,
    default=False,
)
@click.option("--export", "-e", default=None, help="Export markdown to given a md-file name")
def download(permlink, account, save, export):
    """Download body with yaml header"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if account is None:
        account = hv.config["default_account"]
    account = Account(account, blockchain_instance=hv)
    if len(permlink) == 0:
        permlink = []
        progress_length = account.virtual_op_count()
        print("Reading post history...")
        last_index = 0
        with click.progressbar(length=progress_length) as bar:
            for h in account.history(only_ops=["comment"]):
                if h["parent_author"] != "":
                    continue
                if h["author"] != account["name"]:
                    continue
                if h["permlink"] in permlink:
                    continue
                else:
                    permlink.append(h["permlink"])
                    bar.update(h["index"] - last_index)
                    last_index = h["index"]

    for p in permlink:
        if p[0] == "@":
            authorperm = p
        elif os.path.exists(p):
            with open(p) as f:
                content = f.read()
            body, parameter = seperate_yaml_dict_from_body(content)
            if "author" in parameter and "permlink" in parameter:
                authorperm = construct_authorperm(parameter["author"], parameter["permlink"])
            else:
                authorperm = construct_authorperm(account["name"], p)
        else:
            authorperm = construct_authorperm(account["name"], p)
        if len(permlink) > 1:
            print(authorperm)
        comment = Comment(authorperm, blockchain_instance=hv)
        if comment.parent_author != "" and comment.parent_permlink != "":
            reply_identifier = construct_authorperm(comment.parent_author, comment.parent_permlink)
        else:
            reply_identifier = None

        yaml_prefix = "---\n"
        if comment["title"] != "":
            yaml_prefix += 'title: "%s"\n' % comment["title"]
        yaml_prefix += "permlink: %s\n" % comment["permlink"]
        yaml_prefix += "author: %s\n" % comment["author"]
        if "author" in comment.json_metadata:
            yaml_prefix += "authored by: %s\n" % comment.json_metadata["author"]
        if "description" in comment.json_metadata:
            yaml_prefix += 'description: "%s"\n' % comment.json_metadata["description"]
        if "canonical_url" in comment.json_metadata:
            yaml_prefix += "canonical_url: %s\n" % comment.json_metadata["canonical_url"]
        if "app" in comment.json_metadata:
            yaml_prefix += "app: %s\n" % comment.json_metadata["app"]
        if "last_update" in comment.json():
            yaml_prefix += "last_update: %s\n" % comment.json()["last_update"]
        else:
            yaml_prefix += "last_update: %s\n" % comment.json()["updated"]
        yaml_prefix += "max_accepted_payout: %s\n" % str(comment["max_accepted_payout"])
        if "percent_hbd" in comment:
            yaml_prefix += "percent_hbd: %s\n" % str(comment["percent_hbd"])
        if "tags" in comment.json_metadata:
            if (
                len(comment.json_metadata["tags"]) > 0
                and comment["category"] != comment.json_metadata["tags"][0]
                and len(comment["category"]) > 0
            ):
                yaml_prefix += "community: %s\n" % comment["category"]
            yaml_prefix += "tags: %s\n" % ",".join(comment.json_metadata["tags"])
        if "beneficiaries" in comment:
            beneficiaries = []
            for b in comment["beneficiaries"]:
                beneficiaries.append("{}:{:.2f}%".format(b["account"], b["weight"] / 10000 * 100))
            if len(beneficiaries) > 0:
                yaml_prefix += "beneficiaries: %s\n" % ",".join(beneficiaries)
        if reply_identifier is not None:
            yaml_prefix += "reply_identifier: %s\n" % reply_identifier
        yaml_prefix += "---\n"
        if save or export is not None:
            if export is None or len(permlink) > 0:
                export = (
                    comment.json()["created"].replace(":", "-") + "_" + comment["permlink"] + ".md"
                )
            if export[-3:] != ".md":
                export += ".md"

            with open(export, "w", encoding="utf-8") as f:
                f.write(yaml_prefix + comment["body"])
        else:
            print(yaml_prefix + comment["body"])


@cli.command()
@click.argument("markdown-file", nargs=1)
@click.option("--account", "-a", help="Account are you posting from")
@click.option("--title", "-t", help="Title of the post")
@click.option("--tags", "-g", help="A komma separated list of tags to go with the post.")
@click.option("--community", "-c", help=" Name of the community (optional)")
@click.option(
    "--beneficiaries", "-b", help="Post beneficiaries (komma separated, e.g. a:10%,b:20%)"
)
@click.option("--percent-hbd", "-h", help="50% HBD/50% HP is 10000 (default), 100% HP is 0")
@click.option("--max-accepted-payout", "-m", help="Default is 1000000.000 [HBD]")
@click.option(
    "--no-parse-body",
    "-n",
    help="Disable parsing of links, tags and images",
    is_flag=True,
    default=False,
)
def createpost(
    markdown_file,
    account,
    title,
    tags,
    community,
    beneficiaries,
    percent_hbd,
    max_accepted_payout,
    no_parse_body,
):
    """Creates a new markdown file with YAML header"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()

    if account is None:
        account = input("author: ")
    if title is None:
        title = input("title: ")
    if tags is None:
        tags = input("tags (comma seperated): ")
    if community is None:
        community_found = False
        while not community_found:
            community_name = input("community account (name or title): ")
            try:
                community = Community(community_name)
            except Exception:
                c = Communities(limit=1000)
                comm_cand = c.search_title(community_name)
                if len(comm_cand) == 0:
                    print("No community could be found!")
                    continue
                print(comm_cand.printAsTable())
                index = input("Enter community Nr:")
                if int(index) - 1 >= len(comm_cand):
                    continue
                community = comm_cand[int(index) - 1]
            ret = input(
                "Selected community: {} - {} [yes/no]? ".format(
                    community["name"], community["title"]
                )
            )
            if ret in ["y", "yes"]:
                community_found = True
        community = community["name"]

    if beneficiaries is None:
        beneficiaries = input(
            "beneficiaries (komma separated, e.g. a:10%,b:20%) [return to skip]: "
        )
    if percent_hbd is None:
        ret = None
        while ret is None:
            ret = input("50% or 100% Hive Power as post reward [50 or 100]? ")
            if ret not in ["50", "100"]:
                ret = None
        if ret == "50":
            percent_hbd = 10000
        else:
            percent_hbd = 0

    if max_accepted_payout is None:
        max_accepted_payout = input("max accepted payout [return to skip]: ")
    yaml_prefix = "---\n"
    yaml_prefix += 'title: "%s"\n' % title
    yaml_prefix += "author: %s\n" % account
    yaml_prefix += "tags: %s\n" % tags
    yaml_prefix += "percent_hbd: %d\n" % percent_hbd
    if community is not None and community != "":
        yaml_prefix += "community: %s\n" % community
    if beneficiaries is not None and beneficiaries != "":
        yaml_prefix += "beneficiaries: %s\n" % beneficiaries
    if max_accepted_payout is not None and max_accepted_payout != "":
        yaml_prefix += "max_accepted_payout: %s\n" % max_accepted_payout
    yaml_prefix += "---\n"
    with open(markdown_file, "w", encoding="utf-8") as f:
        f.write(yaml_prefix)


@cli.command()
@click.argument("markdown-file", nargs=1)
@click.option("--account", "-a", help="Account are you posting from")
@click.option("--title", "-t", help="Title of the post")
@click.option("--permlink", "-p", help="Manually set the permlink (optional)")
@click.option("--tags", "-g", help="A komma separated list of tags to go with the post.")
@click.option(
    "--reply-identifier",
    "-r",
    help=" Identifier of the parent post/comment, when set a comment is broadcasted",
)
@click.option("--community", "-c", help=" Name of the community (optional)")
@click.option(
    "--canonical-url",
    "-u",
    help="Canonical url, can also set to https://hive.blog or https://peakd.com (optional)",
)
@click.option(
    "--beneficiaries", "-b", help="Post beneficiaries (komma separated, e.g. a:10%,b:20%)"
)
@click.option("--percent-hbd", "-h", help="50% HBD/50% HP is 10000 (default), 100% HP is 0")
@click.option("--max-accepted-payout", "-m", help="Default is 1000000.000 [HBD]")
@click.option(
    "--no-parse-body",
    "-n",
    help="Disable parsing of links, tags and images",
    is_flag=True,
    default=False,
)
@click.option(
    "--no-patch-on-edit",
    "-e",
    help="Disable patch posting on edits (when the permlink already exists)",
    is_flag=True,
    default=False,
)
@click.option("--export", help="When set, transaction is stored in a file")
def post(
    markdown_file,
    account,
    title,
    permlink,
    tags,
    reply_identifier,
    community,
    canonical_url,
    beneficiaries,
    percent_hbd,
    max_accepted_payout,
    no_parse_body,
    no_patch_on_edit,
    export,
):
    """broadcasts a post/comment. All image links which links to a file will be uploaded.
    The yaml header can contain:

    ---
    title: your title
    tags: tag1,tag2
    community: hive-100000
    beneficiaries: hive-nectar:5%,thecrazygm:5%
    ---

    """
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()

    with open(markdown_file) as f:
        content = f.read()
    body, parameter = seperate_yaml_dict_from_body(content)
    if title is not None:
        parameter["title"] = title
    if account is not None:
        parameter["author"] = account
    if tags is not None:
        parameter["tags"] = tags
    if permlink is not None:
        parameter["permlink"] = permlink
    if beneficiaries is not None:
        parameter["beneficiaries"] = beneficiaries
    if community is not None:
        parameter["community"] = community
    if reply_identifier is not None:
        parameter["reply_identifier"] = reply_identifier
    if percent_hbd is not None:
        parameter["percent_hbd"] = percent_hbd
    elif "percent-hbd" in parameter:
        parameter["percent_hbd"] = parameter["percent-hbd"]
    if max_accepted_payout is not None:
        parameter["max_accepted_payout"] = max_accepted_payout
    elif "max-accepted-payout" in parameter:
        parameter["max_accepted_payout"] = parameter["max-accepted-payout"]

    if canonical_url is not None:
        parameter["canonical_url"] = canonical_url

    if not unlock_wallet(hv):
        return
    tags = None
    if "tags" in parameter:
        tags = derive_tags(parameter["tags"])
    title = ""
    if "title" in parameter:
        title = parameter["title"]
    if "author" in parameter:
        author = parameter["author"]
    else:
        author = hv.config["default_account"]
    permlink = None
    if "permlink" in parameter:
        permlink = parameter["permlink"]
    reply_identifier = None
    if "reply_identifier" in parameter:
        reply_identifier = parameter["reply_identifier"]
    community = None
    if "community" in parameter:
        community = parameter["community"]
    if "parse_body" in parameter:
        parse_body = bool(parameter["parse_body"])
    else:
        parse_body = not no_parse_body
    max_accepted_payout = parameter.get("max_accepted_payout")
    percent_hbd = parameter.get("percent_hbd")
    comment_options = {}
    if max_accepted_payout is not None:
        if hv.backed_token_symbol not in max_accepted_payout:
            max_accepted_payout = str(
                Amount(float(max_accepted_payout), hv.backed_token_symbol, blockchain_instance=hv)
            )
        comment_options["max_accepted_payout"] = max_accepted_payout
    if percent_hbd is not None:
        comment_options["percent_hbd"] = percent_hbd
    beneficiaries = None
    if "beneficiaries" in parameter:
        beneficiaries = derive_beneficiaries(parameter["beneficiaries"])
        for b in beneficiaries:
            Account(b["account"], blockchain_instance=hv)

    if permlink is not None:
        try:
            comment = Comment(construct_authorperm(author, permlink), blockchain_instance=hv)
        except Exception:
            comment = None
    else:
        comment = None

    iu = ImageUploader(blockchain_instance=hv)
    for link in list(
        re.findall(r'!\[[^"\'@\]\(]*\]\([^"\'@\(\)]*\.(?:png|jpg|jpeg|gif|png|svg)\)', body)
    ):
        image = link.split("(")[1].split(")")[0]
        image_name = link.split("![")[1].split("]")[0]
        if image[:4] == "http":
            continue
        if hv.unsigned:
            continue
        basepath = os.path.dirname(markdown_file)
        if os.path.exists(image):
            tx = iu.upload(image, author, image_name)
            body = body.replace(image, tx["url"])
        elif os.path.exists(os.path.join(basepath, image)):
            tx = iu.upload(image, author, image_name)
            body = body.replace(image, tx["url"])

    if comment is None and permlink is None and reply_identifier is None:
        permlink = derive_permlink(title, with_suffix=False)
        try:
            comment = Comment(construct_authorperm(author, permlink), blockchain_instance=hv)
        except Exception:
            comment = None
    if comment is None:
        json_metadata = {}
    else:
        json_metadata = comment.json_metadata
    if "authored_by" in parameter:
        json_metadata["authored_by"] = parameter["authored_by"]
    if "description" in parameter:
        json_metadata["description"] = parameter["description"]
    if "canonical_url" in parameter:
        json_metadata["canonical_url"] = parameter["canonical_url"]
    else:
        json_metadata["canonical_url"] = hv.config["default_canonical_url"] or "https://hive.blog"

    if "canonical_url" in json_metadata and json_metadata["canonical_url"].find("@") < 0:
        if json_metadata["canonical_url"][-1] != "/":
            json_metadata["canonical_url"] += "/"
        if json_metadata["canonical_url"][:8] != "https://":
            json_metadata["canonical_url"] = "https://" + json_metadata["canonical_url"]
        if community is None:
            json_metadata["canonical_url"] += tags[0] + "/@" + author + "/" + permlink
        else:
            json_metadata["canonical_url"] += community + "/@" + author + "/" + permlink

    if comment is None or no_patch_on_edit:
        if reply_identifier is None and (len(tags) == 0 or tags is None):
            raise ValueError("Tags must not be empty!")
        tx = hv.post(
            title,
            body,
            author=author,
            permlink=permlink,
            reply_identifier=reply_identifier,
            community=community,
            tags=tags,
            json_metadata=json_metadata,
            comment_options=comment_options,
            beneficiaries=beneficiaries,
            parse_body=parse_body,
            app="hive-nectar/%s" % (__version__),
        )
    else:
        patch_text = make_patch(comment.body, body)
        if patch_text == "":
            print("No changes on post body detected.")
        else:
            print(patch_text)
        edit_ok = click.prompt("Should I broadcast %s [y/n]" % (str(permlink)))
        if edit_ok not in ["y", "ye", "yes"]:
            return
        tx = hv.post(
            title,
            patch_text,
            author=author,
            permlink=permlink,
            reply_identifier=reply_identifier,
            community=community,
            tags=tags,
            json_metadata=json_metadata,
            parse_body=False,
            app="hive-nectar/%s" % (__version__),
        )
    export_trx(tx, export)
    tx = json.dumps(tx, indent=4)
    print(tx)


@cli.command()
@click.argument("authorperm", nargs=1)
@click.argument("body", nargs=1)
@click.option("--account", "-a", help="Account are you posting from")
@click.option("--title", "-t", help="Title of the post")
@click.option("--export", "-e", help="When set, transaction is stored in a file")
def reply(authorperm, body, account, title, export):
    """replies to a comment"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()

    if not account:
        account = hv.config["default_account"]
    if not unlock_wallet(hv):
        return

    if title is None:
        title = ""
    tx = hv.post(
        title,
        body,
        json_metadata=None,
        author=account,
        reply_identifier=authorperm,
        app="hive-nectar/%s" % (__version__),
    )
    export_trx(tx, export)
    tx = json.dumps(tx, indent=4)
    print(tx)


@cli.command()
@click.argument("witness", nargs=1)
@click.option("--account", "-a", help="Your account")
@click.option("--export", "-e", help="When set, transaction is stored in a file")
def approvewitness(witness, account, export):
    """Approve a witnesses"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if not account:
        account = hv.config["default_account"]
    if not unlock_wallet(hv):
        return
    acc = Account(account, blockchain_instance=hv)
    tx = acc.approvewitness(witness, approve=True)
    export_trx(tx, export)
    tx = json.dumps(tx, indent=4)
    print(tx)


@cli.command()
@click.argument("witness", nargs=1)
@click.option("--account", "-a", help="Your account")
@click.option("--export", "-e", help="When set, transaction is stored in a file")
def disapprovewitness(witness, account, export):
    """Disapprove a witnesses"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if not account:
        account = hv.config["default_account"]
    if not unlock_wallet(hv):
        return
    acc = Account(account, blockchain_instance=hv)
    tx = acc.disapprovewitness(witness)
    export_trx(tx, export)
    tx = json.dumps(tx, indent=4)
    print(tx)


@cli.command()
@click.argument("proxy", nargs=1)
@click.option("--account", "-a", help="Your account")
@click.option("--export", "-e", help="When set, transaction is stored in a file")
def setproxy(proxy, account, export):
    """Set your witness/proposal system proxy"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if not account:
        account = hv.config["default_account"]
    if not unlock_wallet(hv):
        return
    acc = Account(account, blockchain_instance=hv)
    tx = acc.setproxy(proxy, account)
    export_trx(tx, export)
    tx = json.dumps(tx, indent=4)
    print(tx)


@cli.command()
@click.option("--account", "-a", help="Your account")
@click.option("--export", "-e", help="When set, transaction is stored in a file")
def delproxy(account, export):
    """Delete your witness/proposal system proxy"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if not account:
        account = hv.config["default_account"]
    if not unlock_wallet(hv):
        return
    acc = Account(account, blockchain_instance=hv)
    tx = acc.setproxy("", account)
    export_trx(tx, export)
    tx = json.dumps(tx, indent=4)
    print(tx)


@cli.command()
@click.option(
    "--file", "-i", help='Load transaction from file. If "-", read from stdin (defaults to "-")'
)
@click.option(
    "--outfile", "-o", help='Load transaction from file. If "-", read from stdin (defaults to "-")'
)
def sign(file, outfile):
    """Sign a provided transaction with available and required keys"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if not unlock_wallet(hv):
        return
    if file and file != "-":
        if not os.path.isfile(file):
            raise Exception("File %s does not exist!" % file)
        with open(file) as fp:
            tx = fp.read()
        if tx.find("\0") > 0:
            with open(file, encoding="utf-16") as fp:
                tx = fp.read()
    else:
        tx = click.get_text_stream("stdin")
    tx = ast.literal_eval(tx)
    tx = hv.sign(tx, reconstruct_tx=False)
    tx = json.dumps(tx, indent=4)
    if outfile and outfile != "-":
        with open(outfile, "w") as fp:
            fp.write(tx)
    else:
        print(tx)


@cli.command()
@click.option(
    "--file", "-f", help='Load transaction from file. If "-", read from stdin (defaults to "-")'
)
def broadcast(file):
    """broadcast a signed transaction"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if file and file != "-":
        if not os.path.isfile(file):
            raise Exception("File %s does not exist!" % file)
        with open(file) as fp:
            tx = fp.read()
        if tx.find("\0") > 0:
            with open(file, encoding="utf-16") as fp:
                tx = fp.read()
    else:
        tx = click.get_text_stream("stdin")
    tx = ast.literal_eval(tx)
    tx = hv.broadcast(tx)
    tx = json.dumps(tx, indent=4)
    print(tx)


@cli.command()
@click.option("--lines", "-n", help="Defines how many ops should be shown", default=10)
@click.option(
    "--head",
    "-h",
    help="Stream mode: When set, it is set to head (default is irreversible)",
    is_flag=True,
    default=False,
)
@click.option("--table", "-t", help="Output as table", is_flag=True, default=False)
@click.option("--follow", "-f", help="Constantly stream output", is_flag=True, default=False)
def stream(lines, head, table, follow):
    """Stream operations"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    mode = "irreversible"
    if head:
        mode = "head"
    b = Blockchain(mode=mode, blockchain_instance=hv)
    op_count = 0
    if table:
        import pprint

        t = PrettyTable(["blocknum", "trx_num", "type", "content"])
        t.align = "l"
        t._max_width = {"content": 80}
        last_block_num = 0
        for ops in b.stream(raw_ops=False):
            op_count += 1
            ops.pop("_id")
            block_num = ops.pop("block_num")
            ops_type = ops.pop("type")
            if last_block_num == 0:
                last_block_num = block_num
            trx_num = ops.pop("trx_num")
            for key in ops:
                if isinstance(ops[key], dict) and "nai" in ops[key]:
                    ops[key] = str(Amount(ops[key], blockchain_instance=hv))
                elif key == "timestamp":
                    ops[key] = formatTimeString(ops[key])
            # value = json.dumps(ops, indent=4)
            if last_block_num < block_num:
                print(t)
                t = PrettyTable(["blocknum", "trx_num", "type", "content"])
                t.align = "l"
                t._max_width = {"content": 80}
                last_block_num = block_num
            content = ops
            if ops_type == "custom_json":
                content = ops["id"]
            elif ops_type == "vote":
                content = "{:.2f}% @{}/{} - {}".format(
                    ops["weight"] / 100,
                    ops["author"],
                    ops["permlink"][:30],
                    ops["voter"],
                )
            elif ops_type == "transfer":
                content = "{}: @{} -> @{}".format(str(ops["amount"]), ops["from"], ops["to"])
            elif ops_type == "transfer_to_vesting":
                content = "{}: @{} -> @{}".format(str(ops["amount"]), ops["from"], ops["to"])
            t.add_row([str(block_num), str(trx_num), ops_type, content])
            if op_count >= lines and not follow:
                print(t)
                return
    else:
        import pprint

        for ops in b.stream(raw_ops=True):
            op_count += 1
            ops["timestamp"] = formatTimeString(ops["timestamp"])
            pprint.pprint(ops)
            if op_count >= lines and not follow:
                return


@cli.command()
def ticker():
    """Show ticker"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    t = PrettyTable(["Key", "Value"])
    t.align = "l"
    market = Market(blockchain_instance=hv)
    ticker = market.ticker()
    for key in ticker:
        if key in "percent_change":
            t.add_row([key, "%.2f %%" % ticker[key]])
        else:
            t.add_row([key, str(ticker[key])])
    print(t)


@cli.command()
@click.option("--width", "-w", help="Plot width (default 75)", default=75)
@click.option("--height", "-h", help="Plot height (default 15)", default=15)
@click.option("--ascii", help="Use only ascii symbols", is_flag=True, default=False)
def pricehistory(width, height, ascii):
    """Show price history"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    feed_history = hv.get_feed_history()
    current_base = Amount(feed_history["current_median_history"]["base"], blockchain_instance=hv)
    current_quote = Amount(feed_history["current_median_history"]["quote"], blockchain_instance=hv)
    price_history = feed_history["price_history"]
    price = []
    for h in price_history:
        base = Amount(h["base"], blockchain_instance=hv)
        quote = Amount(h["quote"], blockchain_instance=hv)
        price.append(float(base.amount / quote.amount))
    if ascii:
        charset = "ascii"
    else:
        charset = "utf8"
    chart = AsciiChart(
        height=height, width=width, offset=4, placeholder="{:6.2f} $", charset=charset
    )
    print(
        "\n            Price history for %s (median price %4.2f $)\n"
        % (hv.token_symbol, float(current_base) / float(current_quote))
    )

    chart.adapt_on_series(price)
    chart.new_chart()
    chart.add_axis()
    if (float(current_base) / float(current_quote)) <= max(price):
        chart._draw_h_line(
            chart._map_y(float(current_base) / float(current_quote)),
            1,
            int(chart.n / chart.skip),
            line=chart.char_set["curve_hl_dot"],
        )
    chart.add_curve(price)
    print(str(chart))


@cli.command()
@click.option("--days", "-d", help="Limit the days of shown trade history (default 7)", default=7.0)
@click.option(
    "--hours", help="Limit the intervall history intervall (default 2 hours)", default=2.0
)
@click.option(
    "--limit",
    "-l",
    help="Limit number of trades which is fetched at each intervall point (default 100)",
    default=100,
)
@click.option("--width", "-w", help="Plot width (default 75)", default=75)
@click.option("--height", "-h", help="Plot height (default 15)", default=15)
@click.option("--ascii", help="Use only ascii symbols", is_flag=True, default=False)
def tradehistory(days, hours, limit, width, height, ascii):
    """Show price history"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    m = Market(blockchain_instance=hv)
    # Using built-in timezone support
    stop = datetime.now(timezone.utc)
    start = stop - timedelta(days=days)
    trades = m.trade_history(start=start, stop=stop, limit=limit)
    price = []
    # Hive-only: compute price as TOKEN/BACKED (e.g., HIVE/HBD)
    for trade in trades:
        # Each trade is a FilledOrder object with base, quote, price, date
        base = float(trade["base"])
        quote = float(trade["quote"])
        price.append(base / quote)
    if ascii:
        charset = "ascii"
    else:
        charset = "utf8"
    chart = AsciiChart(
        height=height, width=width, offset=3, placeholder="{:6.2f} ", charset=charset
    )
    print(
        "\n     Trade history %s - %s \n\n%s/%s"
        % (
            formatTimeString(start),
            formatTimeString(stop),
            hv.token_symbol,
            hv.backed_token_symbol,
        )
    )
    chart.adapt_on_series(price)
    chart.new_chart()
    chart.add_axis()
    chart.add_curve(price)
    print(str(chart))


@cli.command()
@click.option("--chart", help="Enable charting", is_flag=True)
@click.option("--limit", "-l", help="Limit number of returned open orders (default 25)", default=25)
@click.option("--show-date", help="Show dates", is_flag=True, default=False)
@click.option("--width", "-w", help="Plot width (default 75)", default=75)
@click.option("--height", "-h", help="Plot height (default 15)", default=15)
@click.option("--ascii", help="Use only ascii symbols", is_flag=True, default=False)
def orderbook(chart, limit, show_date, width, height, ascii):
    """Obtain orderbook of the internal market"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    market = Market(blockchain_instance=hv)
    orderbook = market.orderbook(limit=limit, raw_data=False)
    if not show_date:
        header = [
            "Asks Sum " + hv.backed_token_symbol,
            "Sell Orders",
            "Bids Sum " + hv.backed_token_symbol,
            "Buy Orders",
        ]
    else:
        header = ["Asks date", "Sell Orders", "Bids date", "Buy Orders"]
    t = PrettyTable(header, hrules=0)
    t.align = "r"
    asks = []
    bids = []
    asks_date = []
    bids_date = []
    sumsum_asks = []
    sum_asks = 0
    sumsum_bids = []
    sum_bids = 0
    n = 0
    for order in orderbook["asks"]:
        asks.append(order)
        sum_asks += float(order.as_base(hv.backed_token_symbol)["base"])
        sumsum_asks.append(sum_asks)
    if n < len(asks):
        n = len(asks)
    for order in orderbook["bids"]:
        bids.append(order)
        sum_bids += float(order.as_base(hv.backed_token_symbol)["base"])
        sumsum_bids.append(sum_bids)
    if n < len(bids):
        n = len(bids)
    if show_date:
        for order in orderbook["asks_date"]:
            asks_date.append(order)
        if n < len(asks_date):
            n = len(asks_date)
        for order in orderbook["bids_date"]:
            bids_date.append(order)
        if n < len(bids_date):
            n = len(bids_date)
    if chart:
        if ascii:
            charset = "ascii"
        else:
            charset = "utf8"
        chart = AsciiChart(
            height=height, width=width, offset=4, placeholder=" {:10.2f} $", charset=charset
        )
        print("\n            Orderbook \n")
        chart.adapt_on_series(sumsum_asks[::-1] + sumsum_bids)
        chart.new_chart()
        chart.add_axis()
        y0 = chart._map_y(chart.minimum)
        y1 = chart._map_y(chart.maximum)
        chart._draw_v_line(
            y0 + 1, y1, int(chart.n / chart.skip / 2), line=chart.char_set["curve_vl_dot"]
        )
        chart.add_curve(sumsum_asks[::-1] + sumsum_bids)
        print(str(chart))
        return
    for i in range(n):
        row = []
        if len(asks_date) > i:
            row.append(formatTimeString(asks_date[i]))
        elif show_date:
            row.append([""])
        if len(sumsum_asks) > i and not show_date:
            row.append("%.2f" % sumsum_asks[i])
        elif not show_date:
            row.append([""])
        if len(asks) > i:
            row.append(str(asks[i]))
        else:
            row.append([""])
        if len(bids_date) > i:
            row.append(formatTimeString(bids_date[i]))
        elif show_date:
            row.append([""])
        if len(sumsum_bids) > i and not show_date:
            row.append("%.2f" % sumsum_bids[i])
        elif not show_date:
            row.append([""])
        if len(bids) > i:
            row.append(str(bids[i]))
        else:
            row.append([""])
        t.add_row(row)
    print(t)


@cli.command()
@click.argument("amount", nargs=1)
@click.argument("asset", nargs=1)
@click.argument("price", nargs=1, required=False)
@click.option("--account", "-a", help='Buy with this account (defaults to "default_account")')
@click.option("--orderid", help="Set an orderid")
@click.option("--export", "-e", help="When set, transaction is stored in a file")
def buy(amount, asset, price, account, orderid, export):
    """Buy HIVE or HBD from the internal market

    Limit buy price denoted in (HBD per HIVE)
    """
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if account is None:
        account = hv.config["default_account"]
    if asset == hv.backed_token_symbol:
        market = Market(
            base=Asset(hv.token_symbol),
            quote=Asset(hv.backed_token_symbol),
            blockchain_instance=hv,
        )
    else:
        market = Market(
            base=Asset(hv.backed_token_symbol),
            quote=Asset(hv.token_symbol),
            blockchain_instance=hv,
        )
    if price is None:
        orderbook = market.orderbook(limit=1, raw_data=False)
        if asset == hv.token_symbol and len(orderbook["bids"]) > 0:
            p = Price(
                orderbook["bids"][0]["base"], orderbook["bids"][0]["quote"], blockchain_instance=hv
            ).invert()
            p_show = p
        elif len(orderbook["asks"]) > 0:
            p = Price(
                orderbook["asks"][0]["base"], orderbook["asks"][0]["quote"], blockchain_instance=hv
            ).invert()
            p_show = p
        price_ok = click.prompt("Is the following Price ok: %s [y/n]" % (str(p_show)))
        if price_ok not in ["y", "ye", "yes"]:
            return
    else:
        p = Price(
            float(price),
            "{}:{}".format(hv.backed_token_symbol, hv.token_symbol),
            blockchain_instance=hv,
        )
    if not unlock_wallet(hv):
        return

    a = Amount(float(amount), asset, blockchain_instance=hv)
    acc = Account(account, blockchain_instance=hv)
    tx = market.buy(p, a, account=acc, orderid=orderid)
    export_trx(tx, export)
    tx = json.dumps(tx, indent=4)
    print(tx)


@cli.command()
@click.argument("amount", nargs=1)
@click.argument("asset", nargs=1)
@click.argument("price", nargs=1, required=False)
@click.option("--account", "-a", help='Sell with this account (defaults to "default_account")')
@click.option("--orderid", help="Set an orderid")
@click.option("--export", "-e", help="When set, transaction is stored in a file")
def sell(amount, asset, price, account, orderid, export):
    """Sell HIVE or HBD from the internal market

    Limit sell price denoted in (HBD per HIVE)
    """
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if asset == hv.backed_token_symbol:
        market = Market(
            base=Asset(hv.token_symbol),
            quote=Asset(hv.backed_token_symbol),
            blockchain_instance=hv,
        )
    else:
        market = Market(
            base=Asset(hv.backed_token_symbol),
            quote=Asset(hv.token_symbol),
            blockchain_instance=hv,
        )
    if not account:
        account = hv.config["default_account"]
    if not price:
        orderbook = market.orderbook(limit=1, raw_data=False)
        if asset == hv.backed_token_symbol and len(orderbook["bids"]) > 0:
            p = Price(
                orderbook["bids"][0]["base"], orderbook["bids"][0]["quote"], blockchain_instance=hv
            ).invert()
            p_show = p
        else:
            p = Price(
                orderbook["asks"][0]["base"], orderbook["asks"][0]["quote"], blockchain_instance=hv
            ).invert()
            p_show = p
        price_ok = click.prompt("Is the following Price ok: %s [y/n]" % (str(p_show)))
        if price_ok not in ["y", "ye", "yes"]:
            return
    else:
        p = Price(
            float(price),
            "{}:{}".format(hv.backed_token_symbol, hv.token_symbol),
            blockchain_instance=hv,
        )
    if not unlock_wallet(hv):
        return
    a = Amount(float(amount), asset, blockchain_instance=hv)
    acc = Account(account, blockchain_instance=hv)
    tx = market.sell(p, a, account=acc, orderid=orderid)
    export_trx(tx, export)
    tx = json.dumps(tx, indent=4)
    print(tx)


@cli.command()
@click.argument("orderid", nargs=1)
@click.option("--account", "-a", help='Sell with this account (defaults to "default_account")')
@click.option("--export", "-e", help="When set, transaction is stored in a file")
def cancel(orderid, account, export):
    """Cancel order in the internal market"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    market = Market(blockchain_instance=hv)
    if not account:
        account = hv.config["default_account"]
    if not unlock_wallet(hv):
        return
    acc = Account(account, blockchain_instance=hv)
    tx = market.cancel(orderid, account=acc)
    export_trx(tx, export)
    tx = json.dumps(tx, indent=4)
    print(tx)


@cli.command()
@click.argument("account", nargs=1, required=False)
def openorders(account):
    """Show open orders"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    market = Market(blockchain_instance=hv)
    if not account:
        account = hv.config["default_account"]
    acc = Account(account, blockchain_instance=hv)
    openorders = market.accountopenorders(account=acc)
    t = PrettyTable(["Orderid", "Created", "Order", "Account"], hrules=0)
    t.align = "r"
    for order in openorders:
        t.add_row(
            [order["orderid"], formatTimeString(order["created"]), str(order["order"]), account]
        )
    print(t)


@cli.command()
@click.argument("identifier", nargs=1)
@click.option("--account", "-a", help="Reblog as this user")
def reblog(identifier, account):
    """Reblog an existing post"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if not account:
        account = hv.config["default_account"]
    if not unlock_wallet(hv):
        return
    acc = Account(account, blockchain_instance=hv)
    post = Comment(identifier, blockchain_instance=hv)
    tx = post.reblog(account=acc)
    tx = json.dumps(tx, indent=4)
    print(tx)


@cli.command()
@click.argument("follow", nargs=-1)
@click.option("--account", "-a", help="Follow from this account")
@click.option("--what", help='Follow these objects (defaults to ["blog"])', default=["blog"])
@click.option("--export", "-e", help="When set, transaction is stored in a file")
def follow(follow, account, what, export):
    """Follow another account

    Can be blog ignore blacklist unblacklist follow_blacklist unfollow_blacklist follow_muted unfollow_muted on HIVE
    """
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if not account:
        account = hv.config["default_account"]
    if isinstance(what, str):
        what = [what]
    if not unlock_wallet(hv):
        return
    acc = Account(account, blockchain_instance=hv)
    tx = acc.follow(follow, what=what)
    export_trx(tx, export)
    tx = json.dumps(tx, indent=4)
    print(tx)


@cli.command()
@click.argument("mute", nargs=1)
@click.option("--account", "-a", help="Mute from this account")
@click.option("--what", help='Mute these objects (defaults to ["ignore"])', default=["ignore"])
@click.option("--export", "-e", help="When set, transaction is stored in a file")
def mute(mute, account, what, export):
    """Mute another account"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if not account:
        account = hv.config["default_account"]
    if isinstance(what, str):
        what = [what]
    if not unlock_wallet(hv):
        return
    acc = Account(account, blockchain_instance=hv)
    tx = acc.follow(mute, what=what)
    export_trx(tx, export)
    tx = json.dumps(tx, indent=4)
    print(tx)


@cli.command()
@click.argument("unfollow", nargs=1)
@click.option("--account", "-a", help="UnFollow/UnMute from this account")
@click.option("--export", "-e", help="When set, transaction is stored in a file")
def unfollow(unfollow, account, export):
    """Unfollow/Unmute another account"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if not account:
        account = hv.config["default_account"]
    if not unlock_wallet(hv):
        return
    acc = Account(account, blockchain_instance=hv)
    tx = acc.unfollow(unfollow)
    export_trx(tx, export)
    tx = json.dumps(tx, indent=4)
    print(tx)


@cli.command()
@click.option("--witness", help="Witness name")
@click.option("--maximum_block_size", help="Max block size")
@click.option("--account_creation_fee", help="Account creation fee")
@click.option("--hbd_interest_rate", help="HBD interest rate in percent")
@click.option("--url", help="Witness URL")
@click.option("--signing_key", help="Signing Key")
@click.option("--export", "-e", help="When set, transaction is stored in a file")
def witnessupdate(
    witness,
    maximum_block_size,
    account_creation_fee,
    hbd_interest_rate,
    url,
    signing_key,
    export,
):
    """Change witness properties"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if not witness:
        witness = hv.config["default_account"]
    if not unlock_wallet(hv):
        return
    witness = Witness(witness, blockchain_instance=hv)
    props = witness["props"]
    if account_creation_fee is not None:
        props["account_creation_fee"] = str(
            Amount(
                "{:.3f} {}".format(float(account_creation_fee), hv.token_symbol),
                blockchain_instance=hv,
            )
        )
    if maximum_block_size is not None:
        props["maximum_block_size"] = int(maximum_block_size)
    if hbd_interest_rate is not None:
        props["hbd_interest_rate"] = int(float(hbd_interest_rate) * 100)
    tx = witness.update(signing_key or witness["signing_key"], url or witness["url"], props)
    export_trx(tx, export)
    tx = json.dumps(tx, indent=4)
    print(tx)


@cli.command()
@click.argument("witness", nargs=1)
@click.option("--export", "-e", help="When set, transaction is stored in a file")
def witnessdisable(witness, export):
    """Disable a witness"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if not witness:
        witness = hv.config["default_account"]
    if not unlock_wallet(hv):
        return
    witness = Witness(witness, blockchain_instance=hv)
    if not witness.is_active:
        print("Cannot disable a disabled witness!")
        return
    props = witness["props"]
    null_key = ("%s" + "1111111111111111111111111111111114T1Anm") % hv.prefix
    tx = witness.update(null_key, witness["url"], props)
    export_trx(tx, export)
    tx = json.dumps(tx, indent=4)
    print(tx)


@cli.command()
@click.argument("witness", nargs=1)
@click.argument("signing_key", nargs=1)
@click.option("--export", "-e", help="When set, transaction is stored in a file")
def witnessenable(witness, signing_key, export):
    """Enable a witness"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if not witness:
        witness = hv.config["default_account"]
    if not unlock_wallet(hv):
        return
    witness = Witness(witness, blockchain_instance=hv)
    props = witness["props"]
    tx = witness.update(signing_key, witness["url"], props)
    export_trx(tx, export)
    tx = json.dumps(tx, indent=4)
    print(tx)


@cli.command()
@click.argument("witness", nargs=1)
@click.argument("pub_signing_key", nargs=1)
@click.option("--maximum_block_size", help="Max block size", default=65536)
@click.option("--account_creation_fee", help="Account creation fee", default=0.1)
@click.option("--hbd_interest_rate", help="HBD interest rate in percent", default=0.0)
@click.option("--url", help="Witness URL", default="")
@click.option("--export", "-e", help="When set, transaction is stored in a file")
def witnesscreate(
    witness,
    pub_signing_key,
    maximum_block_size,
    account_creation_fee,
    hbd_interest_rate,
    url,
    export,
):
    """Create a witness"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if not unlock_wallet(hv):
        return
    props = {
        "account_creation_fee": Amount(
            "{:.3f} {}".format(float(account_creation_fee), hv.token_symbol), blockchain_instance=hv
        ),
        "maximum_block_size": int(maximum_block_size),
        "hbd_interest_rate": int(hbd_interest_rate * 100),
    }

    tx = hv.witness_update(pub_signing_key, url, props, account=witness)
    export_trx(tx, export)
    tx = json.dumps(tx, indent=4)
    print(tx)


@cli.command()
@click.argument("witness", nargs=1)
@click.argument("wif", nargs=1)
@click.option("--account_creation_fee", help="Account creation fee (float)")
@click.option("--account_subsidy_budget", help="Account subisidy per block")
@click.option("--account_subsidy_decay", help="Per block decay of the account subsidy pool")
@click.option("--maximum_block_size", help="Max block size")
@click.option("--hbd_interest_rate", help="HBD interest rate in percent")
@click.option("--new_signing_key", help="Set new signing key (pubkey)")
@click.option("--url", help="Witness URL")
def witnessproperties(
    witness,
    wif,
    account_creation_fee,
    account_subsidy_budget,
    account_subsidy_decay,
    maximum_block_size,
    hbd_interest_rate,
    new_signing_key,
    url,
):
    """Update witness properties of witness WITNESS with the witness signing key WIF"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    # if not unlock_wallet(hv):
    #    return
    props = {}
    if account_creation_fee is not None:
        props["account_creation_fee"] = Amount(
            "{:.3f} {}".format(float(account_creation_fee), hv.token_symbol), blockchain_instance=hv
        )
    if account_subsidy_budget is not None:
        props["account_subsidy_budget"] = int(account_subsidy_budget)
    if account_subsidy_decay is not None:
        props["account_subsidy_decay"] = int(account_subsidy_decay)
    if maximum_block_size is not None:
        props["maximum_block_size"] = int(maximum_block_size)
    if hbd_interest_rate is not None:
        props["hbd_interest_rate"] = int(float(hbd_interest_rate) * 100)
    if new_signing_key is not None:
        props["new_signing_key"] = new_signing_key
    if url is not None:
        props["url"] = url

    tx = hv.witness_set_properties(wif, witness, props)
    tx = json.dumps(tx, indent=4)
    print(tx)


@cli.command()
@click.argument("witness", nargs=1)
@click.argument("wif", nargs=1, required=False)
@click.option(
    "--base", "-b", help="Set base manually, when not set the base is automatically calculated."
)
@click.option(
    "--quote",
    "-q",
    help="HBD/HIVE quote manually; when not set, the base is automatically calculated.",
)
@click.option(
    "--support-peg",
    help="Supports peg adjusting the quote, is overwritten by --set-quote!",
    is_flag=True,
    default=False,
)
def witnessfeed(witness, wif, base, quote, support_peg):
    """Publish price feed for a witness"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if wif is None:
        if not unlock_wallet(hv):
            return
    witness = Witness(witness, blockchain_instance=hv)
    market = Market(blockchain_instance=hv)
    # Prefer HBD exchange rate (Hive-only)
    if "hbd_exchange_rate" in witness:
        old_base = witness["hbd_exchange_rate"]["base"]
        old_quote = witness["hbd_exchange_rate"]["quote"]
        last_published_price = Price(witness["hbd_exchange_rate"], blockchain_instance=hv)
    else:
        # Fallback to current median price if no prior feed
        median = hv.get_current_median_history()
        old_base = median["base"]
        old_quote = median["quote"]
        last_published_price = Price(median, blockchain_instance=hv)

    hive_usd = None
    print(
        "Old price {:.3f} (base: {}, quote {})".format(
            float(last_published_price), old_base, old_quote
        )
    )
    if quote is None and not support_peg:
        quote = Amount("1.000 %s" % hv.token_symbol, blockchain_instance=hv)
    elif quote is None:
        latest_price = market.ticker()["latest"]
        if hive_usd is None:
            hive_usd = market.hive_usd_implied()
        hbd_usd = float(latest_price.as_base(hv.backed_token_symbol)) * hive_usd
        quote = Amount(1.0 / hbd_usd, hv.token_symbol, blockchain_instance=hv)
    else:
        if str(quote[-5:]).upper() == hv.token_symbol:
            quote = Amount(quote, blockchain_instance=hv)
        else:
            quote = Amount(quote, hv.token_symbol, blockchain_instance=hv)
    if base is None:
        if hive_usd is None:
            hive_usd = market.hive_usd_implied()
        base = Amount(hive_usd, hv.backed_token_symbol, blockchain_instance=hv)
    else:
        if str(quote[-3:]).upper() == hv.backed_token_symbol:
            base = Amount(base, blockchain_instance=hv)
        else:
            base = Amount(base, hv.backed_token_symbol, blockchain_instance=hv)
    new_price = Price(base=base, quote=quote, blockchain_instance=hv)
    print("New price {:.3f} (base: {}, quote {})".format(float(new_price), base, quote))
    if wif is not None:
        props = {"hbd_exchange_rate": new_price}
        tx = hv.witness_set_properties(wif, witness["owner"], props)
    else:
        tx = witness.feed_publish(base, quote=quote)
    tx = json.dumps(tx, indent=4)
    print(tx)


@cli.command()
@click.argument("witness", nargs=1)
def witness(witness):
    """List witness information"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    witness = Witness(witness, blockchain_instance=hv)
    witness_json = witness.json()
    witness_schedule = hv.get_witness_schedule()
    config = hv.get_config()
    if "VIRTUAL_SCHEDULE_LAP_LENGTH2" in config:
        lap_length = int(config["VIRTUAL_SCHEDULE_LAP_LENGTH2"])
    elif "HIVE_VIRTUAL_SCHEDULE_LAP_LENGTH2" in config:
        lap_length = int(config["HIVE_VIRTUAL_SCHEDULE_LAP_LENGTH2"])
    else:
        # Hive-only default
        lap_length = int(
            config.get(
                "HIVE_VIRTUAL_SCHEDULE_LAP_LENGTH2", config.get("VIRTUAL_SCHEDULE_LAP_LENGTH2")
            )
        )
    rank = 0
    active_rank = 0
    found = False
    witnesses = WitnessesRankedByVote(limit=250, blockchain_instance=hv)
    vote_sum = witnesses.get_votes_sum()
    for w in witnesses:
        rank += 1
        if w.is_active:
            active_rank += 1
        if w["owner"] == witness["owner"]:
            found = True
            break
    virtual_time_to_block_num = int(witness_schedule["num_scheduled_witnesses"]) / (
        lap_length / (vote_sum + 1)
    )
    t = PrettyTable(["Key", "Value"])
    t.align = "l"
    for key in sorted(witness_json):
        value = witness_json[key]
        if key in ["props", "hbd_exchange_rate"]:
            value = json.dumps(value, indent=4)
        t.add_row([key, value])
    if found:
        t.add_row(["rank", rank])
        t.add_row(["active_rank", active_rank])
    virtual_diff = int(witness_json["virtual_scheduled_time"]) - int(
        witness_schedule["current_virtual_time"]
    )
    block_diff_est = virtual_diff * virtual_time_to_block_num
    if active_rank > 20:
        t.add_row(["virtual_time_diff", virtual_diff])
        t.add_row(["block_diff_est", int(block_diff_est)])
        next_block_s = int(block_diff_est) * 3
        next_block_min = next_block_s / 60
        next_block_h = next_block_min / 60
        next_block_d = next_block_h / 24
        time_diff_est = ""
        if next_block_d > 1:
            time_diff_est = "%.2f days" % next_block_d
        elif next_block_h > 1:
            time_diff_est = "%.2f hours" % next_block_h
        elif next_block_min > 1:
            time_diff_est = "%.2f minutes" % next_block_min
        else:
            time_diff_est = "%.2f seconds" % next_block_s
        t.add_row(["time_diff_est", time_diff_est])
    print(t)


@cli.command()
@click.argument("account", nargs=1, required=False)
@click.option("--limit", help="How many witnesses should be shown", default=100)
def witnesses(account, limit):
    """List witnesses"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if account:
        account = Account(account, blockchain_instance=hv)
        account_name = account["name"]
        if account["proxy"] != "":
            account_name = account["proxy"]
            account_type = "Proxy"
        else:
            account_type = "Account"
        witnesses = WitnessesVotedByAccount(account_name, blockchain_instance=hv)
        print("%s: @%s (%d of 30)" % (account_type, account_name, len(witnesses)))
    else:
        witnesses = WitnessesRankedByVote(limit=limit, blockchain_instance=hv)
    witnesses.printAsTable()


@cli.command()
@click.argument("account", nargs=1, required=False)
@click.option("--direction", default=None, help="in or out")
@click.option("--outgoing", "-o", help="Show outgoing votes", is_flag=True, default=False)
@click.option("--incoming", "-i", help="Show incoming votes", is_flag=True, default=False)
@click.option(
    "--days", "-d", default=2.0, help="Limit shown vote history by this amount of days (default: 2)"
)
@click.option("--export", "-e", default=None, help="Export results to TXT-file")
def votes(account, direction, outgoing, incoming, days, export):
    """List outgoing/incoming account votes"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if not account:
        account = hv.config["default_account"]
    if direction is None and not incoming and not outgoing:
        direction = "in"
    # Using built-in timezone support
    limit_time = datetime.now(timezone.utc) - timedelta(days=days)
    out_votes_str = ""
    in_votes_str = ""
    if direction == "out" or outgoing:
        votes = AccountVotes(account, start=limit_time, blockchain_instance=hv)
        out_votes_str = votes.printAsTable(start=limit_time, return_str=True)
    if direction == "in" or incoming:
        account = Account(account, blockchain_instance=hv)
        votes_list = []
        for v in account.history(start=limit_time, only_ops=["vote"]):
            vote = Vote(v, blockchain_instance=hv)
            vote.refresh()
            votes_list.append(vote)
        votes = ActiveVotes(votes_list, blockchain_instance=hv)
        in_votes_str = votes.printAsTable(votee=account["name"], return_str=True)
    if export:
        with open(export, "w") as w:
            w.write(out_votes_str)
            w.write("\n")
            w.write(in_votes_str)
    else:
        print(out_votes_str)
        print(in_votes_str)


@cli.command()
@click.argument("authorperm", nargs=1, required=False)
@click.option("--account", "-a", help="Show only curation for this account")
@click.option("--limit", "-m", help="Show only the first minutes")
@click.option("--min-vote", "-v", help="Show only votes higher than the given value")
@click.option("--max-vote", "-w", help="Show only votes lower than the given value")
@click.option(
    "--min-performance",
    "-x",
    help="Show only votes with performance higher than the given value in HBD",
)
@click.option(
    "--max-performance",
    "-y",
    help="Show only votes with performance lower than the given value in HBD",
)
@click.option(
    "--payout", default=None, help="Show the curation for a potential payout in HBD as float"
)
@click.option("--export", "-e", default=None, help="Export results to HTML-file")
@click.option("--short", "-s", is_flag=True, default=False, help="Show only Curation without sum")
@click.option("--length", "-l", help="Limits the permlink character length", default=None)
@click.option(
    "--permlink", "-p", help="Show the permlink for each entry", is_flag=True, default=False
)
@click.option("--title", "-t", help="Show the title for each entry", is_flag=True, default=False)
@click.option(
    "--days",
    "-d",
    default=7.0,
    help="Limit shown rewards by this amount of days (default: 7), max is 7 days.",
)
def curation(
    authorperm,
    account,
    limit,
    min_vote,
    max_vote,
    min_performance,
    max_performance,
    payout,
    export,
    short,
    length,
    permlink,
    title,
    days,
):
    """Lists curation rewards of all votes for authorperm

    When authorperm is empty or "all", the curation rewards
    for all account votes are shown.

    authorperm can also be a number. e.g. 5 is equivalent to
    the fifth account vote in the given time duration (default is 7 days)

    """
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    try:
        HP_symbol = "HP"
        if authorperm is None:
            authorperm = "all"
        if account is None and authorperm != "all":
            show_all_voter = True
        else:
            show_all_voter = False
        if authorperm == "all" or authorperm.isdigit():
            if not account:
                account = hv.config["default_account"]
            # Using built-in timezone support
            # Respect CLI --days (help already documents max=7)
            _days = min(float(days), 7.0)
            limit_time = datetime.now(timezone.utc) - timedelta(days=_days)
            votes = AccountVotes(account, start=limit_time, blockchain_instance=hv)
            authorperm_list = [vote.authorperm for vote in votes]
            if authorperm.isdigit():
                if len(authorperm_list) < int(authorperm):
                    raise ValueError(
                        "Authorperm id must be lower than %d" % (len(authorperm_list) + 1)
                    )
                authorperm_list = [authorperm_list[int(authorperm) - 1]]
                all_posts = False
            else:
                all_posts = True
        else:
            authorperm_list = [authorperm]
            all_posts = False
        if (all_posts) and permlink:
            t = PrettyTable(
                [
                    "Author",
                    "permlink",
                    "Voting time",
                    "Vote",
                    "Early vote loss",
                    "Curation",
                    "Performance",
                ]
            )
            t.align = "l"
        elif (all_posts) and title:
            t = PrettyTable(
                [
                    "Author",
                    "permlink",
                    "Voting time",
                    "Vote",
                    "Early vote loss",
                    "Curation",
                    "Performance",
                ]
            )
            t.align = "l"
        elif all_posts:
            t = PrettyTable(
                ["Author", "Voting time", "Vote", "Early vote loss", "Curation", "Performance"]
            )
            t.align = "l"
        elif (export) and permlink:
            t = PrettyTable(
                [
                    "Author",
                    "permlink",
                    "Voter",
                    "Voting time",
                    "Vote",
                    "Early vote loss",
                    "Curation",
                    "Performance",
                ]
            )
            t.align = "l"
        elif (export) and title:
            t = PrettyTable(
                [
                    "Author",
                    "permlink",
                    "Voter",
                    "Voting time",
                    "Vote",
                    "Early vote loss",
                    "Curation",
                    "Performance",
                ]
            )
            t.align = "l"
        elif export:
            t = PrettyTable(
                [
                    "Author",
                    "Voter",
                    "Voting time",
                    "Vote",
                    "Early vote loss",
                    "Curation",
                    "Performance",
                ]
            )
            t.align = "l"
        else:
            t = PrettyTable(
                ["Voter", "Voting time", "Vote", "Early vote loss", "Curation", "Performance"]
            )
            t.align = "l"
        index = 0
        for authorperm in authorperm_list:
            index += 1
            comment = Comment(authorperm, blockchain_instance=hv)
            if payout is not None and comment.is_pending():
                payout = float(payout)
            elif payout is not None:
                payout = None
            curation_rewards_HBD = comment.get_curation_rewards(
                pending_payout_hbd=True, pending_payout_value=payout
            )
            curation_rewards_HP = comment.get_curation_rewards(
                pending_payout_hbd=False, pending_payout_value=payout
            )
            rows = []
            sum_curation = [0, 0, 0, 0]
            max_curation = [0, 0, 0, 0, 0, 0]
            highest_vote = [0, 0, 0, 0, 0, 0]
            for vote in comment.get_votes():
                vote_time = vote["time"]

                vote_HBD = hv.rshares_to_token_backed_dollar(int(vote["rshares"]))
                curation_HBD = curation_rewards_HBD["active_votes"][vote["voter"]]
                curation_HP = curation_rewards_HP["active_votes"][vote["voter"]]
                if vote_HBD > 0:
                    penalty = (comment.get_curation_penalty(vote_time=vote_time)) * vote_HBD
                    performance = float(curation_HBD) / vote_HBD * 100
                else:
                    performance = 0
                    penalty = 0
                vote_befor_min = ((vote_time) - comment["created"]).total_seconds() / 60
                sum_curation[0] += vote_HBD
                sum_curation[1] += penalty
                sum_curation[2] += float(curation_HP)
                sum_curation[3] += float(curation_HBD)
                row = [
                    vote["voter"],
                    vote_befor_min,
                    vote_HBD,
                    penalty,
                    float(curation_HP),
                    performance,
                ]

                rows.append(row)
            sortedList = sorted(rows, key=lambda row: (row[1]), reverse=False)
            new_row = []
            new_row2 = []
            voter = []
            voter2 = []
            if (all_posts or export) and permlink:
                if length:
                    new_row = [comment.author, comment.permlink[: int(length)]]
                else:
                    new_row = [comment.author, comment.permlink]
                new_row2 = ["", ""]
            elif (all_posts or export) and title:
                if length:
                    new_row = [comment.author, comment.title[: int(length)]]
                else:
                    new_row = [comment.author, comment.title]
                new_row2 = ["", ""]
            elif all_posts or export:
                new_row = [comment.author]
                new_row2 = [""]
            if not all_posts:
                voter = [""]
                voter2 = [""]
            found_voter = False
            for row in sortedList:
                if limit is not None and row[1] > float(limit):
                    continue
                if min_vote is not None and float(row[2]) < float(min_vote):
                    continue
                if max_vote is not None and float(row[2]) > float(max_vote):
                    continue
                if min_performance is not None and float(row[5]) < float(min_performance):
                    continue
                if max_performance is not None and float(row[5]) > float(max_performance):
                    continue
                if row[-1] > max_curation[-1]:
                    max_curation = row
                if row[2] > highest_vote[2]:
                    highest_vote = row
                if show_all_voter or account == row[0]:
                    if not all_posts:
                        voter = [row[0]]
                    if all_posts:
                        new_row[0] = "%d. %s" % (index, comment.author)
                    if not found_voter:
                        found_voter = True
                    t.add_row(
                        new_row
                        + voter
                        + [
                            "%.1f min" % row[1],
                            "{:.3f} {}".format(float(row[2]), hv.backed_token_symbol),
                            "{:.3f} {}".format(float(row[3]), hv.backed_token_symbol),
                            "{:.3f} {}".format(row[4], HP_symbol),
                            "%.1f %%" % (row[5]),
                        ]
                    )
                    if len(authorperm_list) == 1:
                        new_row = new_row2
            if not short and found_voter:
                t.add_row(new_row2 + voter2 + ["", "", "", "", ""])
                if sum_curation[0] > 0:
                    curation_sum_percentage = sum_curation[3] / sum_curation[0] * 100
                else:
                    curation_sum_percentage = 0
                sum_line = new_row2 + voter2
                sum_line[-1] = "High. vote"

                t.add_row(
                    sum_line
                    + [
                        "%.1f min" % highest_vote[1],
                        "{:.3f} {}".format(float(highest_vote[2]), hv.backed_token_symbol),
                        "{:.3f} {}".format(float(highest_vote[3]), hv.backed_token_symbol),
                        "{:.3f} {}".format(highest_vote[4], HP_symbol),
                        "%.1f %%" % (highest_vote[5]),
                    ]
                )
                sum_line[-1] = "High. Cur."
                t.add_row(
                    sum_line
                    + [
                        "%.1f min" % max_curation[1],
                        "{:.3f} {}".format(float(max_curation[2]), hv.backed_token_symbol),
                        "{:.3f} {}".format(float(max_curation[3]), hv.backed_token_symbol),
                        "{:.3f} {}".format(max_curation[4], HP_symbol),
                        "%.1f %%" % (max_curation[5]),
                    ]
                )
                sum_line[-1] = "Sum"
                t.add_row(
                    sum_line
                    + [
                        "-",
                        "{:.3f} {}".format(sum_curation[0], hv.backed_token_symbol),
                        "{:.3f} {}".format(sum_curation[1], hv.backed_token_symbol),
                        "{:.3f} {}".format(sum_curation[2], HP_symbol),
                        "%.2f %%" % curation_sum_percentage,
                    ]
                )
                if all_posts or export:
                    t.add_row(new_row2 + voter2 + ["-", "-", "-", "-", "-"])
            if not (all_posts or export):
                print("curation for %s" % (authorperm))
                print(t)
        if export:
            with open(export, "w") as w:
                w.write(str(t.get_html_string()))
        elif all_posts:
            print("curation for @%s" % account)
            print(t)
    except Exception as e:
        print(str(e))
        raise e


@cli.command()
@click.argument("accounts", nargs=-1, required=False)
@click.option("--only-sum", "-s", help="Show only the sum", is_flag=True, default=False)
@click.option("--post", "-p", help="Show post payout", is_flag=True, default=False)
@click.option("--comment", "-c", help="Show comments payout", is_flag=True, default=False)
@click.option("--curation", "-v", help="Shows  curation", is_flag=True, default=False)
@click.option("--length", "-l", help="Limits the permlink character length", default=None)
@click.option("--author", "-a", help="Show the author for each entry", is_flag=True, default=False)
@click.option(
    "--permlink", "-e", help="Show the permlink for each entry", is_flag=True, default=False
)
@click.option("--title", "-t", help="Show the title for each entry", is_flag=True, default=False)
@click.option(
    "--days", "-d", default=7.0, help="Limit shown rewards by this amount of days (default: 7)"
)
@click.option(
    "--witness",
    "-w",
    help="Show witness (producer) rewards",
    is_flag=True,
    default=False,
)
def rewards(
    accounts, only_sum, post, comment, curation, length, author, permlink, title, days, witness
):
    """Lists received rewards"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if not accounts:
        accounts = [hv.config["default_account"]]
    if not witness and not comment and not curation and not post:
        post = True
        permlink = True
    if days < 0:
        days = 1

    # Using built-in timezone support
    now = datetime.now(timezone.utc)
    limit_time = now - timedelta(days=days)
    hp_symbol = f"{hv.token_symbol[0]}P"
    current_median_history = hv.get_current_median_history()
    median_price_fallback = (
        float(Price(current_median_history, blockchain_instance=hv))
        if current_median_history
        else 0.0
    )

    def _render_witness_rewards(account_obj, median_price_obj):
        t = PrettyTable(["Received", hp_symbol, "Invested USD"])
        t.align = "l"
        rows = []
        sum_hp = 0.0
        sum_invested = 0.0
        start_op = account_obj.estimate_virtual_op_num(limit_time)
        if start_op > 0:
            start_op -= 1
        progress_length = (account_obj.virtual_op_count() - start_op) / 1000
        try:
            median_price_value = float(median_price_obj)
        except Exception:
            median_price_value = median_price_fallback
        with click.progressbar(
            account_obj.history(start=start_op, use_block_num=False),
            length=progress_length,
        ) as witness_hist:
            for v in witness_hist:
                timestamp = v.get("timestamp")
                if isinstance(timestamp, (int, float)):
                    received = datetime.fromtimestamp(timestamp, tz=timezone.utc)
                else:
                    # Try to parse the timestamp string
                    try:
                        received = formatTimeString(timestamp)
                        # If it's still a string, try parsing as ISO format
                        if isinstance(received, str):
                            received = datetime.fromisoformat(received.replace("Z", "+00:00"))
                        # Ensure timezone awareness
                        if received.tzinfo is None:
                            received = received.replace(tzinfo=timezone.utc)
                    except Exception as e:
                        # If all else fails, skip this entry
                        if hv.debug:
                            print(f"Skipping entry due to timestamp parsing error: {e}")
                        continue
                if received < limit_time:
                    continue
                if v["type"] != "producer_reward":
                    continue
                reward_vests = hv.vests_to_token_power(
                    Amount(v["vesting_shares"], blockchain_instance=hv)
                )
                reward_hp = float(reward_vests)
                invested_usd = reward_hp * median_price_value
                sum_hp += reward_hp
                sum_invested += invested_usd
                if only_sum:
                    continue
                received_days = (now - received).total_seconds() / 86400.0
                rows.append((received_days, reward_hp, invested_usd))
        if not only_sum:
            sorted_rows = sorted(rows, key=lambda row: row[0])
            for days_ago, reward_hp, invested_usd in sorted_rows:
                t.add_row(
                    [
                        f"{days_ago:.1f} days",
                        f"{reward_hp:.3f} {hp_symbol}",
                        f"{invested_usd:.2f} $",
                    ]
                )
            if sorted_rows:
                t.add_row(["", "", ""])
        t.add_row(
            [
                "Sum",
                f"{sum_hp:.3f} {hp_symbol}",
                f"{sum_invested:.2f} $",
            ]
        )
        message = "\nShowing witness rewards for @%s" % account_obj.name
        print(message)
        print(t)

    for account in accounts:
        sum_reward = [0, 0, 0, 0, 0]
        account = Account(account, blockchain_instance=hv)
        median_price = Price(hv.get_current_median_history(), blockchain_instance=hv)
        if witness:
            _render_witness_rewards(account, median_price)
            continue
        m = Market(blockchain_instance=hv)
        latest = m.ticker()["latest"]
        if author and permlink:
            t = PrettyTable(
                [
                    "Author",
                    "Permlink",
                    "Payout",
                    hv.backed_token_symbol,
                    "{}P + {}".format(hv.token_symbol[0], hv.token_symbol),
                    "Liquid USD",
                    "Invested USD",
                ]
            )
        elif author and title:
            t = PrettyTable(
                [
                    "Author",
                    "Title",
                    "Payout",
                    hv.backed_token_symbol,
                    "{}P + {}".format(hv.token_symbol[0], hv.token_symbol),
                    "Liquid USD",
                    "Invested USD",
                ]
            )
        elif author:
            t = PrettyTable(
                [
                    "Author",
                    "Payout",
                    hv.backed_token_symbol,
                    "{}P + {}".format(hv.token_symbol[0], hv.token_symbol),
                    "Liquid USD",
                    "Invested USD",
                ]
            )
        elif not author and permlink:
            t = PrettyTable(
                [
                    "Permlink",
                    "Payout",
                    hv.backed_token_symbol,
                    "{}P + {}".format(hv.token_symbol[0], hv.token_symbol),
                    "Liquid USD",
                    "Invested USD",
                ]
            )
        elif not author and title:
            t = PrettyTable(
                [
                    "Title",
                    "Payout",
                    hv.backed_token_symbol,
                    "{}P + {}".format(hv.token_symbol[0], hv.token_symbol),
                    "Liquid USD",
                    "Invested USD",
                ]
            )
        else:
            t = PrettyTable(
                [
                    "Received",
                    hv.backed_token_symbol,
                    "{}P + {}".format(hv.token_symbol[0], hv.token_symbol),
                    "Liquid USD",
                    "Invested USD",
                ]
            )
        t.align = "l"
        rows = []
        start_op = account.estimate_virtual_op_num(limit_time)
        if start_op > 0:
            start_op -= 1
        only_ops = ["author_reward", "curation_reward"]
        progress_length = (account.virtual_op_count() - start_op) / 1000
        with click.progressbar(
            account.history(start=start_op, use_block_num=False, only_ops=only_ops),
            length=progress_length,
        ) as comment_hist:
            for v in comment_hist:
                if not curation and v["type"] == "curation_reward":
                    continue
                if not post and not comment and v["type"] == "author_reward":
                    continue
                if v["type"] == "author_reward":
                    c = Comment(v, blockchain_instance=hv)
                    try:
                        c.refresh()
                    except exceptions.ContentDoesNotExistsException:
                        continue
                    if not post and not c.is_comment():
                        continue
                    if not comment and c.is_comment():
                        continue
                    # Initialize to zero to avoid UnboundLocalError and support missing fields
                    payout_HBD = Amount("0.000 HBD", blockchain_instance=hv)
                    payout_HIVE = Amount("0.000 HIVE", blockchain_instance=hv)
                    if "hbd_payout" in v:
                        payout_HBD = Amount(v["hbd_payout"], blockchain_instance=hv)
                    if "hive_payout" in v:
                        payout_HIVE = Amount(v["hive_payout"], blockchain_instance=hv)
                    sum_reward[0] += float(payout_HBD)
                    sum_reward[1] += float(payout_HIVE)
                    payout_VESTS = hv.vests_to_token_power(
                        Amount(v["vesting_payout"], blockchain_instance=hv)
                    )
                    sum_reward[2] += float(payout_VESTS)
                    liquid_USD = float(payout_HBD) / float(latest) * float(median_price) + float(
                        payout_HIVE
                    ) * float(median_price)
                    sum_reward[3] += liquid_USD
                    invested_USD = float(payout_VESTS) * float(median_price)
                    sum_reward[4] += invested_USD
                    if c.is_comment():
                        permlink_row = c.parent_permlink
                    else:
                        if title:
                            permlink_row = c.title
                        else:
                            permlink_row = c.permlink
                    rows.append(
                        [
                            c["author"],
                            permlink_row,
                            (
                                (now - formatTimeString(v["timestamp"])).total_seconds()
                                / 60
                                / 60
                                / 24
                            ),
                            (payout_HBD),
                            (payout_HIVE),
                            (payout_VESTS),
                            (liquid_USD),
                            (invested_USD),
                        ]
                    )
                elif v["type"] == "curation_reward":
                    reward = Amount(v["reward"], blockchain_instance=hv)
                    payout_VESTS = hv.vests_to_token_power(reward)
                    liquid_USD = 0
                    invested_USD = float(payout_VESTS) * float(median_price)
                    sum_reward[2] += float(payout_VESTS)
                    sum_reward[4] += invested_USD
                    comment_author = v.get("comment_author", v.get("author", ""))
                    comment_permlink = v.get("comment_permlink", v.get("permlink", ""))
                    permlink_row = comment_permlink
                    if title and comment_author and comment_permlink:
                        try:
                            c = Comment(
                                construct_authorperm(comment_author, comment_permlink),
                                blockchain_instance=hv,
                            )
                            permlink_row = c.title
                        except Exception:
                            permlink_row = comment_permlink
                    rows.append(
                        [
                            comment_author,
                            permlink_row,
                            (
                                (now - formatTimeString(v["timestamp"])).total_seconds()
                                / 60
                                / 60
                                / 24
                            ),
                            0.000,
                            0.000,
                            payout_VESTS,
                            (liquid_USD),
                            (invested_USD),
                        ]
                    )
        sortedList = sorted(rows, key=lambda row: (row[2]), reverse=False)
        if only_sum:
            sortedList = []
        for row in sortedList:
            if length:
                permlink_row = row[1][: int(length)]
            else:
                permlink_row = row[1]
            if author and (permlink or title):
                t.add_row(
                    [
                        row[0],
                        permlink_row,
                        "%.1f days" % row[2],
                        "%.3f" % float(row[3]),
                        "%.3f" % (float(row[4]) + float(row[5])),
                        "%.2f $" % (row[6]),
                        "%.2f $" % (row[7]),
                    ]
                )
            elif author and not (permlink or title):
                t.add_row(
                    [
                        row[0],
                        "%.1f days" % row[2],
                        "%.3f" % float(row[3]),
                        "%.3f" % (float(row[4]) + float(row[5])),
                        "%.2f $" % (row[5]),
                        "%.2f $" % (row[6]),
                    ]
                )
            elif not author and (permlink or title):
                t.add_row(
                    [
                        permlink_row,
                        "%.1f days" % row[2],
                        "%.3f" % float(row[3]),
                        "%.3f" % (float(row[4]) + float(row[5])),
                        "%.2f $" % (row[5]),
                        "%.2f $" % (row[6]),
                    ]
                )
            else:
                t.add_row(
                    [
                        "%.1f days" % row[2],
                        "%.3f" % float(row[3]),
                        "%.3f" % (float(row[4]) + float(row[5])),
                        "%.2f $" % (row[5]),
                        "%.2f $" % (row[6]),
                    ]
                )

        if author and (permlink or title):
            if not only_sum:
                t.add_row(["", "", "", "", "", "", ""])
            t.add_row(
                [
                    "Sum",
                    "-",
                    "-",
                    "{:.2f} {}".format(sum_reward[0], hv.backed_token_symbol),
                    "{:.2f} {}P".format(sum_reward[1] + sum_reward[2], hv.token_symbol[0]),
                    "%.2f $" % (sum_reward[3]),
                    "%.2f $" % (sum_reward[4]),
                ]
            )
        elif not author and not (permlink or title):
            t.add_row(["", "", "", "", ""])
            t.add_row(
                [
                    "Sum",
                    "{:.2f} {}".format(sum_reward[0], hv.backed_token_symbol),
                    "{:.2f} {}P".format(sum_reward[1] + sum_reward[2], hv.token_symbol[0]),
                    "%.2f $" % (sum_reward[2]),
                    "%.2f $" % (sum_reward[3]),
                ]
            )
        else:
            t.add_row(["", "", "", "", "", ""])
            t.add_row(
                [
                    "Sum",
                    "-",
                    "{:.2f} {}".format(sum_reward[0], hv.backed_token_symbol),
                    "{:.2f} {}P".format(sum_reward[1] + sum_reward[2], hv.token_symbol[0]),
                    "%.2f $" % (sum_reward[3]),
                    "%.2f $" % (sum_reward[4]),
                ]
            )
        message = "\nShowing "
        if post:
            if comment + curation == 0:
                message += "post "
            elif comment + curation == 1:
                message += "post and "
            else:
                message += "post, "
        if comment:
            if curation == 0:
                message += "comment "
            else:
                message += "comment and "
        if curation:
            message += "curation "
        message += "rewards for @%s" % account.name
        print(message)
        print(t)


@cli.command()
@click.argument("accounts", nargs=-1, required=False)
@click.option("--only-sum", "-s", help="Show only the sum", is_flag=True, default=False)
@click.option("--post", "-p", help="Show pending post payout", is_flag=True, default=False)
@click.option("--comment", "-c", help="Show pending comments payout", is_flag=True, default=False)
@click.option("--curation", "-v", help="Shows  pending curation", is_flag=True, default=False)
@click.option("--length", "-l", help="Limits the permlink character length", default=None)
@click.option("--author", "-a", help="Show the author for each entry", is_flag=True, default=False)
@click.option(
    "--permlink", "-e", help="Show the permlink for each entry", is_flag=True, default=False
)
@click.option("--title", "-t", help="Show the title for each entry", is_flag=True, default=False)
@click.option(
    "--days",
    "-d",
    default=7.0,
    help="Limit shown rewards by this amount of days (default: 7), max is 7 days.",
)
@click.option(
    "--from",
    "-f",
    "_from",
    default=0.0,
    help="Start day from which on rewards are shown (default: 0), max is 7 days.",
)
def pending(
    accounts, only_sum, post, comment, curation, length, author, permlink, title, days, _from
):
    """Lists pending rewards"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if not accounts:
        accounts = [hv.config["default_account"]]
    if not comment and not curation and not post:
        post = True
        permlink = True
    if days < 0:
        days = 1
    if days > 7:
        days = 7
    if _from < 0:
        _from = 0
    if _from > 7:
        _from = 7
    if _from + days > 7:
        days = 7 - _from
    sp_symbol = "HP"

    # Using built-in timezone support
    max_limit_time = datetime.now(timezone.utc) - timedelta(days=7)
    limit_time = datetime.now(timezone.utc) - timedelta(days=_from + days)
    start_time = datetime.now(timezone.utc) - timedelta(days=_from)
    for account in accounts:
        sum_reward = [0, 0, 0, 0]
        account = Account(account, blockchain_instance=hv)
        median_price = Price(hv.get_current_median_history(), blockchain_instance=hv)
        m = Market(blockchain_instance=hv)
        latest = m.ticker()["latest"]
        if author and permlink:
            t = PrettyTable(
                [
                    "Author",
                    "Permlink",
                    "Cashout",
                    hv.backed_token_symbol,
                    sp_symbol,
                    "Liquid USD",
                    "Invested USD",
                ]
            )
        elif author and title:
            t = PrettyTable(
                [
                    "Author",
                    "Title",
                    "Cashout",
                    hv.backed_token_symbol,
                    sp_symbol,
                    "Liquid USD",
                    "Invested USD",
                ]
            )
        elif author:
            t = PrettyTable(
                [
                    "Author",
                    "Cashout",
                    hv.backed_token_symbol,
                    sp_symbol,
                    "Liquid USD",
                    "Invested USD",
                ]
            )
        elif not author and permlink:
            t = PrettyTable(
                [
                    "Permlink",
                    "Cashout",
                    hv.backed_token_symbol,
                    sp_symbol,
                    "Liquid USD",
                    "Invested USD",
                ]
            )
        elif not author and title:
            t = PrettyTable(
                [
                    "Title",
                    "Cashout",
                    hv.backed_token_symbol,
                    sp_symbol,
                    "Liquid USD",
                    "Invested USD",
                ]
            )
        else:
            t = PrettyTable(
                ["Cashout", hv.backed_token_symbol, sp_symbol, "Liquid USD", "Invested USD"]
            )
        t.align = "l"
        rows = []
        c_list = {}
        start_op = account.estimate_virtual_op_num(limit_time)
        stop_op = account.estimate_virtual_op_num(start_time)
        if start_op > 0:
            start_op -= 1
        progress_length = (stop_op - start_op) / 1000
        with click.progressbar(
            map(
                Comment,
                account.history(
                    start=start_op, stop=stop_op, use_block_num=False, only_ops=["comment"]
                ),
            ),
            length=progress_length,
        ) as comment_hist:
            for v in comment_hist:
                try:
                    v.refresh()
                except exceptions.ContentDoesNotExistsException:
                    continue
                author_reward = v.get_author_rewards()
                if float(author_reward["total_payout_HBD"]) < 0.001:
                    continue
                if v.permlink in c_list:
                    continue
                c_list[v.permlink] = 1
                if not v.is_pending():
                    continue
                if not post and not v.is_comment():
                    continue
                if not comment and v.is_comment():
                    continue
                if v["author"] != account["name"]:
                    continue
                payout_HBD = author_reward["payout_HBD"]
                sum_reward[0] += float(payout_HBD)
                payout_HP = author_reward["payout_HP"]
                sum_reward[1] += float(payout_HP)
                liquid_USD = (
                    float(author_reward["payout_HBD"]) / float(latest) * float(median_price)
                )
                sum_reward[2] += liquid_USD
                invested_USD = float(author_reward["payout_HP"]) * float(median_price)
                sum_reward[3] += invested_USD
                if v.is_comment():
                    permlink_row = v.permlink
                else:
                    if title:
                        permlink_row = v.title
                    else:
                        permlink_row = v.permlink
                rows.append(
                    [
                        v["author"],
                        permlink_row,
                        ((v["created"] - max_limit_time).total_seconds() / 60 / 60 / 24),
                        (payout_HBD),
                        (payout_HP),
                        (liquid_USD),
                        (invested_USD),
                    ]
                )
        if curation:
            votes = AccountVotes(account, start=limit_time, stop=start_time, blockchain_instance=hv)
            for vote in votes:
                authorperm = construct_authorperm(vote["author"], vote["permlink"])
                try:
                    c = Comment(authorperm, blockchain_instance=hv)
                except exceptions.ContentDoesNotExistsException:
                    continue
                rewards = c.get_curation_rewards()
                if not rewards["pending_rewards"]:
                    continue
                days_to_payout = (c["created"] - max_limit_time).total_seconds() / 60 / 60 / 24
                if days_to_payout < 0:
                    continue
                payout_HP = rewards["active_votes"][account["name"]]
                liquid_USD = 0
                invested_USD = float(payout_HP) * float(median_price)
                sum_reward[1] += float(payout_HP)
                sum_reward[3] += invested_USD
                if title:
                    permlink_row = c.title
                else:
                    permlink_row = c.permlink
                rows.append(
                    [
                        c["author"],
                        permlink_row,
                        days_to_payout,
                        0.000,
                        payout_HP,
                        (liquid_USD),
                        (invested_USD),
                    ]
                )
        sortedList = sorted(rows, key=lambda row: (row[2]), reverse=True)
        if only_sum:
            sortedList = []
        for row in sortedList:
            if length:
                permlink_row = row[1][: int(length)]
            else:
                permlink_row = row[1]
            if author and (permlink or title):
                t.add_row(
                    [
                        row[0],
                        permlink_row,
                        "%.1f days" % row[2],
                        "%.3f" % float(row[3]),
                        "%.3f" % float(row[4]),
                        "%.2f $" % (row[5]),
                        "%.2f $" % (row[6]),
                    ]
                )
            elif author and not (permlink or title):
                t.add_row(
                    [
                        row[0],
                        "%.1f days" % row[2],
                        "%.3f" % float(row[3]),
                        "%.3f" % float(row[4]),
                        "%.2f $" % (row[5]),
                        "%.2f $" % (row[6]),
                    ]
                )
            elif not author and (permlink or title):
                t.add_row(
                    [
                        permlink_row,
                        "%.1f days" % row[2],
                        "%.3f" % float(row[3]),
                        "%.3f" % float(row[4]),
                        "%.2f $" % (row[5]),
                        "%.2f $" % (row[6]),
                    ]
                )
            else:
                t.add_row(
                    [
                        "%.1f days" % row[2],
                        "%.3f" % float(row[3]),
                        "%.3f" % float(row[4]),
                        "%.2f $" % (row[5]),
                        "%.2f $" % (row[6]),
                    ]
                )

        if author and (permlink or title):
            if not only_sum:
                t.add_row(["", "", "", "", "", "", ""])
            t.add_row(
                [
                    "Sum",
                    "-",
                    "-",
                    "{:.2f} {}".format(sum_reward[0], hv.backed_token_symbol),
                    "{:.2f} {}".format(sum_reward[1], sp_symbol),
                    "%.2f $" % (sum_reward[2]),
                    "%.2f $" % (sum_reward[3]),
                ]
            )
        elif not author and not (permlink or title):
            t.add_row(["", "", "", "", ""])
            t.add_row(
                [
                    "Sum",
                    "{:.2f} {}".format(sum_reward[0], hv.backed_token_symbol),
                    "{:.2f} {}".format(sum_reward[1], sp_symbol),
                    "%.2f $" % (sum_reward[2]),
                    "%.2f $" % (sum_reward[3]),
                ]
            )
        else:
            t.add_row(["", "", "", "", "", ""])
            t.add_row(
                [
                    "Sum",
                    "-",
                    "{:.2f} {}".format(sum_reward[0], hv.backed_token_symbol),
                    "{:.2f} {}".format(sum_reward[1], sp_symbol),
                    "%.2f $" % (sum_reward[2]),
                    "%.2f $" % (sum_reward[3]),
                ]
            )
        message = "\nShowing pending "
        if post:
            if comment + curation == 0:
                message += "post "
            elif comment + curation == 1:
                message += "post and "
            else:
                message += "post, "
        if comment:
            if curation == 0:
                message += "comment "
            else:
                message += "comment and "
        if curation:
            message += "curation "
        message += "rewards for @%s" % account.name
        print(message)
        print(t)


@cli.command()
@click.argument("account", nargs=1, required=False)
@click.option("--reward_sbd", help="Amount of HBD you would like to claim", default=0)
@click.option("--reward_vests", help="Amount of VESTS you would like to claim", default=0)
@click.option("--claim_all_sbd", help="Claim all HBD, overwrites reward_sbd", is_flag=True)
@click.option("--claim_all_vests", help="Claim all VESTS, overwrites reward_vests", is_flag=True)
@click.option("--export", "-e", help="When set, transaction is stored in a file")
def claimreward(
    account,
    reward_sbd,
    reward_vests,
    claim_all_sbd,
    claim_all_vests,
    export,
):
    """Claim reward balances

    By default, this will claim ``all`` outstanding balances.
    """
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if not account:
        account = hv.config["default_account"]
    acc = Account(account, blockchain_instance=hv)
    r = acc.balances["rewards"]
    if len(r) == 3 and r[0].amount + r[1].amount + r[2].amount == 0:
        print("Nothing to claim.")
        return
    elif len(r) == 2 and r[0].amount + r[1].amount:
        print("Nothing to claim.")
        return
    if not unlock_wallet(hv):
        return
    if claim_all_sbd:
        reward_sbd = r[0]
    if claim_all_vests:
        reward_vests = r[1]

    tx = acc.claim_reward_balance(reward_sbd, reward_vests)
    export_trx(tx, export)
    tx = json.dumps(tx, indent=4)
    print(tx)


@cli.command()
@click.argument("jsonid", nargs=1)
@click.argument("json_data", nargs=-1)
@click.option("--account", "-a", help="The account which broadcasts the custom_json")
@click.option(
    "--active",
    "-t",
    help="When set, the active key is used for broadcasting",
    is_flag=True,
    default=False,
)
@click.option("--export", "-e", help="When set, transaction is stored in a file")
def customjson(jsonid, json_data, account, active, export):
    """Broadcasts a custom json

    First parameter is the cusom json id, the second field is a json file or a json key value combination
    e.g. hive-nectar customjson -a thecrazygm dw-heist username thecrazygm amount 100
    """
    if jsonid is None:
        print("First argument must be the custom_json id")
    if json_data is None:
        print("Second argument must be the json_data, can be a string or a file name.")
    data = import_custom_json(jsonid, json_data)
    if data is None:
        return
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if not account:
        account = hv.config["default_account"]
    if not unlock_wallet(hv):
        return
    _acc = Account(account, blockchain_instance=hv)
    if active:
        tx = hv.custom_json(jsonid, data, required_auths=[account])
    else:
        tx = hv.custom_json(jsonid, data, required_posting_auths=[account])
    export_trx(tx, export)
    tx = json.dumps(tx, indent=4)
    print(tx)


@cli.command()
@click.argument("blocknumber", nargs=1, required=False)
@click.option("--trx", "-t", help="Show only one transaction number", default=None)
@click.option(
    "--use-api",
    "-u",
    help="Uses the get_potential_signatures api call",
    is_flag=True,
    default=False,
)
def verify(blocknumber, trx, use_api):
    """Returns the public signing keys for a block"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    b = Blockchain(blockchain_instance=hv)
    i = 0
    if not blocknumber:
        blocknumber = b.get_current_block_num()
    try:
        int(blocknumber)
        block = Block(blocknumber, blockchain_instance=hv)
        if trx is not None:
            i = int(trx)
            trxs = [block.json_transactions[int(trx)]]
        else:
            trxs = block.json_transactions
    except Exception:
        trxs = [b.get_transaction(blocknumber)]
        blocknumber = trxs[0]["block_num"]
    wallet = Wallet(blockchain_instance=hv)
    t = PrettyTable(["trx", "Signer key", "Account"])
    t.align = "l"
    if not use_api:
        from nectarbase.signedtransactions import Signed_Transaction
    for trx in trxs:
        if not use_api:
            # trx is now identical to the output of get_transaction
            # This is just for testing porpuse
            if True:
                signed_tx = Signed_Transaction(trx.copy())
            else:
                tx = b.get_transaction(trx["transaction_id"])
                signed_tx = Signed_Transaction(tx)
            public_keys = []
            for key in signed_tx.verify(chain=hv.chain_params, recover_parameter=True):
                public_keys.append(format(Base58(key, prefix=hv.prefix), hv.prefix))
        else:
            tx = TransactionBuilder(tx=trx, blockchain_instance=hv)
            public_keys = tx.get_potential_signatures()
        accounts = []
        empty_public_keys = []
        for key in public_keys:
            account = wallet.getAccountFromPublicKey(key)
            if account is None:
                empty_public_keys.append(key)
            else:
                accounts.append(account)
        new_public_keys = []
        for key in public_keys:
            if key not in empty_public_keys or use_api:
                new_public_keys.append(key)
        if len(new_public_keys) == 0:
            for key in public_keys:
                new_public_keys.append(key)
        if isinstance(new_public_keys, list) and len(new_public_keys) == 1:
            new_public_keys = new_public_keys[0]
        else:
            new_public_keys = json.dumps(new_public_keys, indent=4)
        if isinstance(accounts, list) and len(accounts) == 1:
            accounts = accounts[0]
        else:
            accounts = json.dumps(accounts, indent=4)
        t.add_row(["%d" % i, new_public_keys, accounts])
        i += 1
    print(t)


@cli.command()
def chainconfig():
    """Prints chain config in a table"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    chain_config = hv.get_config()
    t = PrettyTable(["Key", "Value"])
    t.align = "l"
    for key in chain_config:
        if isinstance(chain_config[key], dict) and "amount" in chain_config[key]:
            t.add_row([key, str(Amount(chain_config[key], blockchain_instance=hv))])
        else:
            t.add_row([key, chain_config[key]])
    print(t)


@cli.command()
@click.argument("objects", nargs=-1)
def info(objects):
    """Show basic blockchain info

    General information about the blockchain, a block, an account,
    a post/comment and a public key
    """
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if not objects:
        t = PrettyTable(["Key", "Value"])
        t.align = "l"
        info = hv.get_dynamic_global_properties()
        median_price = hv.get_current_median_history()
        token_per_mvest = hv.get_token_per_mvest()
        chain_props = hv.get_chain_properties()
        try:
            price = (
                Amount(median_price["base"], blockchain_instance=hv).amount
                / Amount(median_price["quote"], blockchain_instance=hv).amount
            )
        except Exception:
            price = None
        for key in info:
            if isinstance(info[key], dict) and "amount" in info[key]:
                t.add_row([key, str(Amount(info[key], blockchain_instance=hv))])
            else:
                t.add_row([key, info[key]])
        t.add_row(["%s per mvest" % hv.token_symbol, token_per_mvest])
        if price is not None:
            t.add_row(["internal price", price])
        t.add_row(
            [
                "account_creation_fee",
                str(Amount(chain_props["account_creation_fee"], blockchain_instance=hv)),
            ]
        )
        print(t.get_string(sortby="Key"))
        # Block
    for obj in objects:
        if (
            re.match(r"^[0-9-]*$", obj)
            or re.match(r"^-[0-9]*$", obj)
            or re.match(r"^[0-9-]*:[0-9]", obj)
            or re.match(r"^[0-9-]*:-[0-9]", obj)
        ):
            tran_nr = ""
            if re.match(r"^[0-9-]*:[0-9-]", obj):
                obj, tran_nr = obj.split(":")
            if int(obj) < 1:
                b = Blockchain(blockchain_instance=hv)
                block_number = b.get_current_block_num() + int(obj) - 1
            else:
                block_number = obj
            block = Block(block_number, blockchain_instance=hv)
            if block:
                t = PrettyTable(["Key", "Value"])
                t.align = "l"
                block_json = block.json()
                for key in sorted(block_json):
                    value = block_json[key]
                    if key == "transactions" and not bool(tran_nr):
                        t.add_row(["Nr. of transactions", len(value)])
                    elif key == "transactions" and bool(tran_nr):
                        if int(tran_nr) < 0:
                            tran_nr = len(value) + int(tran_nr)
                        else:
                            tran_nr = int(tran_nr)
                        if len(value) > tran_nr - 1 and tran_nr > -1:
                            t_value = json.dumps(value[tran_nr], indent=4)
                            t.add_row(["transaction %d/%d" % (tran_nr, len(value)), t_value])
                    elif key == "transaction_ids" and not bool(tran_nr):
                        t.add_row(["Nr. of transaction_ids", len(value)])
                    elif key == "transaction_ids" and bool(tran_nr):
                        if int(tran_nr) < 0:
                            tran_nr = len(value) + int(tran_nr)
                        else:
                            tran_nr = int(tran_nr)
                        if len(value) > tran_nr - 1 and tran_nr > -1:
                            t.add_row(
                                [
                                    "transaction_id %d/%d" % (int(tran_nr), len(value)),
                                    value[tran_nr],
                                ]
                            )
                    else:
                        t.add_row([key, value])
                print(t)
            else:
                print("Block number %s unknown" % obj)
        elif re.match(r"^[a-zA-Z0-9\-\._]{2,16}$", obj):
            account = Account(obj, blockchain_instance=hv)
            t = PrettyTable(["Key", "Value"])
            t.align = "l"
            t._max_width = {"Value": 80}
            account_json = account.json()
            for key in sorted(account_json):
                value = account_json[key]
                if key == "json_metadata":
                    value = json.dumps(json.loads(value or "{}"), indent=4)
                elif key in ["posting", "witness_votes", "active", "owner"]:
                    value = json.dumps(value, indent=4)
                elif key == "reputation" and int(value) > 0:
                    value = int(value)
                    rep = account.rep
                    value = "{:.2f} ({:d})".format(rep, value)
                elif isinstance(value, dict) and "asset" in value:
                    value = str(account[key])
                t.add_row([key, value])
            print(t)

            # witness available?
            try:
                witness = Witness(obj, blockchain_instance=hv)
                witness_json = witness.json()
                t = PrettyTable(["Key", "Value"])
                t.align = "l"
                for key in sorted(witness_json):
                    value = witness_json[key]
                    if key in ["props", "hbd_exchange_rate"]:
                        value = json.dumps(value, indent=4)
                    t.add_row([key, value])
                print(t)
            except exceptions.WitnessDoesNotExistsException as e:
                print(str(e))
        # Public Key
        elif re.match(r"^" + hv.prefix + ".{48,55}$", obj):
            account = hv.wallet.getAccountFromPublicKey(obj)
            if account:
                account = Account(account, blockchain_instance=hv)
                key_type = hv.wallet.getKeyType(account, obj)
                t = PrettyTable(["Account", "Key_type"])
                t.align = "l"
                t._max_width = {"Value": 80}
                t.add_row([account["name"], key_type])
                print(t)
            else:
                print("Public Key %s not known" % obj)
        # Post identifier
        elif re.match(r".*@.{3,16}/.*$", obj):
            post = Comment(obj, blockchain_instance=hv)
            post_json = post.json()
            if post_json:
                t = PrettyTable(["Key", "Value"])
                t.align = "l"
                t._max_width = {"Value": 80}
                for key in sorted(post_json):
                    if key in ["body", "active_votes"]:
                        value = "not shown"
                    else:
                        value = post_json[key]
                    if key in ["json_metadata"]:
                        value = json.loads(value)
                        value = json.dumps(value, indent=4)
                    elif key in ["tags", "active_votes"]:
                        value = json.dumps(value, indent=4)
                    t.add_row([key, value])
                print(t)
            else:
                print("Post now known" % obj)
        elif re.match(r"^[a-zA-Z0-9\_]{40}$", obj):
            b = Blockchain(blockchain_instance=hv)
            from nectarapi.exceptions import UnknownTransaction

            try:
                trx = b.get_transaction(obj)
            except UnknownTransaction:
                print("%s is unknown!" % obj)
                return
            t = PrettyTable(["Key", "Value"])
            t.align = "l"
            t._max_width = {"Value": 80}
            for key in trx:
                value = trx[key]
                if key in ["operations", "signatures"]:
                    value = json.dumps(value, indent=4)
                t.add_row([key, value])
            print(t)
        else:
            print("Couldn't identify object to read")


@cli.command()
@click.argument("account", nargs=1, required=False)
@click.option("--signing-account", "-s", help="Signing account, when empty account is used.")
def userdata(account, signing_account):
    """Get the account's email address and phone number.

    The request has to be signed by the requested account or an admin account.
    """
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    # Removed in Hive-only build
    print("'userdata' command is no longer available in the Hive-only build.")
    return


@cli.command()
@click.argument("account", nargs=1, required=False)
@click.option(
    "--limit", "-l", help="Defines how many ops should be printed (default=10)", default=10
)
@click.option(
    "--sort", "-s", help="Defines the printing sorting, 1 ->, -1 <- (default=-1)", default=-1
)
@click.option("--max-length", "-m", help="Maximum printed string length", default=80)
@click.option(
    "--virtual-ops", "-v", help="When set, virtual ops are also shown", is_flag=True, default=False
)
@click.option(
    "--only-ops",
    "-o",
    help="Included komma seperated list of op types, which limits the shown operations. When set, virtual-ops is always set to true",
)
@click.option(
    "--exclude-ops",
    "-e",
    help="Excluded komma seperated list of op types, which limits the shown operations.",
)
@click.option("--json-file", "-j", help="When set, the results are written into a json file")
def history(account, limit, sort, max_length, virtual_ops, only_ops, exclude_ops, json_file):
    """Returns account history operations as table"""
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if not account:
        if "default_account" in hv.config:
            account = hv.config["default_account"]
    account = Account(account, blockchain_instance=hv)
    t = PrettyTable(["Index", "Type", "Hist op"])
    t.align = "l"
    t._max_width = {"Hist op": max_length}
    cnt = 0
    batch_size = 1000
    if batch_size > int(limit) + 1 and int(limit) > 0:
        batch_size = int(limit) + 1
    if only_ops is None:
        only_ops = []
    else:
        only_ops = only_ops.split(",")
    if exclude_ops is None:
        exclude_ops = []
    else:
        exclude_ops = exclude_ops.split(",")
    if len(only_ops) > 0:
        virtual_ops = True
    data = []
    if int(sort) == -1:
        hist = account.history_reverse(
            batch_size=batch_size, only_ops=only_ops, exclude_ops=exclude_ops
        )
    else:
        hist = account.history(batch_size=batch_size, only_ops=only_ops, exclude_ops=exclude_ops)
    for h in hist:
        if h["virtual_op"] == 1 and not virtual_ops:
            continue

        cnt += 1
        if cnt > int(limit) and int(limit) > 0:
            break
        if json_file is not None:
            data.append(h)
        else:
            # if key in ["operations", "signatures"]:
            index = h.pop("index")
            op_type = h.pop("type")
            h.pop("trx_in_block")
            h.pop("op_in_trx")
            h.pop("virtual_op")
            h.pop("_id")
            if h["trx_id"] == "0000000000000000000000000000000000000000":
                h.pop("trx_id")
            for key in h:
                if isinstance(h[key], dict) and "nai" in h[key]:
                    h[key] = str(Amount(h[key], blockchain_instance=hv))
                if key == "json" or key == "json_metadata" and h[key] is not None and h[key] != "":
                    h[key] = json.loads(h[key])
            value = json.dumps(h, indent=4)
            t.add_row([str(index), op_type, value])

    if json_file is not None:
        with open(json_file, "w", encoding="utf-8") as f:
            json.dump(data, f)
    else:
        print(t)


@cli.command()
@click.argument("account", nargs=1, required=False)
@click.option("--signing-account", "-s", help="Signing account, when empty account is used.")
def featureflags(account, signing_account):
    """Get the account's feature flags.

    The request has to be signed by the requested account or an admin account.
    """
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    # Removed in Hive-only build
    print("'featureflags' command is no longer available in the Hive-only build.")
    return


@cli.command()
@click.option(
    "--block",
    "-b",
    help="Select a block number, when skipped the latest block is used.",
    default=None,
)
@click.option(
    "--trx-id", "-t", help="Select a trx-id, When skipped, the latest one is used.", default=None
)
@click.option("--draws", "-d", help="Number of draws (default = 1)", default=1)
@click.option(
    "--participants",
    "-p",
    help="Number of participants or file name including participants (one participant per line), (default = 100)",
    default="100",
)
@click.option(
    "--hashtype", "-h", help="Can be md5, sha256, sha512 (default = sha256)", default="sha256"
)
@click.option(
    "--separator",
    "-s",
    help="Is used for sha256 and sha512 to seperate the draw number from the seed (default = ,)",
    default=",",
)
@click.option("--account", "-a", help="The account which broadcasts the reply")
@click.option(
    "--reply",
    "-r",
    help="Parent post/comment authorperm. When set, the results will be broadcasted as reply to this authorperm.",
    default=None,
)
@click.option(
    "--without-replacement",
    "-w",
    help="When set, numbers are drawed without replacement.",
    is_flag=True,
    default=False,
)
@click.option(
    "--markdown",
    "-m",
    help="When set, results are returned in markdown format",
    is_flag=True,
    default=False,
)
def draw(
    block,
    trx_id,
    draws,
    participants,
    hashtype,
    separator,
    account,
    reply,
    without_replacement,
    markdown,
):
    """Generate pseudo-random numbers based on trx id, block id and previous block id.

    When using --reply, the result is directly broadcasted as comment
    """
    hv = shared_blockchain_instance()
    if hv.rpc is not None:
        hv.rpc.rpcconnect()
    if not account:
        account = hv.config["default_account"]
    if reply is not None:
        if not unlock_wallet(hv):
            return
        reply_comment = Comment(reply, blockchain_instance=hv)
    if block is not None and block != "":
        block = Block(int(block), blockchain_instance=hv)
    else:
        blockchain = Blockchain(blockchain_instance=hv)
        block = blockchain.get_current_block()
    data = None

    for trx in block.transactions:
        if trx["transaction_id"] == trx_id:
            data = trx
        elif trx_id is None:
            trx_id = trx["transaction_id"]
            data = trx
    if trx_id is None:
        trx_id = "0"

    if os.path.exists(participants):
        with open(participants) as f:
            content = f.read()
        if content.find(",") > 0:
            participants_list = content.split(",")
        else:
            participants_list = content.split("\n")
        if participants_list[-1] == "":
            participants_list = participants_list[:-1]
        participants = len(participants_list)
    else:
        participants = int(participants)
        participants_list = []

    if without_replacement:
        assert draws <= participants
    trx = data["operations"][0]["value"]
    if hashtype == "md5":
        seed = hashlib.md5((trx_id + block["block_id"] + block["previous"]).encode()).hexdigest()
    elif hashtype == "sha256":
        seed = hashlib.sha256((trx_id + block["block_id"] + block["previous"]).encode()).hexdigest()
    elif hashtype == "sha512":
        seed = hashlib.sha512((trx_id + block["block_id"] + block["previous"]).encode()).hexdigest()
    random.seed(a=seed, version=2)
    t = PrettyTable(["Key", "Value"])
    t.align = "l"
    t.add_row(["block number", block["id"]])
    t.add_row(["trx id", trx_id])
    t.add_row(["block id", block["block_id"]])
    t.add_row(["previous", block["previous"]])
    t.add_row(["hash type", hashtype])
    t.add_row(["draws", draws])
    t.add_row(["participants", participants])
    draw_list = [x + 1 for x in range(participants)]
    results = []
    for i in range(int(draws)):
        if hashtype == "md5":
            number = int(random.random() * len(draw_list))
        elif hashtype == "sha256":
            seed = hashlib.sha256(
                (trx_id + block["block_id"] + block["previous"] + separator + str(i + 1)).encode()
            ).digest()
            bigRand = int.from_bytes(seed, "big")
            number = bigRand % (len(draw_list))
        elif hashtype == "sha512":
            seed = hashlib.sha512(
                (trx_id + block["block_id"] + block["previous"] + separator + str(i + 1)).encode()
            ).digest()
            bigRand = int.from_bytes(seed, "big")
            number = bigRand % (len(draw_list))
        results.append(draw_list[number])
        if len(participants_list) > 0:
            t.add_row(
                [
                    "%d. draw" % (i + 1),
                    "%d - %s" % (draw_list[number], participants_list[draw_list[number] - 1]),
                ]
            )
        else:
            t.add_row(["%d. draw" % (i + 1), draw_list[number]])
        if without_replacement:
            draw_list.pop(number)

    body = "The following results can be checked with:\n"
    body += "```\n"
    if without_replacement:
        body += "hive-nectar draw -d %d -p %d -b %d -t %s -h %s -s '%s' -w\n" % (
            draws,
            participants,
            block["id"],
            trx_id,
            hashtype,
            separator,
        )
    else:
        body += "hive-nectar draw -d %d -p %d -b %d -t %s -h %s -s '%s'\n" % (
            draws,
            participants,
            block["id"],
            trx_id,
            hashtype,
            separator,
        )
    body += "```\n\n"
    body += "| key | value |\n"
    body += "| --- | --- |\n"
    body += "| block number | [%d](https://hiveblocks.com/b/%d#%s) |\n" % (
        block["id"],
        block["id"],
        trx_id,
    )
    body += "| trx id | [{}](https://hiveblocks.com/tx/{}) |\n".format(trx_id, trx_id)
    body += "| block id | %s |\n" % block["block_id"]
    body += "| previous id | %s |\n" % block["previous"]
    body += "| hash type | %s |\n" % hashtype
    body += "| draws | %d |\n" % draws
    body += "| participants | %d |\n" % participants
    i = 0
    for result in results:
        i += 1
        if len(participants_list) > 0:
            body += "| %d. draw | %d - %s |\n" % (i, result, participants_list[result - 1])
        else:
            body += "| %d. draw | %d |\n" % (i, result)
    if markdown:
        print(body)
    else:
        print(t)
    if reply:
        reply_comment.reply(body, author=account)


if __name__ == "__main__":
    if getattr(sys, "frozen", False):
        os.environ["SSL_CERT_FILE"] = os.path.join(sys._MEIPASS, "lib", "cert.pem")  # type: ignore
        cli(sys.argv[1:])
    else:
        cli()
