#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright 2012-2020 CERN
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Authors:
# - Mario Lassnig <mario.lassnig@cern.ch>, 2012-2020
# - Martin Barisits <martin.barisits@cern.ch>, 2012-2020
# - Vincent Garonne <vincent.garonne@cern.ch>, 2012-2018
# - Thomas Beermann <thomas.beermann@cern.ch>, 2012-2013
# - Cedric Serfon <cedric.serfon@cern.ch>, 2013-2020
# - Wen Guan <wen.guan@cern.ch>, 2014
# - Ralph Vigne <ralph.vigne@cern.ch>, 2014
# - David Cameron <david.cameron@cern.ch>, 2014-2015
# - Cheng-Hsi Chao <cheng-hsi.chao@cern.ch>, 2014
# - Joaquín Bogado <jbogado@linti.unlp.edu.ar>, 2014-2019
# - Brian Bockelman <bbockelm@cse.unl.edu>, 2017-2018
# - Tomas Javurek <tomas.javurek@cern.ch>, 2018
# - Nicolo Magini <nicolo.magini@cern.ch>, 2018
# - nataliaratnikova <natasha@fnal.gov>, 2018
# - Hannes Hansen <hannes.jakob.hansen@cern.ch>, 2018-2019
# - Dimitrios Christidis <dimitrios.christidis@cern.ch>, 2019-2020
# - Ruturaj Gujar <ruturaj.gujar23@gmail.com>, 2019
# - Brandon White <bjwhite@fnal.gov>, 2020
# - Jaroslav Guenther <jaroslav.guenther@cern.ch>, 2019-2020
# - Patrick Austin <patrick.austin@stfc.ac.uk>, 2020
# - Rob Barnsley <robbarnsley@users.noreply.github.com>, 2020

from __future__ import division
from __future__ import print_function

import argparse
import datetime
import json
import math
import logging
import os
import signal
import sys
import time
import traceback

try:
    from ConfigParser import NoOptionError, NoSectionError
except ImportError:
    from configparser import NoOptionError, NoSectionError

from functools import wraps

import argcomplete
import tabulate

from rucio.client import Client
from rucio.common.config import config_get
from rucio.common.exception import (AccountNotFound, DataIdentifierAlreadyExists, AccessDenied,
                                    DataIdentifierNotFound, InvalidObject, ReplicaNotFound,
                                    RSENotFound, RSEOperationNotSupported, InvalidRSEExpression,
                                    DuplicateContent, RuleNotFound, CannotAuthenticate,
                                    Duplicate, ReplicaIsLocked, ConfigNotFound, ScopeNotFound)
from rucio.common.utils import chunks, construct_surl, sizefmt, get_bytes_value_from_string, render_json, parse_response, extract_scope
from rucio import version
from rucio.rse import rsemanager as rsemgr

possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
                                                os.pardir, os.pardir))
if os.path.exists(os.path.join(possible_topdir, 'lib/rucio', '__init__.py')):
    sys.path.insert(0, possible_topdir)


SUCCESS = 0
FAILURE = 1
DEFAULT_PORT = 443


logger = logging.getLogger("user")

tablefmt = 'psql'


def setup_logger(logger):
    logger.setLevel(logging.DEBUG)
    hdlr = logging.StreamHandler()

    def emit_decorator(fcn):
        def func(*args):
            if True:
                formatter = logging.Formatter("%(message)s")
            else:
                levelno = args[0].levelno
                if levelno >= logging.CRITICAL:
                    color = '\033[31;1m'
                elif levelno >= logging.ERROR:
                    color = '\033[31;1m'
                elif levelno >= logging.WARNING:
                    color = '\033[33;1m'
                elif levelno >= logging.INFO:
                    color = '\033[32;1m'
                elif levelno >= logging.DEBUG:
                    color = '\033[36;1m'
                else:
                    color = '\033[0m'
                formatter = logging.Formatter('{0}%(asctime)s %(levelname)s [%(message)s]\033[0m'.format(color))
            hdlr.setFormatter(formatter)
            return fcn(*args)
        return func
    hdlr.emit = emit_decorator(hdlr.emit)
    logger.addHandler(hdlr)


setup_logger(logger)


def signal_handler(signal, frame):
    logger.warning('You pressed Ctrl+C! Exiting gracefully')
    sys.exit(1)


signal.signal(signal.SIGINT, signal_handler)


def exception_handler(function):
    @wraps(function)
    def new_funct(*args, **kwargs):
        try:
            return function(*args, **kwargs)
        except InvalidObject as error:
            logger.error(error)
            return error.error_code
        except DataIdentifierNotFound as error:
            logger.error(error)
            logger.debug('This means that the Data IDentifier you provided is not known by Rucio.')
            return error.error_code
        except AccessDenied as error:
            logger.error(error)
            logger.debug('This error is a permission issue. You cannot run this command with your account.')
            return error.error_code
        except DataIdentifierAlreadyExists as error:
            logger.error(error)
            logger.debug('This means that the data IDentifier you try to add is already registered in Rucio.')
            return error.error_code
        except RSENotFound as error:
            logger.error(error)
            logger.debug('This means that the Rucio Storage Element you provided is not known by Rucio.')
            return error.error_code
        except InvalidRSEExpression as error:
            logger.error(error)
            logger.debug('This means the RSE expression you provided is not syntactically correct.')
            return error.error_code
        except DuplicateContent as error:
            logger.error(error)
            logger.debug('This means that the DID you want to attach is already in the target DID.')
            return error.error_code
        except TypeError as error:
            logger.error(error)
            logger.debug('This means the parameter you passed has a wrong type.')
            return FAILURE
        except RuleNotFound as error:
            logger.error(error)
            logger.debug('This means the rule you specified does not exist.')
            return error.error_code
        except AccountNotFound as error:
            logger.error(error)
            logger.debug('This means that the specified account cannot be found.')
            return error.error_code
        except NotImplementedError as error:
            logger.error(error)
            logger.debug('This means that the method is not implemented yet.')
            return FAILURE
        except Duplicate as error:
            logger.error(error)
            logger.debug('This means that you are trying to add something that already exists.')
            return error.error_code
        except ReplicaIsLocked as error:
            logger.error(error)
            logger.error('This means that the replica has a lock and is therefore protected.')
            return FAILURE
        except ReplicaNotFound as error:
            logger.error(error)
            logger.error('This means that the replica does not exist.')
            return FAILURE
        except ConfigNotFound as error:
            logger.error(error)
            logger.error('This means that the configuration section you are looking for does not exist.')
            return FAILURE
        except ScopeNotFound as error:
            logger.error(error)
            logger.error('This means that no scopes were found for the specified account ID.')
            return FAILURE
        except Exception as error:
            logger.error(error)
            logger.error('Rucio exited with an unexpected/unknown error, please provide the traceback below to the developers.')
            logger.debug(traceback.format_exc())
            return FAILURE
    return new_funct


def get_client(args):
    """
    Returns a new client object.
    """
    if not args.auth_strategy:
        if 'RUCIO_AUTH_TYPE' in os.environ:
            auth_type = os.environ['RUCIO_AUTH_TYPE'].lower()
        else:
            try:
                auth_type = config_get('client', 'auth_type').lower()
            except (NoOptionError, NoSectionError):
                logger.error('Cannot get AUTH_TYPE')
                sys.exit(FAILURE)
    else:
        auth_type = args.auth_strategy.lower()

    if auth_type in ['userpass', 'saml'] and args.username is not None and args.password is not None:
        creds = {'username': args.username, 'password': args.password}
    elif auth_type == 'oidc':
        if args.oidc_issuer:
            args.oidc_issuer = args.oidc_issuer.lower()
        creds = {'oidc_auto': args.oidc_auto,
                 'oidc_scope': args.oidc_scope,
                 'oidc_audience': args.oidc_audience,
                 'oidc_polling': args.oidc_polling,
                 'oidc_refresh_lifetime': args.oidc_refresh_lifetime,
                 'oidc_issuer': args.oidc_issuer,
                 'oidc_username': args.oidc_username,
                 'oidc_password': args.oidc_password}
    else:
        creds = None

    try:
        client = Client(rucio_host=args.host, auth_host=args.auth_host,
                        account=args.issuer,
                        auth_type=auth_type, creds=creds,
                        ca_cert=args.ca_certificate, timeout=args.timeout, vo=args.vo)
    except CannotAuthenticate as error:
        logger.error(error)
        if 'alert certificate expired' in str(error):
            logger.error('The server certificate expired.')
        elif auth_type.lower() == 'x509_proxy':
            logger.error('Please verify that your proxy is still valid and renew it if needed.')
        sys.exit(FAILURE)
    return client


def get_scope(did, client):
    try:
        scope, name = extract_scope(did)
        return scope, name
    except TypeError:
        scopes = client.list_scopes()
        scope, name = extract_scope(did, scopes)
        return scope, name
    return None, did


@exception_handler
def add_account(args):
    """
    %(prog)s add [options] <field1=value1 field2=value2 ...>

    Adds a new account. Specify metadata fields as arguments.

    """
    client = get_client(args)
    client.add_account(account=args.account, type=args.accounttype, email=args.accountemail)
    print('Added new account: %s' % args.account)
    return SUCCESS


@exception_handler
def delete_account(args):
    """
    %(prog)s disable [options] <field1=value1 field2=value2 ...>

    Delete account.

    """
    client = get_client(args)
    client.delete_account(args.acnt)
    print('Deleted account: %s' % args.acnt)
    return SUCCESS


@exception_handler
def update_account(args):
    """
    %(prog)s update [options] <field1=value1 field2=value2 ...>

    Update an account.

    """
    client = get_client(args)
    client.update_account(account=args.account, key=args.key, value=args.value)
    print('%s of account %s changed to %s' % (args.key, args.account, args.value))
    return SUCCESS


@exception_handler
def ban_account(args):
    """
    %(prog)s ban [options] <field1=value1 field2=value2 ...>

    Ban an account.

    """
    client = get_client(args)
    client.update_account(account=args.account, key='status', value='SUSPENDED')
    print('Account %s banned' % args.account)
    return SUCCESS


@exception_handler
def unban_account(args):
    """
    %(prog)s unban [options] <field1=value1 field2=value2 ...>

    Unban a banned account.

    """
    client = get_client(args)
    client.update_account(account=args.account, key='status', value='ACTIVE')
    print('Account %s unbanned' % args.account)
    return SUCCESS


@exception_handler
def list_accounts(args):
    """
    %(prog)s list [options] <field1=value1 field2=value2 ...>

    List accounts.

    """
    client = get_client(args)
    filters = {}
    if args.filters:
        for key, value in [(_.split('=')[0], _.split('=')[1]) for _ in args.filters.split(',')]:
            filters[key] = value
    accounts = client.list_accounts(identity=args.identity, account_type=args.account_type, filters=filters)
    for account in accounts:
        print(account['account'])
    return SUCCESS


@exception_handler
def info_account(args):
    """
    %(prog)s show [options] <field1=value1 field2=value2 ...>

    Show extended information of a given account

    """
    client = get_client(args)
    info = client.get_account(account=args.account)
    for k in info:
        print(k.ljust(10) + ' : ' + str(info[k]))
    return SUCCESS


@exception_handler
def list_identities(args):
    """
    %(prog)s list-identities [options] <field1=value1 field2=value2 ...>

    List all identities on an account.
    """
    client = get_client(args)
    identities = client.list_identities(account=args.account)
    for identity in identities:
        print('Identity: %(identity)s,\ttype: %(type)s' % identity)
    return SUCCESS


@exception_handler
def set_limits(args):
    """
    %(prog)s set [options] <field1=value1 field2=value2 ...>

    Set account limit for an account and rse.
    """
    client = get_client(args)
    locality = args.locality.lower()
    byte_limit = None
    limit_input = args.bytes.lower()

    if limit_input == 'inf' or limit_input == 'infinity':
        byte_limit = -1
    else:
        byte_limit = get_bytes_value_from_string(limit_input)
        if not byte_limit:
            try:
                byte_limit = int(limit_input)
            except ValueError:
                logger.error('The limit could not be set. Either you misspelled infinity or your input could not be converted to integer or you used a wrong pattern. Please use a format like 10GB with B,KB,MB,GB,TB,PB as units (not case sensistive)')
                return FAILURE

    client.set_account_limit(account=args.account, rse=args.rse, bytes=byte_limit, locality=locality)
    print('Set account limit for account %s on RSE %s: %s' % (args.account, args.rse, sizefmt(byte_limit, True)))
    return SUCCESS


@exception_handler
def get_limits(args):
    """
    %(prog)s get-limits [options] <field1=value1 field2=value2 ...>

    Grant an identity access to an account.

    """
    client = get_client(args)
    locality = args.locality.lower()
    limits = client.get_account_limits(account=args.account, rse_expression=args.rse, locality=locality)
    for rse in limits:
        print('Quota on %s for %s : %s' % (rse, args.account, sizefmt(limits[rse], True)))
    return SUCCESS


@exception_handler
def delete_limits(args):
    """
    %(prog)s delete [options] <field1=value1 field2=value2 ...>

    Delete account limit for an account and rse.
    """
    client = get_client(args)
    client.delete_account_limit(account=args.account, rse=args.rse, locality=args.locality)
    print('Deleted account limit for account %s and RSE %s' % (args.account, args.rse))
    return SUCCESS


@exception_handler
def identity_add(args):
    """
    %(prog)s del [options] <field1=value1 field2=value2 ...>

    Grant an identity access to an account.

    """
    client = get_client(args)
    if args.email == "":
        logger.error('Error: --email argument can\'t be an empty string. Failed to grant an identity access to an account')
        return FAILURE

    if args.authtype == 'USERPASS' and not args.password:
        logger.error('missing --password argument')
        return FAILURE

    client.add_identity(account=args.account, identity=args.identity, authtype=args.authtype, email=args.email, password=args.password)
    print('Added new identity to account: %s-%s' % (args.identity, args.account))
    return SUCCESS


@exception_handler
def identity_delete(args):
    """
    %(prog)s delete [options] <field1=value1 field2=value2 ...>

    Revoke an identity's access to an account.

    """
    client = get_client(args)
    client.del_identity(args.account, args.identity, authtype=args.authtype)
    print('Deleted identity: %s' % args.identity)
    return SUCCESS


@exception_handler
def add_rse(args):
    """
    %(prog)s add [options] <field1=value1 field2=value2 ...>

    Adds a new RSE. Specify metadata fields as arguments.

    """
    client = get_client(args)
    client.add_rse(args.rse, deterministic=not args.non_deterministic)
    print('Added new %sdeterministic RSE: %s' % ('non-' if args.non_deterministic else '', args.rse))
    return SUCCESS


@exception_handler
def disable_rse(args):
    """
    %(prog)s del [options] <field1=value1 field2=value2 ...>

    Disable RSE.

    """
    client = get_client(args)
    client.delete_rse(args.rse)
    return SUCCESS


@exception_handler
def list_rses(args):
    """
    %(prog)s list [options] <field1=value1 field2=value2 ...>

    List RSEs.

    """
    client = get_client(args)
    rses = client.list_rses()
    for rse in rses:
        print('%(rse)s' % rse)
    return SUCCESS


@exception_handler
def update_rse(args):
    """
    %(prog)s update [options] <field1=value1 field2=value2 ...>

    Update the settings of the RSE:
      deterministic, rse_type, staging_are, volatile, qos_class,
      availability_delete, availability_read, availability_write,
      city, country_name, latitude, longitude, region_code, time_zone

    Use '', 'None' or 'null' to wipe the value of following RSE settings:
      qos_class
    """
    client = get_client(args)
    if args.value in ['true', 'True', 'TRUE', '1']:
        args.value = True
    if args.value in ['false', 'False', 'FALSE', '0']:
        args.value = False
    params = {args.param: args.value}
    client.update_rse(args.rse, parameters=params)

    if isinstance(args.value, bool):
        args.value = str(args.value)

    print('Updated RSE %s settings %s to %s' % (args.rse, args.param, args.value if args.value.lower() not in ['', 'none', 'null'] else '[WIPED]'))
    return SUCCESS


@exception_handler
def info_rse(args):
    """
    %(prog)s info [options] <field1=value1 field2=value2 ...>

    Show extended information of a given RSE

    """
    client = get_client(args)
    rseinfo = client.get_rse(rse=args.rse)
    attributes = client.list_rse_attributes(rse=args.rse)
    usage = client.get_rse_usage(rse=args.rse)
    print('Settings:')
    print('=========')
    for key in sorted(rseinfo):
        if key != 'protocols':
            print('  ' + key + ': ' + str(rseinfo[key]))
    print('Attributes:')
    print('===========')
    for attribute in sorted(attributes):
        print('  ' + attribute + ': ' + str(attributes[attribute]))
    print('Protocols:')
    print('==========')
    for protocol in sorted(rseinfo['protocols'], key=lambda x: x['scheme']):
        print('  ' + protocol['scheme'])
        for item in sorted(protocol):
            if item == 'domains':
                print('    ' + item + ': \'' + json.dumps(protocol[item]) + '\'')
            else:
                print('    ' + item + ': ' + str(protocol[item]))
    print('Usage:')
    print('======')
    for elem in sorted(usage, key=lambda x: x['source']):
        print('  ' + elem['source'])
        for item in sorted(elem):
            print('    ' + item + ': ' + str(elem[item]))

    return SUCCESS


@exception_handler
def set_attribute_rse(args):
    """
    %(prog)s set-attribute [options] <field1=value1 field2=value2 ...>

    Set RSE attributes.

    """
    client = get_client(args)
    client.add_rse_attribute(rse=args.rse, key=args.key, value=args.value)
    print('Added new RSE attribute for %s: %s-%s ' % (args.rse, args.key, args.value))
    return SUCCESS


@exception_handler
def get_attribute_rse(args):
    """
    %(prog)s get-attribute [options] <field1=value1 field2=value2 ...>

    Get RSE attributes.

    """
    client = get_client(args)
    attributes = client.list_rse_attributes(rse=args.rse)
    for k in attributes:
        print(k + ': ' + str(attributes[k]))

    return SUCCESS


@exception_handler
def delete_attribute_rse(args):
    """
    %(prog)s delete-attribute [options] <field1=value1 field2=value2 ...>

    Delete RSE attributes.

    """
    client = get_client(args)
    client.delete_rse_attribute(rse=args.rse, key=args.key)
    print('Deleted RSE attribute for %s: %s-%s ' % (args.rse, args.key, args.value))
    return SUCCESS


@exception_handler
def add_distance_rses(args):
    """
    %(prog)s add-distance [options] SOURCE_RSE DEST_RSE

    Set the distance between two RSEs.
    """
    client = get_client(args)
    params = {'ranking': args.ranking, 'distance': args.distance}
    client.add_distance(args.source, args.destination, params)
    print('Set distance from %s to %s to %d with ranking %d' % (args.source, args.destination, args.distance, args.ranking))
    return SUCCESS


@exception_handler
def get_distance_rses(args):
    """
    %(prog)s get-distance SOURCE_RSE DEST_RSE

    Retrieve the existing distance information between two RSEs.
    """
    client = get_client(args)
    distance_info = client.get_distance(args.source, args.destination)
    if distance_info:
        print('Distance information from %s to %s: distance=%d, ranking=%d' % (args.source, args.destination, distance_info[0]['distance'], distance_info[0]['ranking']))
    else:
        print("No distance set from %s to %s" % (args.source, args.destination))
    return SUCCESS


@exception_handler
def update_distance_rses(args):
    """
    %(prog)s update-distance [options] SOURCE_RSE DEST_RSE

    Update the existing distance entry between two RSEs.
    """
    client = get_client(args)
    params = {}
    if args.ranking is not None:
        params['ranking'] = args.ranking
    if args.distance is not None:
        params['distance'] = args.distance
    client.update_distance(args.source, args.destination, params)
    print('Update distance information from %s to %s:' % (args.source, args.destination))
    if args.distance is not None:
        print("- Distance set to %d" % args.distance)
    if args.ranking is not None:
        print("- Ranking set to %d" % args.ranking)
    return SUCCESS


@exception_handler
def add_protocol_rse(args):
    """
    %(prog)s add-protocol-rse [options] <rse>

    Add a new protocol handler for an RSE
    """
    client = get_client(args)
    proto = {'hostname': args.hostname,
             'scheme': args.scheme,
             'port': args.port,
             'impl': args.impl,
             'prefix': args.prefix}
    if args.domain_json:
        proto['domains'] = args.domain_json
    proto.setdefault('extended_attributes', {})
    if args.ext_attr_json:
        proto['extended_attributes'] = args.ext_attr_json
    if proto['scheme'] == 'srm' and not args.web_service_path:
        print('Error: space-token and web-service-path must be provided for SRM endpoints.')
        return FAILURE
    if args.space_token:
        proto['extended_attributes']['space_token'] = args.space_token
    if args.web_service_path:
        proto['extended_attributes']['web_service_path'] = args.web_service_path
    # Rucio 1.14.1 chokes on an empty extended_attributes key.
    if not proto['extended_attributes']:
        del proto['extended_attributes']
    client.add_protocol(args.rse, proto)
    return SUCCESS


@exception_handler
def del_protocol_rse(args):
    """
    %(prog)s delete-protocol-rse [options] <rse>

    Remove a protocol handler for a RSE
    """
    client = get_client(args)
    kwargs = {}
    if args.port:
        kwargs['port'] = args.port
    if args.hostname:
        kwargs['hostname'] = args.hostname
    client.delete_protocols(args.rse, args.scheme, **kwargs)


@exception_handler
def add_qos_policy(args):
    """
    %(prog)s add-qos-policy <rse> <qos_policy>

    Add a QoS policy to an RSE.
    """
    client = get_client(args)
    client.add_qos_policy(args.rse, args.qos_policy)
    print('Added QoS policy to RSE %s: %s' % (args.rse, args.qos_policy))
    return SUCCESS


@exception_handler
def delete_qos_policy(args):
    """
    %(prog)s delete-qos-policy <rse> <qos_policy>

    Delete a QoS policy from an RSE.
    """
    client = get_client(args)
    client.delete_qos_policy(args.rse, args.qos_policy)
    print('Deleted QoS policy from RSE %s: %s' % (args.rse, args.qos_policy))
    return SUCCESS


@exception_handler
def list_qos_policies(args):
    """
    %(prog)s list-qos-policies <rse>

    List all QoS policies of an RSE.
    """
    client = get_client(args)
    qos_policies = client.list_qos_policies(args.rse)
    for qos_policy in sorted(qos_policies):
        print(qos_policy)
    return SUCCESS


@exception_handler
def add_scope(args):
    """
    %(prog)s add [options] <field1=value1 field2=value2 ...>

    Add scope.

    """
    client = get_client(args)
    client.add_scope(account=args.account, scope=args.scope)
    print('Added new scope to account: %s-%s' % (args.scope, args.account))
    return SUCCESS


@exception_handler
def list_scopes(args):
    """
    %(prog)s list [options] <field1=value1 field2=value2 ...>

    List scopes.

    """
    client = get_client(args)
    if args.account:
        scopes = client.list_scopes_for_account(args.account)
    else:
        scopes = client.list_scopes()
    for scope in scopes:
        if 'mock' not in scope:
            print(scope)
    return SUCCESS


@exception_handler
def get_config(args):
    """
    %(prog)s get [options] <field1=value1 field2=value2 ...>

    Get the configuration. Either everything, or matching the given section/option.
    """
    client = get_client(args)
    res = client.get_config(section=args.section, option=args.option)
    if not isinstance(res, dict):
        print('[%s]\n%s=%s' % (args.section, args.option, str(res)))
    else:
        print_header = True
        for i in list(res.keys()):
            if print_header:
                if args.section is not None:
                    print('[%s]' % args.section)
                else:
                    print('[%s]' % i)
            if not isinstance(res[i], dict):
                print('%s=%s' % (i, str(res[i])))
                print_header = False
            else:
                for j in list(res[i].keys()):
                    print('%s=%s' % (j, str(res[i][j])))
    return SUCCESS


@exception_handler
def set_config_option(args):
    """
    %(prog)s set [options] <field1=value1 field2=value2 ...>

    Set the configuration value for a matching section/option. Missing section/option will be created.
    """
    client = get_client(args)
    client.set_config_option(section=args.section, option=args.option, value=args.value)
    print('Set configuration: %s.%s=%s' % (args.section, args.option, args.value))
    return SUCCESS


@exception_handler
def delete_config_option(args):
    """
    %(prog)s delete [options] <field1=value1 field2=value2 ...>

    Delete a configuration option from a section
    """
    client = get_client(args)
    if client.delete_config_option(section=args.section, option=args.option):
        print('Deleted section \'%s\' option \'%s\'' % (args.section, args.option))
    else:
        print('Section \'%s\' option \'%s\' not found' % (args.section, args.option))
    return SUCCESS


@exception_handler
def add_subscription(args):
    """
    %(prog)s add [options] name Filter replication_rules

    Add subscription.

    """
    client = get_client(args)
    if args.subs_account:
        account = args.subs_account
    elif args.issuer:
        account = args.issuer
    else:
        account = client.account
    subscription_id = client.add_subscription(name=args.name, account=account, filter=json.loads(args.filter), replication_rules=json.loads(args.replication_rules),
                                              comments=args.comments, lifetime=args.lifetime, retroactive=False, dry_run=False, priority=args.priority)
    print('Subscription added %s' % (subscription_id))
    return SUCCESS


@exception_handler
def list_subscriptions(args):
    """
    %(prog)s list [options] [name]

    List subscriptions.

    """
    client = get_client(args)
    if args.subs_account:
        account = args.subs_account
    elif args.issuer:
        account = args.issuer
    else:
        account = client.account
    subs = client.list_subscriptions(name=args.name, account=account)
    for sub in subs:
        if args.long:
            print('\n'.join('%s: %s' % (str(k), str(v)) for (k, v) in list(sub.items())))
            print()
        else:
            print("%s: %s %s\n  priority: %s\n  filter: %s\n  rules: %s\n  comments: %s" % (sub['account'], sub['name'], sub['state'], sub['policyid'], sub['filter'], sub['replication_rules'], sub.get('comments', '')))
    return SUCCESS


@exception_handler
def update_subscription(args):
    """
    %(prog)s update [options] name filter replication_rules

    Update a subscription.

    """
    client = get_client(args)
    if args.subs_account:
        account = args.subs_account
    elif args.issuer:
        account = args.issuer
    else:
        account = client.account
    client.update_subscription(name=args.name, account=account, filter=json.loads(args.filter), replication_rules=json.loads(args.replication_rules),
                               comments=args.comments, lifetime=args.lifetime, retroactive=False, dry_run=False, priority=args.priority)
    return SUCCESS


@exception_handler
def reevaluate_did_for_subscription(args):
    """
    %(prog)s reevaulate [options] dids

    Reevaluate a list of DIDs against all active subscriptions.

    """
    client = get_client(args)
    for did in args.dids.split(','):
        scope, name = get_scope(did, client)
        client.set_metadata(scope, name, 'is_new', True)
    return SUCCESS


@exception_handler
def list_account_attributes(args):
    """
    %(prog)s show [options] <field1=value1 field2=value2 ...>

    List the attributes for an account.

    """
    client = get_client(args)
    account = args.account or client.account
    attributes = next(client.list_account_attributes(account))
    table = []
    for attr in attributes:
        table.append([attr['key'], attr['value']])
    print(tabulate.tabulate(table, tablefmt=tablefmt, headers=['Key', 'Value']))
    return SUCCESS


@exception_handler
def add_account_attribute(args):
    """
    %(prog)s show [options] <field1=value1 field2=value2 ...>

    Add attribute for an account.

    """
    client = get_client(args)
    client.add_account_attribute(account=args.account, key=args.key, value=args.value)
    return SUCCESS


@exception_handler
def delete_account_attribute(args):
    """
    %(prog)s show [options] <field1=value1 field2=value2 ...>

    Delete attribute for an account.

    """
    client = get_client(args)
    client.delete_account_attribute(account=args.account, key=args.key)
    return SUCCESS


@exception_handler
def declare_bad_file_replicas(args):
    """
    %(prog)s show [options] <field1=value1 field2=value2 ...>

    Declare a list of bad replicas.

    """
    client = get_client(args)
    bad_files = []
    if args.inputfile:
        with open(args.inputfile) as infile:
            for line in infile:
                bad_file = line.rstrip('\n')
                if bad_file != '':
                    bad_files.append(bad_file)
    else:
        bad_files = args.listbadfiles

    # Interpret filenames not in scheme://* format as LFNs and convert them to PFNs
    bad_files_pfns = []
    for bad_file in bad_files:
        if bad_file.find('://') == -1:
            scope, name = get_scope(bad_file, client)
            did_info = client.get_did(scope, name)
            if did_info['type'].upper() != 'FILE' and not args.allow_collection:
                print('DID %s:%s is a collection and --allow-collection was not specified.' % (scope, name))
                return FAILURE
            replicas = [replica for rep in client.list_replicas([{'scope': scope, 'name': name}])
                        for replica in list(rep['pfns'].keys())]
            bad_files_pfns.extend(replicas)
        else:
            bad_files_pfns.append(bad_file)
    if args.verbose:
        print("PFNs that will be declared bad:")
        for pfn in bad_files_pfns:
            print(pfn)

    chunk_size = 10000
    tot_files = len(bad_files)
    cnt = 0
    nchunk = math.ceil(tot_files / chunk_size)
    for chunk in chunks(bad_files_pfns, chunk_size):
        cnt += 1
        client.add_bad_pfns(pfns=chunk, reason=args.reason, state='BAD', expires_at=None)
        ndeclared = len(chunk)
        print('Chunk %s/%s : %s replicas successfully declared' % (int(cnt), int(nchunk), ndeclared))
    print('--------------------------------')
    print('Summary')
    print('%s replicas successfully declared' % tot_files)

    return SUCCESS


@exception_handler
def declare_temporary_unavailable_replicas(args):
    """
    %(prog)s show [options] <field1=value1 field2=value2 ...>

    Declare a list of temporary unavailable replicas.

    """
    client = get_client(args)
    bad_files = []
    if args.inputfile:
        with open(args.inputfile) as infile:
            for line in infile:
                bad_file = line.rstrip('\n')
                if '://' not in bad_file:
                    print('%s is not a valid PFN. Aborting', bad_file)
                    return FAILURE
                if bad_file != '':
                    bad_files.append(bad_file)
    else:
        bad_files = args.listbadfiles

    if args.expiration_date is not None:
        expiration_date = (datetime.datetime.utcnow() + datetime.timedelta(hours=args.expiration_date)).isoformat()

    chunk_size = 10000
    tot_files = len(bad_files)
    cnt = 0
    nchunk = math.ceil(tot_files / chunk_size)
    for chunk in chunks(bad_files, chunk_size):
        cnt += 1
        client.add_bad_pfns(pfns=chunk, reason=args.reason, state='TEMPORARY_UNAVAILABLE', expires_at=expiration_date)
        ndeclared = len(chunk)
        print('Chunk %s/%s : %s replicas successfully declared' % (int(cnt), int(nchunk), ndeclared))
    print('--------------------------------')
    print('Summary')
    print('%s replicas successfully declared' % tot_files)

    return SUCCESS


@exception_handler
def list_pfns(args):
    """
    %(prog)s list [options] <field1=value1 field2=value2 ...>

    List the possible PFN for a file at a site.

    """
    client = get_client(args)
    dids = args.dids.split(',')
    rse = args.rse
    protocol = args.protocol
    for input_did in dids:
        scope, name = get_scope(input_did, client)
        replicas = [rep for rep in client.list_replicas([{'scope': scope, 'name': name}, ], schemes=[protocol, ])]
        if rse in replicas[0]['rses'] and replicas[0]['rses'][rse]:
            print(replicas[0]['rses'][rse][0])
        else:
            logger.warning('The file has no replica on the specified RSE')
            rse_info = rsemgr.get_rse_info(rse, vo=client.vo)
            proto = rsemgr.create_protocol(rse_info, 'read', scheme=protocol)
            try:
                pfn = proto.lfns2pfns(lfns={'scope': scope, 'name': name})
                result = list(pfn.values())[0]
            except ReplicaNotFound as error:
                result = error
            if isinstance(result, (RSEOperationNotSupported, ReplicaNotFound)):
                if not rse_info['deterministic']:
                    logger.warning('This is a non-deterministic site, so the real PFN might be different from the on suggested')
                    rse_attr = client.list_rse_attributes(rse)
                    naming_convention = rse_attr.get('naming_convention', None)
                    parents = [did for did in client.list_parent_dids(scope, name)]
                    if len(parents) > 1:
                        logger.warning('The file has multiple parents')
                    for did in parents:
                        if did['type'] == 'DATASET':
                            path = construct_surl(did['name'], name, naming_convention=naming_convention)
                            pfn = ''.join([proto.attributes['scheme'],
                                           '://',
                                           proto.attributes['hostname'],
                                           ':',
                                           str(proto.attributes['port']),
                                           proto.attributes['prefix'],
                                           path if not path.startswith('/') else path[1:]])
                            print(pfn)
                else:
                    logger.error('Unexpected error')
                    return FAILURE
            else:
                print(result)
    return SUCCESS


@exception_handler
def import_data(args):
    """
    %(prog)s list [options] <field1=value1 field2=value2 ...>

    Import data from JSON file to Rucio.

    """
    client = get_client(args)
    import_file_path = args.file_path
    data = None
    print('Start reading file.')
    try:
        with open(import_file_path) as import_file:
            data_string = import_file.read()
            data = parse_response(data_string)
    except ValueError as error:
        print('There was problem with decoding your file.')
        print(error)
        return FAILURE
    except IOError as error:
        print('There was a problem with reading your file.')
        print(error)
        return FAILURE

    if data:
        client.import_data(data)
        print('Data successfully imported.')
        return SUCCESS
    else:
        print('Nothing to import.')
        return FAILURE


@exception_handler
def export_data(args):
    """
    %(prog)s list [options] <field1=value1 field2=value2 ...>

    Export data from Rucio to JSON file.

    """
    client = get_client(args)
    destination_file_path = args.file_path
    print('Start querying data.')
    data = client.export_data()
    try:
        with open(destination_file_path, 'w+') as destination_file:
            destination_file.write(render_json(**data))
            print('File successfully written.')
        print('Data successfully exported to %s' % args.file_path)
        return SUCCESS
    except IOError as error:
        print('There was a problem with reading your file.')
        print(error)
        return FAILURE


@exception_handler
def set_tombstone(args):
    """
    %(prog)s list [options] <field1=value1 field2=value2 ...>

    Set a tombstone on a list of replicas.
    """
    client = get_client(args)
    dids = args.dids
    rse = args.rse
    dids = [dids] if ',' not in dids else dids.split(',')
    dids = [{'scope': did.split(':')[0], 'name': did.split(':')[1], 'rse': rse} for did in dids]
    client.set_tombstone(dids)
    logger.info('Set tombstone successfully on: %s' % args.dids)
    return SUCCESS


def get_parser():
    """
    Returns the argparse parser.
    """
    oparser = argparse.ArgumentParser(prog=os.path.basename(sys.argv[0]), add_help=True)
    subparsers = oparser.add_subparsers()

    # Main arguments
    oparser.add_argument('--version', action='version', version='%(prog)s ' + version.version_string())
    oparser.add_argument('--verbose', '-v', default=False, action='store_true', help="Print more verbose output")
    oparser.add_argument('-H', '--host', dest="host", metavar="ADDRESS", help="The Rucio API host")
    oparser.add_argument('--auth-host', dest="auth_host", metavar="ADDRESS", help="The Rucio Authentication host")
    oparser.add_argument('-a', '--account', dest="issuer", metavar="ACCOUNT", help="Rucio account to use")
    oparser.add_argument('-S', '--auth-strategy', dest="auth_strategy", default=None, help="Authentication strategy (userpass, x509, ssh ...)")
    oparser.add_argument('-T', '--timeout', dest="timeout", type=float, default=None, help="Set all timeout values to SECONDS")
    oparser.add_argument('--vo', dest="vo", metavar="VO", default=None, help="VO to authenticate at. Only used in multi-VO mode.")

    # Options for the userpass auth_strategy
    oparser.add_argument('-u', '--user', dest='username', default=None, help='username')
    oparser.add_argument('-pwd', '--password', dest='password', default=None, help='password')
    # Options for defining the OIDC scope# Options for defining remaining OIDC parameters
    oparser.add_argument('--oidc-user', dest='oidc_username', default=None, help='OIDC username')
    oparser.add_argument('--oidc-password', dest='oidc_password', default=None, help='OIDC password')
    oparser.add_argument('--oidc-scope', dest='oidc_scope', default='openid profile', help='Defines which (OIDC) information user will share with Rucio. '
                         + 'Rucio requires at least -sc="openid profile". To request refresh token for Rucio, scope must include "openid offline_access" and '  # NOQA: W503
                         + 'there must be no active access token saved on the side of the currently used Rucio Client.')  # NOQA: W503
    oparser.add_argument('--oidc-audience', dest='oidc_audience', default=None, help='Defines which audience are tokens requested for.')
    oparser.add_argument('--oidc-auto', dest='oidc_auto', default=False, action='store_true', help='If not specified, username and password credentials are not required and users will be given a URL '
                         + 'to use in their browser. If specified, the users explicitly trust Rucio with their IdP credentials.')  # NOQA: W503
    oparser.add_argument('--oidc-polling', dest='oidc_polling', default=False, action='store_true', help='If not specified, user will be asked to enter a code returned by the browser to the command line. '
                         + 'If --polling is set, Rucio Client should get the token without any further interaction of the user. This option is active only if --auto is *not* specified.')  # NOQA: W503
    oparser.add_argument('--oidc-refresh-lifetime', dest='oidc_refresh_lifetime', default=None, help='Max lifetime in hours for this an access token will be refreshed by asynchronous Rucio daemon. '
                         + 'If not specified, refresh will be stopped after 4 days. This option is effective only if --oidc-scope includes offline_access scope for a refresh token to be granted to Rucio.')  # NOQA: W503
    oparser.add_argument('--oidc-issuer', dest='oidc_issuer', default=None,
                         help='Defines which Identity Provider is goign to be used. The issuer string must correspond '
                         + 'to the keys configured in the /etc/idpsecrets.json auth server configuration file.')  # NOQA: W503

    # Options for the x509  auth_strategy
    oparser.add_argument('--certificate', dest='certificate', default=None, help='Client certificate file')
    oparser.add_argument('--ca-certificate', dest='ca_certificate', default=None, help='CA certificate to verify peer against (SSL)')

    # The import export subparser
    data_parser = subparsers.add_parser('data', help='Import and export data')
    data_subparsers = data_parser.add_subparsers()

    # The import command
    import_parser = data_subparsers.add_parser('import',
                                               help='Import data to Rucio from JSON file.',
                                               formatter_class=argparse.RawDescriptionHelpFormatter,
                                               epilog='Usage example\n'
                                                      '"""""""""""""\n'
                                                      'Import data from the file file.json::\n'
                                                      '\n'
                                                      '    $ rucio-admin data import file.json\n'
                                                      '\n')
    import_parser.add_argument('file_path', action='store', help='File path.')
    import_parser.set_defaults(which='import')

    # The export command
    export_parser = data_subparsers.add_parser('export',
                                               help='Export data from  Rucio to JSON file.',
                                               formatter_class=argparse.RawDescriptionHelpFormatter,
                                               epilog='Usage example\n'
                                                      '"""""""""""""\n'
                                                      'Export data to the file file.json::\n'
                                                      '\n'
                                                      '    $ rucio-admin data export file.json\n'
                                                      '\n')
    export_parser.add_argument('file_path', action='store', help='File path.')
    export_parser.set_defaults(which='export')

    # The account subparser
    account_parser = subparsers.add_parser('account', help='Account methods')
    account_subparser = account_parser.add_subparsers()

    # The list_accounts command
    list_account_parser = account_subparser.add_parser('list',
                                                       help='List Rucio accounts.',
                                                       formatter_class=argparse.RawDescriptionHelpFormatter,
                                                       epilog='Usage example\n'
                                                              '"""""""""""""\n'
                                                              '::\n'
                                                              '\n'
                                                              '    $ rucio-admin account list --type \'user\'\n'
                                                              '\n')
    list_account_parser.add_argument('--type', dest='account_type', action='store', help='Account Type (USER, GROUP, SERVICE)')
    list_account_parser.add_argument('--id', dest='identity', action='store', help='Identity (e.g. DN)')
    list_account_parser.add_argument('--filters', dest='filters', action='store', help='Filter arguments in form `key=value,another_key=next_value`')
    list_account_parser.set_defaults(which='list_accounts')

    # The list_account_attributes command
    list_attr_parser = account_subparser.add_parser('list-attributes',
                                                    help='List attributes for an account.',
                                                    formatter_class=argparse.RawDescriptionHelpFormatter,
                                                    epilog='Usage example\n'
                                                           '"""""""""""""\n'
                                                           '::\n'
                                                           '\n'
                                                           '    $ rucio-admin account list-attributes jdoe\n'
                                                           '    +-------+---------+\n'
                                                           '    | Key   | Value   |\n'
                                                           '    |-------+---------|\n'
                                                           '    | admin | False   |\n'
                                                           '    +-------+---------+\n'
                                                           '\n'
                                                           'Note: this table empty in most cases.\n'
                                                           '\n')
    list_attr_parser.add_argument('account', action='store', help='Account name')
    list_attr_parser.set_defaults(which='list_account_attributes')

    # The add_account_attribute command
    add_attr_parser = account_subparser.add_parser('add-attribute',
                                                   help='Add attribute for an account.',
                                                   formatter_class=argparse.RawDescriptionHelpFormatter,
                                                   epilog='Usage example\n'
                                                          '"""""""""""""\n'
                                                          '::\n'
                                                          '\n'
                                                          '    $ rucio-admin account add-attribute --key \'test\' --value true jdoe\n'
                                                          '\n'
                                                          'Note: no printed stdout.\n'
                                                          '\n')
    add_attr_parser.add_argument('account', action='store', help='Account name')
    add_attr_parser.add_argument('--key', dest='key', action='store', help='Attribute key', required=True)
    add_attr_parser.add_argument('--value', dest='value', action='store', help='Attribute value', required=True)
    add_attr_parser.set_defaults(which='add_account_attribute')

    # The delete_account_attribute command
    delete_attr_parser = account_subparser.add_parser('delete-attribute',
                                                      help='Delete attribute for an account.',
                                                      formatter_class=argparse.RawDescriptionHelpFormatter,
                                                      epilog='Usage example\n'
                                                             '"""""""""""""\n'
                                                             '::\n'
                                                             '\n'
                                                             '   $ rucio-admin account delete-attribute --key \'test\' jdoe\n'
                                                             '\n'
                                                             'Note: no printed stdout.\n'
                                                             '\n')
    delete_attr_parser.add_argument('account', action='store', help='Account name')
    delete_attr_parser.add_argument('--key', dest='key', action='store', help='Attribute key', required=True)
    delete_attr_parser.set_defaults(which='delete_account_attribute')

    # The add_account command
    add_account_parser = account_subparser.add_parser('add',
                                                      help='Add Rucio account.',
                                                      formatter_class=argparse.RawDescriptionHelpFormatter,
                                                      epilog='Usage example\n'
                                                             '"""""""""""""\n'
                                                             '::\n'
                                                             '\n'
                                                             '    $ rucio-admin account add jdoe-sister\n'
                                                             '    Added new account: jdoe-sister\n'
                                                             '\n')
    add_account_parser.set_defaults(which='add_account')
    add_account_parser.add_argument('account', action='store', help='Account name')
    add_account_parser.add_argument('--type', dest='accounttype', default='USER', help='Account Type (USER, GROUP, SERVICE)')
    add_account_parser.add_argument('--email', dest='accountemail', action='store',
                                    help='Email address associated with the account')

    # The disable_account command
    delete_account_parser = account_subparser.add_parser('delete',
                                                         help='Delete Rucio account.',
                                                         formatter_class=argparse.RawDescriptionHelpFormatter,
                                                         epilog='Usage example\n'
                                                                '"""""""""""""\n'
                                                                '::\n'
                                                                '\n'
                                                                '    $ rucio-admin account delete jdoe-sister\n'
                                                                '    Deleted account: jdoe-sister\n'
                                                                '\n')
    delete_account_parser.set_defaults(which='delete_account')
    delete_account_parser.add_argument('acnt', action='store', help='Account name')

    # The info_account command
    info_account_parser = account_subparser.add_parser('info',
                                                       help='Show detailed information about an account.',
                                                       formatter_class=argparse.RawDescriptionHelpFormatter,
                                                       epilog='Usage example\n'
                                                              '"""""""""""""\n'
                                                              '::\n'
                                                              '\n'
                                                              '    $ rucio-admin account info jdoe\n'
                                                              '    status     : ACTIVE\n'
                                                              '    account    : jdoe\n'
                                                              '    account_type : SERVICE\n'
                                                              '    created_at : 2015-02-03T15:51:16\n'
                                                              '    suspended_at : None\n'
                                                              '    updated_at : 2015-02-03T15:51:16\n'
                                                              '    deleted_at : None\n'
                                                              '    email      : None\n'
                                                              '\n')
    info_account_parser.set_defaults(which='info_account')
    info_account_parser.add_argument('account', action='store', help='Account name')

    # The list_account_identities command
    list_account_identities_parser = account_subparser.add_parser('list-identities',
                                                                  help='List all identities (DNs) on an account.',
                                                                  formatter_class=argparse.RawDescriptionHelpFormatter,
                                                                  epilog='Usage example\n'
                                                                         '"""""""""""""\n'
                                                                         '::\n'
                                                                         '\n'
                                                                         '    $ rucio-admin account list-identities jdoe\n'
                                                                         '    Identity: /C=DE/O=GermanGrid/OU=Desy/CN=Joe Doe, type: X509\n'
                                                                         '    Identity: jdoe@CERN.CH,  type: GSS\n'
                                                                         '    Identity: /DC=ch/DC=cern/OU=Organic Units/OU=Users/CN=jdoe/CN=707654/CN=Joe Doe, type: X509\n'
                                                                         '\n')
    list_account_identities_parser.set_defaults(which='list_identities')
    list_account_identities_parser.add_argument('account', action='store', help='Account name')

    # The set-limits command
    set_account_limits_parser = account_subparser.add_parser('set-limits',
                                                             help='Set the limits for the provided account at given RSE.',
                                                             formatter_class=argparse.RawDescriptionHelpFormatter,
                                                             epilog='Usage example\n'
                                                                    '"""""""""""""\n'
                                                                    '::\n'
                                                                    '\n'
                                                                    '    $ rucio-admin account set-limits jdoe DESY-ZN_DATADISK 1000000000000\n'
                                                                    '    Set account limit for account jdoe on RSE DESY-ZN_DATADISK: 1.000 TB\n'
                                                                    '\n'
                                                                    'Note: the order of perameters is fixed: account, rse, bytes.\n'
                                                                    '\n')
    set_account_limits_parser.set_defaults(which='set_limits')
    set_account_limits_parser.add_argument('account', action='store', help='Account name')
    set_account_limits_parser.add_argument('rse', action='store', help='RSE boolean expression')
    set_account_limits_parser.add_argument('bytes', action='store', help='Value can be specified in bytes ("10000"), with a storage unit ("10GB"), or "infinity"')
    set_account_limits_parser.add_argument('locality', action='store', nargs='?', default='local', choices=['local', 'global'], help='Global or local limit scope. Default: "local"')

    # The account-limit subparser
    get_account_limits_parser = account_subparser.add_parser('get-limits',
                                                             help='To get the account limits on an RSE.',
                                                             formatter_class=argparse.RawDescriptionHelpFormatter,
                                                             epilog='Usage example\n'
                                                                    '"""""""""""""\n'
                                                                    '::\n'
                                                                    '\n'
                                                                    '    $ rucio-admin account get-limits jdoe DESY-ZN_DATADISK\n'
                                                                    '    Quota on DESY-ZN_DATADISK for jdoe : 1.000 TB\n'
                                                                    'Note: the order of parameters is fixed: account, rse.\n'
                                                                    '\n')
    get_account_limits_parser.set_defaults(which='get_limits')
    get_account_limits_parser.add_argument('account', action='store', help='Account name')
    get_account_limits_parser.add_argument('rse', action='store', help='The RSE name')
    get_account_limits_parser.add_argument('locality', action='store', nargs='?', default='local', choices=['local', 'global'], help='Global or local limit scope. Default: "local"')

    # The delete_quota command
    delete_account_limits_parser = account_subparser.add_parser('delete-limits',
                                                                help='Delete limites for an account at given RSE.',
                                                                formatter_class=argparse.RawDescriptionHelpFormatter,
                                                                epilog='Usage example\n'
                                                                       '"""""""""""""\n'
                                                                       '::\n'
                                                                       '\n'
                                                                       '    $ rucio-admain account delete-limits jdoe DESY-ZN_DATADISK\n'
                                                                       '    Deleted account limit for account jdoe and RSE DESY-ZN_DATADISK\n'
                                                                       '\n'
                                                                       'Note: the order of parameters is fixed: account, rse.\n'
                                                                       '\n')
    delete_account_limits_parser.set_defaults(which='delete_limits')
    delete_account_limits_parser.add_argument('account', action='store', help='Account name')
    delete_account_limits_parser.add_argument('rse', action='store', help='RSE name')
    delete_account_limits_parser.add_argument('locality', action='store', nargs='?', default='local', choices=['local', 'global'], help='Global or local limit scope. Default: "local"')

    # Ban/unban operations not implemented yet
    ban_account_limits_parser = account_subparser.add_parser('ban',
                                                             help='Disable an account.',
                                                             formatter_class=argparse.RawDescriptionHelpFormatter,
                                                             epilog='Usage example\n'
                                                                    '"""""""""""""\n'
                                                                    '::\n'
                                                                    '\n'
                                                                    '    $ rucio-admin account ban --account jdoe\n'
                                                                    '    Account jdoe banned\n'
                                                                    '\n'
                                                                    'Note: in case of accidental ban, use unban.\n'
                                                                    'CAUTION: the account is completely disabled.\n'
                                                                    '\n')
    ban_account_limits_parser.set_defaults(which='ban_account')
    ban_account_limits_parser.add_argument('--account', dest='account', action='store', help='Account name', required=True)

    unban_account_limits_parser = account_subparser.add_parser('unban',
                                                               help='Unban a banned account. The account is mandatory parameter.',
                                                               formatter_class=argparse.RawDescriptionHelpFormatter,
                                                               epilog='Usage example\n'
                                                                      '"""""""""""""\n'
                                                                      '::\n'
                                                                      '\n'
                                                                      '    $ rucio-admin account unban --account jdoe\n'
                                                                      '    Account jdoe unbanned\n'
                                                                      '\n')
    unban_account_limits_parser.set_defaults(which='unban_account')
    unban_account_limits_parser.add_argument('--account', dest='account', action='store', help='Account name', required=True)

    # Update account subparser
    update_account_parser = account_subparser.add_parser('update',
                                                         help='Update an account.',
                                                         formatter_class=argparse.RawDescriptionHelpFormatter,
                                                         epilog='Usage example\n'
                                                                '"""""""""""""\n'
                                                                '::\n'
                                                                '\n'
                                                                '    $ rucio-admin account update --account jdoe --key email --value test\n'
                                                                '    Account jdoe updated\n'
                                                                '\n')
    update_account_parser.set_defaults(which='update_account')
    update_account_parser.add_argument('--account', dest='account', action='store', help='Account name', required=True)
    update_account_parser.add_argument('--key', dest='key', action='store', help='Account parameter', required=True)
    update_account_parser.add_argument('--value', dest='value', action='store', help='Account parameter value', required=True)

    # The identity subparser
    identity_parser = subparsers.add_parser('identity', help='Identity methods')
    identity_subparser = identity_parser.add_subparsers()

    # The identity_add command
    identity_add_parser = identity_subparser.add_parser('add',
                                                        help='Grant an identity access to an account.',
                                                        formatter_class=argparse.RawDescriptionHelpFormatter,
                                                        epilog='Usage example\n'
                                                               '"""""""""""""\n'
                                                               '\n'
                                                               'To add an identity of X509 type::\n'
                                                               '\n'
                                                               '    $ rucio-admin identity add --account jdoe --type X509 --id \'/DC=ch/DC=cern/OU=Organic Units/OU=Users/CN=jdoe/CN=707658/CN=Joe Doe\' --email jdoe@cern.ch\n'
                                                               '    Added new identity to account: /DC=ch/DC=cern/OU=Organic Units/OU=Users/CN=jdoe/CN=707658/CN=Joe Doe-jdoe\n'
                                                               '    \n'
                                                               '    $ rucio-admin account list-identities jdoe\n'
                                                               '    Identity: /DC=ch/DC=cern/OU=Organic Units/OU=Users/CN=jdoe/CN=707658/CN=Joe Doe,   type: X509\n'
                                                               '\n'
                                                               'Note: please keep the DN inside quota marks.\n'
                                                               '\n'
                                                               'To add an identity of GSS type::\n'
                                                               '\n'
                                                               '    $ rucio-admin identity add --account jdoe --type GSS --email jdoe@cern.ch --id jdoe@CERN.CH\n'
                                                               '    Added new identity to account: jdoe@CERN.CH-jdoe\n'
                                                               '    \n'
                                                               '    $ rucio-admin account list-identities jdoe\n'
                                                               '    Identity: jdoe@CERN.CH,    type: GSS\n'
                                                               '    Identity: /DC=ch/DC=cern/OU=Organic Units/OU=Users/CN=jdoe/CN=707658/CN=Joe Doe,   type: X509\n'
                                                               '\n')
    identity_add_parser.set_defaults(which='identity_add')
    identity_add_parser.add_argument('--account', dest='account', action='store', help='Account name', required=True)
    identity_add_parser.add_argument('--type', dest='authtype', action='store', choices=['X509', 'GSS', 'USERPASS', 'SSH', 'SAML', 'OIDC'], help='Authentication type [X509|GSS|USERPASS|SSH|SAML|OIDC]', required=True)
    identity_add_parser.add_argument('--id', dest='identity', action='store', help='Identity', required=True)
    identity_add_parser.add_argument('--email', dest='email', action='store', help='Email address associated with the identity', required=True)
    identity_add_parser.add_argument('--password', dest='password', action='store', help='Password if authtype is USERPASS', required=False)

    # The identity_delete command
    identity_delete_parser = identity_subparser.add_parser('delete',
                                                           help="Revoke an identity's access to an account. The mandatory parameters are account, type and identity.",
                                                           formatter_class=argparse.RawDescriptionHelpFormatter,
                                                           epilog='Usage example\n'
                                                                  '"""""""""""""\n'
                                                                  '::\n'
                                                                  '\n'
                                                                  '    $ rucio-admin identity delete --account jdoe --type X509 --id \'/DC=ch/DC=cern/OU=Organic Units/OU=Users/CN=jdoe/CN=707658/CN=Joe Doe\'\n'
                                                                  '    Deleted identity: /DC=ch/DC=cern/OU=Organic Units/OU=Users/CN=jdoe/CN=707658/CN=Joe Doe\n'
                                                                  '\n'
                                                                  'Note: if the identity was accidentaly deleted, use add option.\n'
                                                                  '\n')
    identity_delete_parser.set_defaults(which='identity_delete')
    identity_delete_parser.add_argument('--account', dest='account', action='store', help='Account name', required=True)
    identity_delete_parser.add_argument('--type', dest='authtype', action='store', choices=['X509', 'GSS', 'USERPASS', 'SSH', 'SAML', 'OIDC'], help='Authentication type [X509|GSS|USERPASS|SSH|SAML|OIDC]', required=True)
    identity_delete_parser.add_argument('--id', dest='identity', action='store', help='Identity', required=True)

    # The RSE subparser
    rse_parser = subparsers.add_parser('rse', help='RSE (Rucio Storage Element) methods')
    rse_subparser = rse_parser.add_subparsers()

    # The list_rses command
    list_rse_parser = rse_subparser.add_parser('list',
                                               help='List all RSEs.',
                                               formatter_class=argparse.RawDescriptionHelpFormatter,
                                               epilog='Usage example\n'
                                                      '"""""""""""""\n'
                                                      'To list all rses::\n'
                                                      '\n'
                                                      '    $ rucio-admin rse list'
                                                      '\n'
                                                      'Note: same as rucio list-rses\n'
                                                      '\n'
                                                      'To list special class of rses::\n'
                                                      '\n'
                                                      '    $ rucio list-rses --expression \"tier=2&type=DATADISK\"\n'
                                                      '\n')
    list_rse_parser.set_defaults(which='list_rses')

    # The add_rse command
    add_rse_parser = rse_subparser.add_parser('add',
                                              help='Add new RSE.',
                                              formatter_class=argparse.RawDescriptionHelpFormatter,
                                              epilog='Example Usage\n'
                                                     '"""""""""""""\n'
                                                     '::\n'
                                                     '\n'
                                                     '    $ rucio-admin rse add JDOE_DATADISK\n'
                                                     '    Added new deterministic RSE: JDOE_DATADISK\n'
                                                     '\n'
                                                     '    $ rucio-admin rse add --non-deterministic JDOE-TEST_DATATAPE\n'
                                                     '    Added new non-deterministic RSE: JDOE-TEST_DATATAPE\n'
                                                     '\n')
    add_rse_parser.set_defaults(which='add_rse')
    add_rse_parser.add_argument('rse', action='store', help='RSE name')
    add_rse_parser.add_argument('--non-deterministic', action='store_true', help='Create RSE in non-deterministic mode')

    # The update_rse command
    update_rse_parser = rse_subparser.add_parser('update',
                                                 help='Update RSE settings.',
                                                 formatter_class=argparse.RawDescriptionHelpFormatter,
                                                 epilog='Example Usage\n'
                                                        '"""""""""""""\n'
                                                        '\n'
                                                        '    $ rucio-admin rse update --setting availability_write --value False\n'
                                                        '\n'
                                                        '\n')
    update_rse_parser.set_defaults(which='update_rse')
    update_rse_parser.add_argument('--rse', dest='rse', action='store', help='RSE name', required=True)
    update_rse_parser.add_argument('--setting', dest='param', action='store', help="One of deterministic, rse_type, staging_are, volatile, qos_class, availability_delete, availability_read, availability_write, city, country_name, latitude, longitude, region_code, time_zone", required=True)  # noqa: E501
    update_rse_parser.add_argument('--value', dest='value', action='store', help='Value for the new setting configuration. Use "", None or null to wipe the value', required=True)

    # The info_rse command
    info_rse_parser = rse_subparser.add_parser('info',
                                               help='Information about RSE.',
                                               formatter_class=argparse.RawDescriptionHelpFormatter,
                                               epilog='Usage example\n'
                                                      '"""""""""""""\n'
                                                      'Information about a RSE::\n'
                                                      '\n'
                                                      '    $ rucio-admin rse info JDOE_DATADISK\n'
                                                      '    Settings:\n'
                                                      '    =========\n'
                                                      '      third_party_copy_protocol: 1\n'
                                                      '      rse_type: DISK\n'
                                                      '      domain: [u\'lan\', u\'wan\']\n'
                                                      '      availability_delete: True\n'
                                                      '      delete_protocol: 1\n'
                                                      '      rse: JDOE_DATADISK\n'
                                                      '      deterministic: True\n'
                                                      '      write_protocol: 1\n'
                                                      '      read_protocol: 1\n'
                                                      '      staging_area: False\n'
                                                      '      credentials: None\n'
                                                      '      availability_write: True\n'
                                                      '      lfn2pfn_algorithm: default\n'
                                                      '      availability_read: True\n'
                                                      '      volatile: False\n'
                                                      '      id: 9c54c73cbd534450b2202a576f809f1f\n'
                                                      '    Attributes:\n'
                                                      '    ===========\n'
                                                      '      JDOE_DATADISK: True\n'
                                                      '    Protocols:\n'
                                                      '    ==========\n'
                                                      '    Usage:\n'
                                                      '    ======\n'
                                                      '      rucio\n'
                                                      '      used: 0\n'
                                                      '      rse: JDOE_DATADISK\n'
                                                      '      updated_at: 2018-02-16 13:08:28\n'
                                                      '      free: None\n'
                                                      '      source: rucio\n'
                                                      '      total: 0\n'
                                                      '\n'
                                                      'Note: alternatively:  rucio list-rse-usage JDOE_DATADISK.\n'
                                                      '\n')
    info_rse_parser.set_defaults(which='info_rse')
    info_rse_parser.add_argument('rse', action='store', help='RSE name')

    # The set_attribute_rse command
    set_attribute_rse_parser = rse_subparser.add_parser('set-attribute',
                                                        help='Add RSE attribute(key-value pair).',
                                                        formatter_class=argparse.RawDescriptionHelpFormatter,
                                                        epilog='Usage example\n'
                                                               '"""""""""""""\n'
                                                               '::\n'
                                                               '\n'
                                                               '    $ rucio-admin rse set-attribute --rse JDOE_DATADISK --key owner --value jdoe\n'
                                                               '    Added new RSE attribute for JDOE_DATADISK: owner-jdoe\n'
                                                               '\n'
                                                               'CAUTION: the existing attribute can be overwritten. Check rucio list-rse-attributes JDOE_DATADISK before setting an attribute.\n'
                                                               '\n')
    set_attribute_rse_parser.set_defaults(which='set_attribute_rse')
    set_attribute_rse_parser.add_argument('--rse', dest='rse', action='store', help='RSE name', required=True)
    set_attribute_rse_parser.add_argument('--key', dest='key', action='store', help='Attribute key', required=True)
    set_attribute_rse_parser.add_argument('--value', dest='value', action='store', help='Attribute value', required=True)

    # The delete_attribute_rse command
    delete_attribute_rse_parser = rse_subparser.add_parser('delete-attribute',
                                                           help='Delete a RSE attribute(key-value pair).',
                                                           formatter_class=argparse.RawDescriptionHelpFormatter,
                                                           epilog='Usage example\n'
                                                                  '"""""""""""""\n'
                                                                  '::\n'
                                                                  '\n'
                                                                  '    $ rucio-admin rse delete-attribute --rse JDOE_DATADISK --key owner --value jdoe\n'
                                                                  '    Deleted RSE attribute for JDOE_DATADISK: owner-jdoe\n'
                                                                  '\n')
    delete_attribute_rse_parser.set_defaults(which='delete_attribute_rse')
    delete_attribute_rse_parser.add_argument('--rse', dest='rse', action='store', help='RSE name', required=True)
    delete_attribute_rse_parser.add_argument('--key', dest='key', action='store', help='Attribute key', required=True)
    delete_attribute_rse_parser.add_argument('--value', dest='value', action='store', help='Attribute value', required=True)

    # The add_distance_rses command
    add_distance_rses_parser = rse_subparser.add_parser('add-distance',
                                                        help='Set the distance between a pair of RSEs.',
                                                        formatter_class=argparse.RawDescriptionHelpFormatter,
                                                        epilog='Usage example\n'
                                                               '"""""""""""""\n'
                                                               '::\n'
                                                               '\n'
                                                               '    $ rucio-admin rse add-distance JDOE_SCRATCHDISK JDOE_DATADISK\n'
                                                               '    Set distance from JDOE_SCRATCHDISK to JDOE_DATADISK to 1 with ranking 1/n'
                                                               '\n'
                                                               'Note::\n'
                                                               '\n'
                                                               '    --distance and --ranking can range (0-11), 0 is the closest\n'
                                                               'Note: order of RSEs is fixed: source, destination\n'
                                                               '\n')
    add_distance_rses_parser.set_defaults(which='add_distance_rses')
    add_distance_rses_parser.add_argument(dest='source', action='store', help='Source RSE name')
    add_distance_rses_parser.add_argument(dest='destination', action='store', help='Destination RSE name')
    add_distance_rses_parser.add_argument('--distance', dest='distance', default=1, type=int, help='Distance between RSEs')
    add_distance_rses_parser.add_argument('--ranking', dest='ranking', default=1, type=int, help='Ranking of link')

    # The update_distance_rses command
    update_distance_rses_parser = rse_subparser.add_parser('update-distance',
                                                           help='Update the existing distance or ranking between a pair of RSEs. The mandatory parameters are source, destination and distance or ranking.',
                                                           formatter_class=argparse.RawDescriptionHelpFormatter,
                                                           epilog='Usage example\n'
                                                                  '"""""""""""""\n'
                                                                  '::\n'
                                                                  '\n'
                                                                  '    $ rucio-admin rse update-distance JDOE_DATADISK JDOE_SCRATCHDISK --ranking 10\n'
                                                                  '    Update distance information from JDOE_DATADISK to JDOE_SCRATCHDISK:\n'
                                                                  '    - Ranking set to 10\n'
                                                                  '\n'
                                                                  'Note::\n'
                                                                  '\n'
                                                                  '    --distance can be set in range (0-11), 0 is the closest\n'
                                                                  '    --ranking can be set in range (-inf+inf), the larger the better\n'
                                                                  'Note: order of RSEs is fixed: source, destination.\n'
                                                                  'Note: ranking is updated dynamically against coditions at grid.\n'
                                                                  '\n')
    update_distance_rses_parser.set_defaults(which='update_distance_rses')
    update_distance_rses_parser.add_argument(dest='source', action='store', help='Source RSE name')
    update_distance_rses_parser.add_argument(dest='destination', action='store', help='Destination RSE name')
    update_distance_rses_parser.add_argument('--distance', dest='distance', type=int, help='Distance between RSEs')
    update_distance_rses_parser.add_argument('--ranking', dest='ranking', type=int, help='Ranking of link')

    # The get_distance_rses command
    get_distance_rses_parser = rse_subparser.add_parser('get-distance',
                                                        help='Get the distance information between a pair of RSEs.',
                                                        formatter_class=argparse.RawDescriptionHelpFormatter,
                                                        epilog='Usage example\n'
                                                               '"""""""""""""\n'
                                                               '::\n'
                                                               '\n'
                                                               '    $ rucio-admin rse get-distance JDOE_DATADISK JDOE_SCRATCHDISK\n'
                                                               '    Distance information from JDOE_DATADISK to JDOE_SCRATCHDISK: distance=3, ranking=10\n'
                                                               '\n'
                                                               'Note: order of RSEs is fixed: source, destination.\n'
                                                               '\n')
    get_distance_rses_parser.set_defaults(which='get_distance_rses')
    get_distance_rses_parser.add_argument(dest='source', action='store', help='Source RSE name')
    get_distance_rses_parser.add_argument(dest='destination', action='store', help='Destination RSE name')

    # The get_attribute_rse command
    get_attribute_rse_parser = rse_subparser.add_parser('get-attribute',
                                                        help='List RSE attributes.',
                                                        formatter_class=argparse.RawDescriptionHelpFormatter,
                                                        epilog='Usage example\n'
                                                               '"""""""""""""\n'
                                                               '::\n'
                                                               '\n'
                                                               '    $ rucio-admin rse get-attribute JDOE_DATADISK\n'
                                                               '    owner: jdoe\n'
                                                               '    JDOE_DATADISK: True\n'
                                                               '\n'
                                                               'Note: alternatively: rucio list-rse-attributes JDOE_DATADISK.\n'
                                                               '\n')
    get_attribute_rse_parser.set_defaults(which='get_attribute_rse')
    get_attribute_rse_parser.add_argument(dest='rse', action='store', help='RSE name')

    # The add_protocol_rse command
    add_protocol_rse_parser = rse_subparser.add_parser('add-protocol',
                                                       help='Add a protocol and its settings to a RSE.',
                                                       formatter_class=argparse.RawDescriptionHelpFormatter,
                                                       epilog='Usage example\n'
                                                              '"""""""""""""\n'
                                                              '::\n'
                                                              '\n'
                                                              '    $ rucio-admin rse add-protocol --hostname jdoes.test.org --scheme gsiftp --prefix \'/atlasdatadisk/rucio/\' --port 8443 JDOE_DATADISK\n'
                                                              '\n'
                                                              'Note: no printed stdout.\n'
                                                              'Note: examples of optional parametres::\n'
                                                              '\n'
                                                              '    --space-token DATADISK\n'
                                                              '    --web-service-path \'/srm/managerv2?SFN=\'\n'
                                                              '    --port 8443\n'
                                                              '    --impl \'rucio.rse.protocols.gfalv2.Default\'\n'
                                                              '      (for other protocol implementation, replace gfal2 with impl. name, e.g. srm)\n'
                                                              '    --domain-json\n'
                                                              '    --extended-attributes-json example.json\n'
                                                              '      where example.json contains dict {\'attr_name\':\'value\', ...}\n'
                                                              '\n')
    add_protocol_rse_parser.set_defaults(which='add_protocol_rse')
    add_protocol_rse_parser.add_argument(dest='rse', action='store', help='RSE name')
    add_protocol_rse_parser.add_argument('--hostname', dest='hostname', action='store', help='Endpoint hostname', required=True)
    add_protocol_rse_parser.add_argument('--scheme', dest='scheme', action='store', help='Endpoint URL scheme', required=True)
    add_protocol_rse_parser.add_argument('--prefix', dest='prefix', action='store', help='Endpoint URL path prefix', required=True)
    add_protocol_rse_parser.add_argument('--space-token', dest='space_token', action='store', help='Space token name (SRM-only)')
    add_protocol_rse_parser.add_argument('--web-service-path', dest='web_service_path', action='store', help='Web service URL (SRM-only)')
    add_protocol_rse_parser.add_argument('--port', dest='port', action='store', type=int, help='URL port')
    add_protocol_rse_parser.add_argument('--impl', dest='impl', default='rucio.rse.protocols.gfalv2.Default', action='store', help='Transfer protocol implementation to use')
    add_protocol_rse_parser.add_argument('--domain-json', dest='domain_json', action='store', type=json.loads, help='JSON describing the WAN / LAN setup')
    add_protocol_rse_parser.add_argument('--extended-attributes-json', dest='ext_attr_json', action='store', type=json.loads, help='JSON describing any extended attributes')

    # The del_protocol_rse command
    del_protocol_rse_parser = rse_subparser.add_parser('delete-protocol',
                                                       help='Delete a protocol from a RSE.',
                                                       formatter_class=argparse.RawDescriptionHelpFormatter,
                                                       epilog='Usage example\n'
                                                              '"""""""""""""\n'
                                                              '::\n'
                                                              '\n'
                                                              '   $ rucio-admin rse delete-protocol  --scheme gsiftp JDOE_DATADISK\n'
                                                              '\n'
                                                              'Note: no printed stdout.\n'
                                                              '\n')
    del_protocol_rse_parser.set_defaults(which='del_protocol_rse')
    del_protocol_rse_parser.add_argument(dest='rse', action='store', help='RSE name')
    del_protocol_rse_parser.add_argument('--hostname', dest='hostname', action='store', help='Endpoint hostname')
    del_protocol_rse_parser.add_argument('--scheme', dest='scheme', action='store', help='Endpoint URL scheme', required=True)
    del_protocol_rse_parser.add_argument('--port', dest='port', action='store', type=int, help='URL port')

    # The disable_location command
    disable_rse_parser = rse_subparser.add_parser('delete',
                                                  help='Disable RSE.',
                                                  formatter_class=argparse.RawDescriptionHelpFormatter,
                                                  epilog='Usage example\n'
                                                         '"""""""""""""\n'
                                                         '::\n'
                                                         '\n'
                                                         '   $ rucio-admin rse delete JDOE_SCRATCHDISK\n'
                                                         '\n'
                                                         'Note: no printed stdout.\n'
                                                         'CAUTION: all information about the RSE might be lost!\n'
                                                         '\n')
    disable_rse_parser.set_defaults(which='disable_rse')
    disable_rse_parser.add_argument('rse', action='store', help='RSE name')

    # The add_qos_policy command
    add_qos_policy_parser = rse_subparser.add_parser('add-qos-policy',
                                                     help='Add a QoS policy to an RSE.',
                                                     formatter_class=argparse.RawDescriptionHelpFormatter,
                                                     epilog='Usage example\n'
                                                            '"""""""""""""\n'
                                                            '\n'
                                                            '   $ rucio-admin rse add-qos-policy JDOE_DATADISK SLOW_BUT_CHEAP')
    add_qos_policy_parser.set_defaults(which='add_qos_policy')
    add_qos_policy_parser.add_argument('rse', action='store', help='RSE name')
    add_qos_policy_parser.add_argument('qos_policy', action='store', help='QoS policy')

    # The delete_qos_policy command
    delete_qos_policy_parser = rse_subparser.add_parser('delete-qos-policy',
                                                        help='Delete a QoS policy from an RSE.',
                                                        formatter_class=argparse.RawDescriptionHelpFormatter,
                                                        epilog='Usage example\n'
                                                               '"""""""""""""\n'
                                                               '\n'
                                                               '   $ rucio-admin rse delete-qos-policy JDOE_DATADISK SLOW_BUT_CHEAP')
    delete_qos_policy_parser.set_defaults(which='delete_qos_policy')
    delete_qos_policy_parser.add_argument('rse', action='store', help='RSE name')
    delete_qos_policy_parser.add_argument('qos_policy', action='store', help='QoS policy')

    # The delete_qos_policy command
    list_qos_policies_parser = rse_subparser.add_parser('list-qos-policies',
                                                        help='List all QoS policies of an RSE.',
                                                        formatter_class=argparse.RawDescriptionHelpFormatter,
                                                        epilog='Usage example\n'
                                                               '"""""""""""""\n'
                                                               '\n'
                                                               '   $ rucio-admin rse list-qos-policies JDOE_DATADISK')
    list_qos_policies_parser.set_defaults(which='list_qos_policies')
    list_qos_policies_parser.add_argument('rse', action='store', help='RSE name')

    # The scope subparser
    scope_parser = subparsers.add_parser('scope', help='Scope methods')
    scope_subparser = scope_parser.add_subparsers()

    # The add_scope command
    add_scope_parser = scope_subparser.add_parser('add',
                                                  help='Add scope.',
                                                  formatter_class=argparse.RawDescriptionHelpFormatter,
                                                  epilog='Usage example\n'
                                                         '"""""""""""""\n'
                                                         '::\n'
                                                         '\n'
                                                         '    $ rucio-admin scope add --scope user.jdoe --account jdoe\n'
                                                         '    Added new scope to account: user.jdoe-jdoe\n'
                                                         '\n')
    add_scope_parser.set_defaults(which='add_scope')
    add_scope_parser.add_argument('--account', dest='account', action='store', help='Account name', required=True)
    add_scope_parser.add_argument('--scope', dest='scope', action='store', help='Scope name', required=True)

    # The list_scope command
    list_scope_parser = scope_subparser.add_parser('list',
                                                   help='List scopes.',
                                                   formatter_class=argparse.RawDescriptionHelpFormatter,
                                                   epilog='Usage example\n'
                                                          '"""""""""""""\n'
                                                          '::\n'
                                                          '\n'
                                                          '    $ rucio-admin scope list --account jdoe\n'
                                                          '    user.jdoe\n'
                                                          '\n'
                                                          'Note: alternatively: rucio list-scopes.\n'
                                                          '\n')
    list_scope_parser.set_defaults(which='list_scopes')
    list_scope_parser.add_argument('--account', dest='account', action='store', help='Account name')

    # The config subparser
    config_parser = subparsers.add_parser('config',
                                          help='Configuration methods. The global configuration of data mangement system can by modified.',
                                          formatter_class=argparse.RawDescriptionHelpFormatter,
                                          epilog='''e.g. quotas, daemons, rses''')
    config_subparser = config_parser.add_subparsers()

    # The get_config command
    get_config_parser = config_subparser.add_parser('get',
                                                    help='Get matching configuration.',
                                                    formatter_class=argparse.RawDescriptionHelpFormatter,
                                                    epilog='Usage example\n'
                                                           '"""""""""""""\n'
                                                           '::\n'
                                                           '\n'
                                                           '    $ rucio-admin config get --section quota\n'
                                                           '    [quota]\n'
                                                           '    LOCALGROUPDISK=95\n'
                                                           '    SCRATCHDISK=30\n'
                                                           '    USERDISK=30\n'
                                                           '\n'
                                                           'Note: to list other sections: rucio-admin config get.\n'
                                                           '\n')
    get_config_parser.set_defaults(which='get_config')
    get_config_parser.add_argument('--section', dest='section', action='store', help='Section name', required=False)
    get_config_parser.add_argument('--option', dest='option', action='store', help='Option name', required=False)

    # The set_config_option command
    set_config_parser = config_subparser.add_parser('set',
                                                    help='Set matching configuration.',
                                                    formatter_class=argparse.RawDescriptionHelpFormatter,
                                                    epilog='Usage example\n'
                                                           '"""""""""""""\n'
                                                           '::\n'
                                                           '\n'
                                                           '    $ rucio-admin config set --section limitsscratchdisk --option testlimit --value 30\n'
                                                           '    Set configuration: limitsscratchdisk.testlimit=30\n'
                                                           '\n'
                                                           'CAUTION: you might not intend to change global configuration!\n'
                                                           '\n')
    set_config_parser.set_defaults(which='set_config_option')
    set_config_parser.add_argument('--section', dest='section', action='store', help='Section name', required=True)
    set_config_parser.add_argument('--option', dest='option', action='store', help='Option name', required=True)
    set_config_parser.add_argument('--value', dest='value', action='store', help='String-encoded value', required=True)

    # The delete_config_option command
    delete_config_parser = config_subparser.add_parser('delete',
                                                       help='Delete matching configuration.',
                                                       formatter_class=argparse.RawDescriptionHelpFormatter,
                                                       epilog='Usage example\n'
                                                              '"""""""""""""\n'
                                                              '::\n'
                                                              '\n'
                                                              '    $ rucio-admin config delete --section limitsscratchdisk --option testlimit\n'
                                                              '    Deleted section \'limitsscratchdisk\' option \'testlimit\'\n'
                                                              '\n'
                                                              'CAUTION: you might not intend to change global configuration!\n'
                                                              '\n')
    delete_config_parser.set_defaults(which='delete_config_option')
    delete_config_parser.add_argument('--section', dest='section', action='store', help='Section name', required=True)
    delete_config_parser.add_argument('--option', dest='option', action='store', help='Option name', required=True)

    # The subscription parser
    subs_parser = subparsers.add_parser('subscription', help='Subscription methods. The methods for automated and regular processing of some specific rules.')
    subs_subparser = subs_parser.add_subparsers()

    # The add-subscription command
    add_sub_parser = subs_subparser.add_parser('add',
                                               help='Add subscription',
                                               formatter_class=argparse.RawDescriptionHelpFormatter,
                                               epilog='Usage example\n'
                                                      '"""""""""""""\n'
                                                      '::\n'
                                                      '\n'
                                                      '    $ rucio-admin subscription add --lifetime 2 --account jdoe --priority 1 jdoes_txt_files_on_datadisk\n'
                                                      '    \'{\"scope\": [\"user.jdoe\"], \"datatype\": [\"txt\"]}\' \'[{\"copies\": 1, \"rse_expression\": \"JDOE_DATADISK\", \"lifetime\": 3600, \"activity\": \"User Subscriptions\"}]\'\n'
                                                      '    \'keeping replica on jdoes disk for 60 mins\'\n'
                                                      '    Subscription added 9a89cc8e692f4cabb8836fdafd884c5a\n'
                                                      '\n'
                                                      'Note: priority can range from 1 to infinity. Internal share for given account.\n'
                                                      '\n')
    add_sub_parser.set_defaults(which='add_subscription')
    add_sub_parser.add_argument(dest='name', action='store', help='Subscription name')
    add_sub_parser.add_argument(dest='filter', action='store', help='DID filter (eg \'{"scope": ["tests"], "project": ["data12_8TeV"]}\')')
    add_sub_parser.add_argument(dest='replication_rules', action='store', help='Replication rules (eg \'[{"copies": 2, "rse_expression": "tier=2", "lifetime": 3600, "activity": "Functional Tests", "weight": "mou"}]\')')
    add_sub_parser.add_argument(dest='comments', action='store', help='Comments on subscription')
    add_sub_parser.add_argument('--lifetime', dest='lifetime', action='store', type=int, help='Subscription lifetime (in days)')
    add_sub_parser.add_argument('--account', dest='subs_account', action='store', help='Account name')
    add_sub_parser.add_argument('--priority', dest='priority', action='store', help='The priority of the subscription')
    # retroactive and dry_run hard-coded for now

    # The list-subscriptions command
    list_sub_parser = subs_subparser.add_parser('list',
                                                help='List subscriptions',
                                                formatter_class=argparse.RawDescriptionHelpFormatter,
                                                epilog='Usage example\n'
                                                       '"""""""""""""\n'
                                                       '::\n'
                                                       '\n'
                                                       '    $ rucio-admin subscription list --account jdoe\n'
                                                       '    jdoe: jdoes_txt_files_on_datadisk UPDATED\n'
                                                       '    priority: 1\n'
                                                       '    filter: {\'datatype\': [\'txt\'], \'scope\': [\'user.jdoe\']}\n'
                                                       '    rules: [{\'lifetime\': 3600, \'rse_expression\': \'JDOE_DATADISK\', \'copies\': 1, \'activity\': \'User Subscriptions\'}]\n'
                                                       '    comments: keeping replica on jdoes disk for 60 mins\n'
                                                       '\n')
    list_sub_parser.set_defaults(which='list_subscriptions')
    list_sub_parser.add_argument('--account', dest='subs_account', action='store', help='Account name')
    list_sub_parser.add_argument('--long', dest='long', action='store_true', help='Long listing')
    list_sub_parser.add_argument(dest='name', nargs='?', action='store', help='Subscription name')

    # The update-subscription command
    update_sub_parser = subs_subparser.add_parser('update',
                                                  help='Update subscription',
                                                  formatter_class=argparse.RawDescriptionHelpFormatter,
                                                  epilog='Usage example\n'
                                                         '"""""""""""""\n'
                                                         '::\n'
                                                         '\n'
                                                         '    $ rucio-admin subscription update --lifetime 3 --account jdoe --priority 1 jdoes_txt_files_on_datadisk\n'
                                                         '    \'{\"scope\": [\"user.jdoe\"], \"datatype\": [\"txt\"]}\' \'[{\"copies\": 1, \"rse_expression\": \"JDOE_DATADISK\", \"lifetime\": 3600, \"activity\": \"User Subscriptions\"}]\n'
                                                         '    keeping replica on jdoes disk for 60 mins, valid until 23.2.2018\n'
                                                         '\n'
                                                         'Note: no printed stdout.\n'
                                                         'Note: all the input parameters are mandatory.\n'
                                                         '::\n'
                                                         '\n'
                                                         '    $ rucio-admin subscription list --account jdoe\n'
                                                         '    jdoe: jdoes_txt_files_on_datadisk UPDATED\n'
                                                         '    priority: 1\n'
                                                         '    filter: {\"datatype\": [\"txt\"], \"scope\": [\"user.jdoe\"]}\n'
                                                         '    rules: [{\"lifetime\": 3600, \"rse_expression\": \"JDOE_DATADISK\", \"copies\": 1, \"activity\": \"User Subscriptions\"}]\n'
                                                         '    comments: keeping replica on jdoes disk for 60 mins, valid until 23.2.2018\n'
                                                         '\n')
    update_sub_parser.set_defaults(which='update_subscription')
    update_sub_parser.add_argument(dest='name', action='store', help='Subscription name')
    update_sub_parser.add_argument(dest='filter', action='store', help='DID filter (eg \'{"scope": ["tests"], "project": ["data12_8TeV"]}\')')
    update_sub_parser.add_argument(dest='replication_rules', action='store', help='Replication rules (eg \'[{"activity": "Functional Tests", "copies": 2, "rse_expression": "tier=2", "lifetime": 3600, "weight": "mou"}]\')')
    update_sub_parser.add_argument(dest='comments', action='store', help='Comments on subscription')
    update_sub_parser.add_argument('--lifetime', dest='lifetime', action='store', type=int, help='Subscription lifetime (in days)')
    update_sub_parser.add_argument('--account', dest='subs_account', action='store', help='Account name')
    update_sub_parser.add_argument('--priority', dest='priority', action='store', help='The priority of the subscription')
    # subscription policy, retroactive and dry_run hard-coded for now

    # The reevaluate command
    reevaluate_did_for_subscription_parser = subs_subparser.add_parser('reevaluate',
                                                                       help='Reevaluate a list of DIDs against all active subscriptions',
                                                                       formatter_class=argparse.RawDescriptionHelpFormatter,
                                                                       epilog='Usage example\n'
                                                                              '"""""""""""""\n'
                                                                              '::\n'
                                                                              '\n'
                                                                              '    $ rucio-admin subscription reevaluate user.jdoe:jdoes.test.dataset\n'
                                                                              '\n'
                                                                              'Note: no printed stdout.\n'
                                                                              '\n')
    reevaluate_did_for_subscription_parser.set_defaults(which='reevaluate_did_for_subscription')
    reevaluate_did_for_subscription_parser.add_argument(dest='dids', action='store', help='List of DIDs (coma separated)')

    # The replica parser
    rep_parser = subparsers.add_parser('replicas', help='Replica methods')
    rep_subparser = rep_parser.add_subparsers()

    # The declare-bad command
    declare_bad_file_replicas_parser = rep_subparser.add_parser('declare-bad',
                                                                help='Declare bad file replicas',
                                                                formatter_class=argparse.RawDescriptionHelpFormatter,
                                                                epilog='Usage example\n'
                                                                       '"""""""""""""\n'
                                                                       '::\n'
                                                                       '\n'
                                                                       '    $ rucio-admin replicas declare-bad\n'
                                                                       '    srm://se.bfg.uni-freiburg.de:8443/srm/managerv2?SFN=/pnfs/bfg.uni-freiburg.de/data/atlasdatadisk/rucio/user/jdoe/e2/a7/jdoe.TXT.txt --reason \'test only\'\n'
                                                                       '\n'
                                                                       'Note: no printed stdout.\n'
                                                                       '\n'
                                                                       'Note: pfn can be provided, see rucio-admin replicas list-pfns or rucio list-file-replicas\n'
                                                                       '\n')
    declare_bad_file_replicas_parser.set_defaults(which='declare_bad_file_replicas')
    declare_bad_file_replicas_parser.add_argument(dest='listbadfiles', action='store', nargs='*', help='List of bad items. Each can be a PFN (for one replica) or an LFN (for all replicas of the LFN) or a collection DID (for all file replicas in the DID)')
    declare_bad_file_replicas_parser.add_argument('--reason', dest='reason', required=True, action='store', help='Reason')
    declare_bad_file_replicas_parser.add_argument('--inputfile', dest='inputfile', nargs='?', action='store', help='File containing list of bad items')
    declare_bad_file_replicas_parser.add_argument('--allow-collection', dest='allow_collection', action='store_true', help='Allow passing a collection DID as bad item')

    # The declare-temporary-unavailable command
    declare_temporary_unavailable_replicas_parser = rep_subparser.add_parser('declare-temporary-unavailable',
                                                                             help='Declare temporary unavailable replicas',
                                                                             formatter_class=argparse.RawDescriptionHelpFormatter,
                                                                             epilog='Usage example\n'
                                                                                    '"""""""""""""\n'
                                                                                    '::\n'
                                                                                    '\n'
                                                                                    '    $ rucio-admin replicas declare-temporary-unavailable\n'
                                                                                    '    srm://se.bfg.uni-freiburg.de/pnfs/bfg.uni-freiburg.de/data/atlasdatadisk/rucio/user/jdoe/e2/a7/jdoe.TXT.txt --expiration-date 168 --reason \'test only\'\n')
    declare_temporary_unavailable_replicas_parser.set_defaults(which='declare_temporary_unavailable_replicas')
    declare_temporary_unavailable_replicas_parser.add_argument(dest='listbadfiles', action='store', nargs='*', help='List of replicas. Each needs to be a proper PFN including the protocol')
    declare_temporary_unavailable_replicas_parser.add_argument('--reason', dest='reason', required=True, action='store', help='Reason')
    declare_temporary_unavailable_replicas_parser.add_argument('--inputfile', dest='inputfile', nargs='?', action='store', help='File containing list of replicas')
    declare_temporary_unavailable_replicas_parser.add_argument('--expiration-date', dest='expiration_date', action='store', type=int, default=24, help='Timeout in hours when the replicas will become available again. Default 24')

    # The list-pfns command
    list_pfns_parser = rep_subparser.add_parser('list-pfns',
                                                help='List the possible PFN for a file at a site.',
                                                formatter_class=argparse.RawDescriptionHelpFormatter,
                                                epilog='Usage example\n'
                                                       '"""""""""""""\n'
                                                       '::\n'
                                                       '\n'
                                                       '    $ rucio-admin replicas list-pfns \n'
                                                       '    user.jdoe:jdoe.TXT.txt CERN-PROD_SCRATCHDISK srm \'{\"all_states\": False, \"schemes\": [\"srm\"], \"dids\": [{\"scope\": \"user.jdoe\", \"name\": \"jdoe.TXT.txt\"}]}\'\n'
                                                       '    srm://srm-eosatlas.cern.ch:8443/srm/v2/server?SFN=/eos/atlas/atlasscratchdisk/rucio/user/jdoe/e2/a7/jdoe.TXT.txt'
                                                       '\n')
    list_pfns_parser.set_defaults(which='list_pfns')
    list_pfns_parser.add_argument(dest='dids', action='store', help='List of DIDs (coma separated)')
    list_pfns_parser.add_argument(dest='rse', action='store', help='RSE')
    list_pfns_parser.add_argument(dest='protocol', action='store', default='srm', help='The protocol, by default srm, can be one of [root|srm|http(s)].')

    # The set-tombstone command
    set_tombstone_parser = rep_subparser.add_parser('set-tombstone',
                                                    help='Set a tombstone on a replica manually to force deletion. Only works if there is no lock on the replica.',
                                                    formatter_class=argparse.RawDescriptionHelpFormatter,
                                                    epilog='Usage example\n'
                                                           '"""""""""""""\n'
                                                           '::\n'
                                                           '\n'
                                                           '    $ rucio-admin replicas set-tombstone mock:file --rse MOCK'
                                                           '\n')
    set_tombstone_parser.add_argument('dids', action='store', help='One or multiple comma separated DIDs.')
    set_tombstone_parser.add_argument('--rse', action='store', required=True, help='RSE')
    set_tombstone_parser.set_defaults(which='set_tombstone')

    return oparser


if __name__ == '__main__':
    oparser = get_parser()
    argcomplete.autocomplete(oparser)

    if len(sys.argv) == 1:
        oparser.print_help()
        sys.exit(FAILURE)

    args = oparser.parse_args(sys.argv[1:])

    commands = {'add_account': add_account,
                'list_accounts': list_accounts,
                'list_account_attributes': list_account_attributes,
                'add_account_attribute': add_account_attribute,
                'delete_account_attribute': delete_account_attribute,
                'delete_account': delete_account,
                'info_account': info_account,
                'ban_account': ban_account,
                'unban_account': unban_account,
                'update_account': update_account,
                'get_limits': get_limits,
                'set_limits': set_limits,
                'delete_limits': delete_limits,
                'list_identities': list_identities,
                'identity_add': identity_add,
                'identity_delete': identity_delete,
                'add_rse': add_rse,
                'update_rse': update_rse,
                'set_attribute_rse': set_attribute_rse,
                'get_attribute_rse': get_attribute_rse,
                'delete_attribute_rse': delete_attribute_rse,
                'add_distance_rses': add_distance_rses,
                'update_distance_rses': update_distance_rses,
                'get_distance_rses': get_distance_rses,
                'add_protocol_rse': add_protocol_rse,
                'del_protocol_rse': del_protocol_rse,
                'list_rses': list_rses,
                'disable_rse': disable_rse,
                'add_qos_policy': add_qos_policy,
                'delete_qos_policy': delete_qos_policy,
                'list_qos_policies': list_qos_policies,
                'add_scope': add_scope,
                'list_scopes': list_scopes,
                'info_rse': info_rse,
                'get_config': get_config,
                'set_config_option': set_config_option,
                'delete_config_option': delete_config_option,
                'add_subscription': add_subscription,
                'list_subscriptions': list_subscriptions,
                'update_subscription': update_subscription,
                'reevaluate_did_for_subscription': reevaluate_did_for_subscription,
                'declare_bad_file_replicas': declare_bad_file_replicas,
                'declare_temporary_unavailable_replicas': declare_temporary_unavailable_replicas,
                'list_pfns': list_pfns,
                'import': import_data,
                'export': export_data,
                'set_tombstone': set_tombstone
                }

    try:
        if args.verbose:
            logger.setLevel(logging.DEBUG)
        start_time = time.time()
        command = commands.get(args.which)
        result = command(args)
        end_time = time.time()
        if args.verbose:
            print("Completed in %-0.4f sec." % (end_time - start_time))
        sys.exit(result)
    except Exception as error:
        logger.error("Strange error: {0}".format(error))
        sys.exit(FAILURE)
