#! /usr/bin/env python3
"""
Convenience script for reading a ledger transaction data (stored in leveldb)

"""
import argparse
import logging
import os
import shutil

from pathlib import Path

from common.serializers.json_serializer import JsonSerializer
from plenum.common.ledger import Ledger
from plenum.common.constants import HS_ROCKSDB

from indy_common.config_util import getConfig
from indy_common.config_helper import ConfigHelper, NodeConfigHelper
from common.serializers.serialization import ledger_txn_serializer


logging.root.handlers = []
logger = logging.getLogger()
logger.propagate = False
logger.disabled = True


def read_args():
    parser = argparse.ArgumentParser(
        description="Read ledger transactions")

    parser.add_argument('--type', required=True,
                        help='Ledger type (pool, domain, config)')
    parser.add_argument(
        '--frm',
        required=False,
        default=None,
        help="read all transactions starting from (beginning by default)")
    parser.add_argument('--to', required=False, default=100,
                        help="read all transactions till (100 by default)")
    parser.add_argument('--seq_no', required=False,
                        help="read a particular transaction")
    parser.add_argument('--count', required=False, action='store_true',
                        help="returns the number of txns in the given ledger")
    parser.add_argument('--node_name', required=False, help="Node's name")
    parser.add_argument('--client_name', required=False, help="Client's name")
    parser.add_argument('--serializer', required=False, default='json',
                        help="How to represent the data (json by default)")
    parser.add_argument('--network', required=False, type=str,
                        help="Network name to read ledger from")

    return parser.parse_args()


def get_ledger_dir(node_name, client_name, network):
    if node_name and client_name:
        print("Either 'node_name' or 'client_name' can be specified")
        exit()

    config = getConfig()
    _network = network if network else config.NETWORK_NAME
    if node_name or client_name:
        if node_name:
            config_helper = NodeConfigHelper(node_name, config)
            ledger_data_dir = config_helper.ledger_dir
        else:
            ledger_data_dir = os.path.join(config.CLI_BASE_DIR, _network,
                                           config.clientDataDir, client_name)
        if not os.path.isdir(ledger_data_dir):
            print("Specified Node or Client folder not found: {}".format(ledger_data_dir))
            exit()
    else:
        config_helper = ConfigHelper(config)
        dirs = os.listdir(config_helper.ledger_data_dir)
        if len(dirs) == 0:
            print("Node's 'data' folder not found: {}".format(config_helper.ledger_data_dir))
            exit()
        ledger_data_dir = os.path.join(config_helper.ledger_data_dir, dirs[0])

    return ledger_data_dir


def get_storage(type_, ledger_data_dir):
    config = getConfig()

    storage_name = None
    if type_ == 'pool':
        storage_name = config.poolTransactionsFile
    elif type_ == 'domain':
        storage_name = config.domainTransactionsFile
    elif type_ == 'config':
        storage_name = config.configTransactionsFile
    else:
        print("Unknown ledger type: {}".format(type_))
        exit()

    return Ledger._defaultStore(dataDir=ledger_data_dir,
                                logName=storage_name,
                                ensureDurability=True,
                                open=True,
                                config=config,
                                read_only=True)


def print_txns(storage, args):
    serializer = None
    if args.serializer == 'json':
        serializer = JsonSerializer()
    else:
        print("Unknown serializer for output: {}".format(args.serializer))
        exit()

    # --count
    count = args.count
    if count:
        print_count(storage)
        return

    # --seq_no
    seq_no = args.seq_no
    if seq_no:
        print_by_seq_no(storage, seq_no, serializer)
        return

    # print all (--from --to)
    print_all(storage, serializer)


def print_by_seq_no(storage, seq_no, serializer):
    try:
        txn = storage.get(seq_no)
    except KeyError:
        print('No transactions found for seq_no={}'.format(seq_no))
        return
    txn = serializer.serialize(txn, toBytes=False)
    print(txn)
    return


def print_count(storage):
    print(storage.size)


def print_all(storage, serializer):
    frm = int(args.frm) if args.frm else None
    to = int(args.to) if args.to else None
    for seqNo, txn in storage.iterator(start=frm, end=to):
        txn = ledger_txn_serializer.deserialize(txn)
        print(int(seqNo), serializer.serialize(txn, toBytes=False))


def make_copy_of_ledger(data_dir):
    read_copy_data_dir = data_dir + '-read-copy'
    if os.path.exists(read_copy_data_dir):
        shutil.rmtree(read_copy_data_dir)
    shutil.copytree(data_dir, read_copy_data_dir)
    return read_copy_data_dir


if __name__ == '__main__':
    args = read_args()
    config = getConfig()

    ledger_data_dir = get_ledger_dir(args.node_name, args.client_name, args.network)
    read_copy_ledger_data_dir = None
    try:
        # RocksDB supports real read-only mode and does not need to have a ledger copy.
        if config.hashStore['type'].lower() != HS_ROCKSDB:
            # NOTE: such approach works well only for small ledgers.
            tmp = make_copy_of_ledger(ledger_data_dir)

            # Let's be paranoid to avoid removing of ledger instead of its copy.
            ledger_path = Path(ledger_data_dir)
            ledger_copy_path = Path(tmp)
            assert ledger_path != ledger_copy_path
            assert ledger_copy_path not in ledger_path.parents

            read_copy_ledger_data_dir = tmp
            ledger_data_dir = read_copy_ledger_data_dir
        storage = get_storage(args.type, ledger_data_dir)
        print_txns(storage, args)
    finally:
        if read_copy_ledger_data_dir:
            shutil.rmtree(read_copy_ledger_data_dir)
