import collections
from argparse import Action, ArgumentParser

from borgmatic.config import collect

SUBPARSER_ALIASES = {
    'rcreate': ['init', '-I'],
    'prune': ['-p'],
    'compact': [],
    'create': ['-C'],
    'check': ['-k'],
    'extract': ['-x'],
    'export-tar': [],
    'mount': ['-m'],
    'umount': ['-u'],
    'restore': ['-r'],
    'rlist': [],
    'list': ['-l'],
    'rinfo': [],
    'info': ['-i'],
    'transfer': [],
    'break-lock': [],
    'borg': [],
}


def parse_subparser_arguments(unparsed_arguments, subparsers):
    '''
    Given a sequence of arguments and a dict from subparser name to argparse.ArgumentParser
    instance, give each requested action's subparser a shot at parsing all arguments. This allows
    common arguments like "--repository" to be shared across multiple subparsers.

    Return the result as a tuple of (a dict mapping from subparser name to a parsed namespace of
    arguments, a list of remaining arguments not claimed by any subparser).
    '''
    arguments = collections.OrderedDict()
    remaining_arguments = list(unparsed_arguments)
    alias_to_subparser_name = {
        alias: subparser_name
        for subparser_name, aliases in SUBPARSER_ALIASES.items()
        for alias in aliases
    }

    # If the "borg" action is used, skip all other subparsers. This avoids confusion like
    # "borg list" triggering borgmatic's own list action.
    if 'borg' in unparsed_arguments:
        subparsers = {'borg': subparsers['borg']}

    for argument in remaining_arguments:
        canonical_name = alias_to_subparser_name.get(argument, argument)
        subparser = subparsers.get(canonical_name)

        if not subparser:
            continue

        # If a parsed value happens to be the same as the name of a subparser, remove it from the
        # remaining arguments. This prevents, for instance, "check --only extract" from triggering
        # the "extract" subparser.
        parsed, unused_remaining = subparser.parse_known_args(unparsed_arguments)
        for value in vars(parsed).values():
            if isinstance(value, str):
                if value in subparsers:
                    remaining_arguments.remove(value)
            elif isinstance(value, list):
                for item in value:
                    if item in subparsers:
                        remaining_arguments.remove(item)

        arguments[canonical_name] = parsed

    # If no actions are explicitly requested, assume defaults.
    if not arguments and '--help' not in unparsed_arguments and '-h' not in unparsed_arguments:
        for subparser_name in ('create', 'prune', 'compact', 'check'):
            subparser = subparsers[subparser_name]
            parsed, unused_remaining = subparser.parse_known_args(unparsed_arguments)
            arguments[subparser_name] = parsed

    remaining_arguments = list(unparsed_arguments)

    # Now ask each subparser, one by one, to greedily consume arguments.
    for subparser_name, subparser in subparsers.items():
        if subparser_name not in arguments.keys():
            continue

        subparser = subparsers[subparser_name]
        unused_parsed, remaining_arguments = subparser.parse_known_args(remaining_arguments)

    # Special case: If "borg" is present in the arguments, consume all arguments after (+1) the
    # "borg" action.
    if 'borg' in arguments:
        borg_options_index = remaining_arguments.index('borg') + 1
        arguments['borg'].options = remaining_arguments[borg_options_index:]
        remaining_arguments = remaining_arguments[:borg_options_index]

    # Remove the subparser names themselves.
    for subparser_name, subparser in subparsers.items():
        if subparser_name in remaining_arguments:
            remaining_arguments.remove(subparser_name)

    return (arguments, remaining_arguments)


class Extend_action(Action):
    '''
    An argparse action to support Python 3.8's "extend" action in older versions of Python.
    '''

    def __call__(self, parser, namespace, values, option_string=None):
        items = getattr(namespace, self.dest, None)

        if items:
            items.extend(values)
        else:
            setattr(namespace, self.dest, list(values))


def make_parsers():
    '''
    Build a top-level parser and its subparsers and return them as a tuple.
    '''
    config_paths = collect.get_default_config_paths(expand_home=True)
    unexpanded_config_paths = collect.get_default_config_paths(expand_home=False)

    global_parser = ArgumentParser(add_help=False)
    global_parser.register('action', 'extend', Extend_action)
    global_group = global_parser.add_argument_group('global arguments')

    global_group.add_argument(
        '-c',
        '--config',
        nargs='*',
        dest='config_paths',
        default=config_paths,
        help=f"Configuration filenames or directories, defaults to: {' '.join(unexpanded_config_paths)}",
    )
    global_group.add_argument(
        '--excludes',
        dest='excludes_filename',
        help='Deprecated in favor of exclude_patterns within configuration',
    )
    global_group.add_argument(
        '-n',
        '--dry-run',
        dest='dry_run',
        action='store_true',
        help='Go through the motions, but do not actually write to any repositories',
    )
    global_group.add_argument(
        '-nc', '--no-color', dest='no_color', action='store_true', help='Disable colored output'
    )
    global_group.add_argument(
        '-v',
        '--verbosity',
        type=int,
        choices=range(-1, 3),
        default=0,
        help='Display verbose progress to the console (from only errors to very verbose: -1, 0, 1, or 2)',
    )
    global_group.add_argument(
        '--syslog-verbosity',
        type=int,
        choices=range(-1, 3),
        default=0,
        help='Log verbose progress to syslog (from only errors to very verbose: -1, 0, 1, or 2). Ignored when console is interactive or --log-file is given',
    )
    global_group.add_argument(
        '--log-file-verbosity',
        type=int,
        choices=range(-1, 3),
        default=0,
        help='Log verbose progress to log file (from only errors to very verbose: -1, 0, 1, or 2). Only used when --log-file is given',
    )
    global_group.add_argument(
        '--monitoring-verbosity',
        type=int,
        choices=range(-1, 3),
        default=0,
        help='Log verbose progress to monitoring integrations that support logging (from only errors to very verbose: -1, 0, 1, or 2)',
    )
    global_group.add_argument(
        '--log-file', type=str, help='Write log messages to this file instead of syslog',
    )
    global_group.add_argument(
        '--log-file-format',
        type=str,
        help='Log format string used for log messages written to the log file',
    )
    global_group.add_argument(
        '--override',
        metavar='SECTION.OPTION=VALUE',
        nargs='+',
        dest='overrides',
        action='extend',
        help='One or more configuration file options to override with specified values',
    )
    global_group.add_argument(
        '--no-environment-interpolation',
        dest='resolve_env',
        action='store_false',
        help='Do not resolve environment variables in configuration file',
    )
    global_group.add_argument(
        '--bash-completion',
        default=False,
        action='store_true',
        help='Show bash completion script and exit',
    )
    global_group.add_argument(
        '--version',
        dest='version',
        default=False,
        action='store_true',
        help='Display installed version number of borgmatic and exit',
    )

    top_level_parser = ArgumentParser(
        description='''
            Simple, configuration-driven backup software for servers and workstations. If none of
            the action options are given, then borgmatic defaults to: create, prune, compact, and
            check.
            ''',
        parents=[global_parser],
    )

    subparsers = top_level_parser.add_subparsers(
        title='actions',
        metavar='',
        help='Specify zero or more actions. Defaults to create, prune, compact, and check. Use --help with action for details:',
    )
    rcreate_parser = subparsers.add_parser(
        'rcreate',
        aliases=SUBPARSER_ALIASES['rcreate'],
        help='Create a new, empty Borg repository',
        description='Create a new, empty Borg repository',
        add_help=False,
    )
    rcreate_group = rcreate_parser.add_argument_group('rcreate arguments')
    rcreate_group.add_argument(
        '-e',
        '--encryption',
        dest='encryption_mode',
        help='Borg repository encryption mode',
        required=True,
    )
    rcreate_group.add_argument(
        '--source-repository',
        '--other-repo',
        metavar='KEY_REPOSITORY',
        help='Path to an existing Borg repository whose key material should be reused (Borg 2.x+ only)',
    )
    rcreate_group.add_argument(
        '--repository',
        help='Path of the new repository to create (must be already specified in a borgmatic configuration file), defaults to the configured repository if there is only one',
    )
    rcreate_group.add_argument(
        '--copy-crypt-key',
        action='store_true',
        help='Copy the crypt key used for authenticated encryption from the source repository, defaults to a new random key (Borg 2.x+ only)',
    )
    rcreate_group.add_argument(
        '--append-only', action='store_true', help='Create an append-only repository',
    )
    rcreate_group.add_argument(
        '--storage-quota', help='Create a repository with a fixed storage quota',
    )
    rcreate_group.add_argument(
        '--make-parent-dirs',
        action='store_true',
        help='Create any missing parent directories of the repository directory',
    )
    rcreate_group.add_argument(
        '-h', '--help', action='help', help='Show this help message and exit'
    )

    transfer_parser = subparsers.add_parser(
        'transfer',
        aliases=SUBPARSER_ALIASES['transfer'],
        help='Transfer archives from one repository to another, optionally upgrading the transferred data (Borg 2.0+ only)',
        description='Transfer archives from one repository to another, optionally upgrading the transferred data (Borg 2.0+ only)',
        add_help=False,
    )
    transfer_group = transfer_parser.add_argument_group('transfer arguments')
    transfer_group.add_argument(
        '--repository',
        help='Path of existing destination repository to transfer archives to, defaults to the configured repository if there is only one',
    )
    transfer_group.add_argument(
        '--source-repository',
        help='Path of existing source repository to transfer archives from',
        required=True,
    )
    transfer_group.add_argument(
        '--archive',
        help='Name of single archive to transfer (or "latest"), defaults to transferring all archives',
    )
    transfer_group.add_argument(
        '--upgrader',
        help='Upgrader type used to convert the transferred data, e.g. "From12To20" to upgrade data from Borg 1.2 to 2.0 format, defaults to no conversion',
    )
    transfer_group.add_argument(
        '--progress',
        default=False,
        action='store_true',
        help='Display progress as each archive is transferred',
    )
    transfer_group.add_argument(
        '-a',
        '--match-archives',
        '--glob-archives',
        metavar='PATTERN',
        help='Only transfer archives with names matching this pattern',
    )
    transfer_group.add_argument(
        '--sort-by', metavar='KEYS', help='Comma-separated list of sorting keys'
    )
    transfer_group.add_argument(
        '--first',
        metavar='N',
        help='Only transfer first N archives after other filters are applied',
    )
    transfer_group.add_argument(
        '--last', metavar='N', help='Only transfer last N archives after other filters are applied'
    )
    transfer_group.add_argument(
        '-h', '--help', action='help', help='Show this help message and exit'
    )

    prune_parser = subparsers.add_parser(
        'prune',
        aliases=SUBPARSER_ALIASES['prune'],
        help='Prune archives according to the retention policy (with Borg 1.2+, run compact afterwards to actually free space)',
        description='Prune archives according to the retention policy (with Borg 1.2+, run compact afterwards to actually free space)',
        add_help=False,
    )
    prune_group = prune_parser.add_argument_group('prune arguments')
    prune_group.add_argument(
        '--repository',
        help='Path of specific existing repository to prune (must be already specified in a borgmatic configuration file)',
    )
    prune_group.add_argument(
        '--stats',
        dest='stats',
        default=False,
        action='store_true',
        help='Display statistics of archive',
    )
    prune_group.add_argument(
        '--list', dest='list_archives', action='store_true', help='List archives kept/pruned'
    )
    prune_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')

    compact_parser = subparsers.add_parser(
        'compact',
        aliases=SUBPARSER_ALIASES['compact'],
        help='Compact segments to free space (Borg 1.2+, borgmatic 1.5.23+ only)',
        description='Compact segments to free space (Borg 1.2+, borgmatic 1.5.23+ only)',
        add_help=False,
    )
    compact_group = compact_parser.add_argument_group('compact arguments')
    compact_group.add_argument(
        '--repository',
        help='Path of specific existing repository to compact (must be already specified in a borgmatic configuration file)',
    )
    compact_group.add_argument(
        '--progress',
        dest='progress',
        default=False,
        action='store_true',
        help='Display progress as each segment is compacted',
    )
    compact_group.add_argument(
        '--cleanup-commits',
        dest='cleanup_commits',
        default=False,
        action='store_true',
        help='Cleanup commit-only 17-byte segment files left behind by Borg 1.1 (flag in Borg 1.2 only)',
    )
    compact_group.add_argument(
        '--threshold',
        type=int,
        dest='threshold',
        help='Minimum saved space percentage threshold for compacting a segment, defaults to 10',
    )
    compact_group.add_argument(
        '-h', '--help', action='help', help='Show this help message and exit'
    )

    create_parser = subparsers.add_parser(
        'create',
        aliases=SUBPARSER_ALIASES['create'],
        help='Create an archive (actually perform a backup)',
        description='Create an archive (actually perform a backup)',
        add_help=False,
    )
    create_group = create_parser.add_argument_group('create arguments')
    create_group.add_argument(
        '--repository',
        help='Path of specific existing repository to backup to (must be already specified in a borgmatic configuration file)',
    )
    create_group.add_argument(
        '--progress',
        dest='progress',
        default=False,
        action='store_true',
        help='Display progress for each file as it is backed up',
    )
    create_group.add_argument(
        '--stats',
        dest='stats',
        default=False,
        action='store_true',
        help='Display statistics of archive',
    )
    create_group.add_argument(
        '--list', '--files', dest='list_files', action='store_true', help='Show per-file details'
    )
    create_group.add_argument(
        '--json', dest='json', default=False, action='store_true', help='Output results as JSON'
    )
    create_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')

    check_parser = subparsers.add_parser(
        'check',
        aliases=SUBPARSER_ALIASES['check'],
        help='Check archives for consistency',
        description='Check archives for consistency',
        add_help=False,
    )
    check_group = check_parser.add_argument_group('check arguments')
    check_group.add_argument(
        '--repository',
        help='Path of specific existing repository to check (must be already specified in a borgmatic configuration file)',
    )
    check_group.add_argument(
        '--progress',
        dest='progress',
        default=False,
        action='store_true',
        help='Display progress for each file as it is checked',
    )
    check_group.add_argument(
        '--repair',
        dest='repair',
        default=False,
        action='store_true',
        help='Attempt to repair any inconsistencies found (for interactive use)',
    )
    check_group.add_argument(
        '--only',
        metavar='CHECK',
        choices=('repository', 'archives', 'data', 'extract'),
        dest='only',
        action='append',
        help='Run a particular consistency check (repository, archives, data, or extract) instead of configured checks (subject to configured frequency, can specify flag multiple times)',
    )
    check_group.add_argument(
        '--force',
        default=False,
        action='store_true',
        help='Ignore configured check frequencies and run checks unconditionally',
    )
    check_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')

    extract_parser = subparsers.add_parser(
        'extract',
        aliases=SUBPARSER_ALIASES['extract'],
        help='Extract files from a named archive to the current directory',
        description='Extract a named archive to the current directory',
        add_help=False,
    )
    extract_group = extract_parser.add_argument_group('extract arguments')
    extract_group.add_argument(
        '--repository',
        help='Path of repository to extract, defaults to the configured repository if there is only one',
    )
    extract_group.add_argument(
        '--archive', help='Name of archive to extract (or "latest")', required=True
    )
    extract_group.add_argument(
        '--path',
        '--restore-path',
        metavar='PATH',
        nargs='+',
        dest='paths',
        help='Paths to extract from archive, defaults to the entire archive',
    )
    extract_group.add_argument(
        '--destination',
        metavar='PATH',
        dest='destination',
        help='Directory to extract files into, defaults to the current directory',
    )
    extract_group.add_argument(
        '--strip-components',
        type=lambda number: number if number == 'all' else int(number),
        metavar='NUMBER',
        help='Number of leading path components to remove from each extracted path or "all" to strip all leading path components. Skip paths with fewer elements',
    )
    extract_group.add_argument(
        '--progress',
        dest='progress',
        default=False,
        action='store_true',
        help='Display progress for each file as it is extracted',
    )
    extract_group.add_argument(
        '-h', '--help', action='help', help='Show this help message and exit'
    )

    export_tar_parser = subparsers.add_parser(
        'export-tar',
        aliases=SUBPARSER_ALIASES['export-tar'],
        help='Export an archive to a tar-formatted file or stream',
        description='Export an archive to a tar-formatted file or stream',
        add_help=False,
    )
    export_tar_group = export_tar_parser.add_argument_group('export-tar arguments')
    export_tar_group.add_argument(
        '--repository',
        help='Path of repository to export from, defaults to the configured repository if there is only one',
    )
    export_tar_group.add_argument(
        '--archive', help='Name of archive to export (or "latest")', required=True
    )
    export_tar_group.add_argument(
        '--path',
        metavar='PATH',
        nargs='+',
        dest='paths',
        help='Paths to export from archive, defaults to the entire archive',
    )
    export_tar_group.add_argument(
        '--destination',
        metavar='PATH',
        dest='destination',
        help='Path to destination export tar file, or "-" for stdout (but be careful about dirtying output with --verbosity or --list)',
        required=True,
    )
    export_tar_group.add_argument(
        '--tar-filter', help='Name of filter program to pipe data through'
    )
    export_tar_group.add_argument(
        '--list', '--files', dest='list_files', action='store_true', help='Show per-file details'
    )
    export_tar_group.add_argument(
        '--strip-components',
        type=int,
        metavar='NUMBER',
        dest='strip_components',
        help='Number of leading path components to remove from each exported path. Skip paths with fewer elements',
    )
    export_tar_group.add_argument(
        '-h', '--help', action='help', help='Show this help message and exit'
    )

    mount_parser = subparsers.add_parser(
        'mount',
        aliases=SUBPARSER_ALIASES['mount'],
        help='Mount files from a named archive as a FUSE filesystem',
        description='Mount a named archive as a FUSE filesystem',
        add_help=False,
    )
    mount_group = mount_parser.add_argument_group('mount arguments')
    mount_group.add_argument(
        '--repository',
        help='Path of repository to use, defaults to the configured repository if there is only one',
    )
    mount_group.add_argument('--archive', help='Name of archive to mount (or "latest")')
    mount_group.add_argument(
        '--mount-point',
        metavar='PATH',
        dest='mount_point',
        help='Path where filesystem is to be mounted',
        required=True,
    )
    mount_group.add_argument(
        '--path',
        metavar='PATH',
        nargs='+',
        dest='paths',
        help='Paths to mount from archive, defaults to the entire archive',
    )
    mount_group.add_argument(
        '--foreground',
        dest='foreground',
        default=False,
        action='store_true',
        help='Stay in foreground until ctrl-C is pressed',
    )
    mount_group.add_argument('--options', dest='options', help='Extra Borg mount options')
    mount_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')

    umount_parser = subparsers.add_parser(
        'umount',
        aliases=SUBPARSER_ALIASES['umount'],
        help='Unmount a FUSE filesystem that was mounted with "borgmatic mount"',
        description='Unmount a mounted FUSE filesystem',
        add_help=False,
    )
    umount_group = umount_parser.add_argument_group('umount arguments')
    umount_group.add_argument(
        '--mount-point',
        metavar='PATH',
        dest='mount_point',
        help='Path of filesystem to unmount',
        required=True,
    )
    umount_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')

    restore_parser = subparsers.add_parser(
        'restore',
        aliases=SUBPARSER_ALIASES['restore'],
        help='Restore database dumps from a named archive',
        description='Restore database dumps from a named archive. (To extract files instead, use "borgmatic extract".)',
        add_help=False,
    )
    restore_group = restore_parser.add_argument_group('restore arguments')
    restore_group.add_argument(
        '--repository',
        help='Path of repository to restore from, defaults to the configured repository if there is only one',
    )
    restore_group.add_argument(
        '--archive', help='Name of archive to restore from (or "latest")', required=True
    )
    restore_group.add_argument(
        '--database',
        metavar='NAME',
        nargs='+',
        dest='databases',
        help="Names of databases to restore from archive, defaults to all databases. Note that any databases to restore must be defined in borgmatic's configuration",
    )
    restore_group.add_argument(
        '-h', '--help', action='help', help='Show this help message and exit'
    )

    rlist_parser = subparsers.add_parser(
        'rlist',
        aliases=SUBPARSER_ALIASES['rlist'],
        help='List repository',
        description='List the archives in a repository',
        add_help=False,
    )
    rlist_group = rlist_parser.add_argument_group('rlist arguments')
    rlist_group.add_argument(
        '--repository', help='Path of repository to list, defaults to the configured repositories',
    )
    rlist_group.add_argument(
        '--short', default=False, action='store_true', help='Output only archive names'
    )
    rlist_group.add_argument('--format', help='Format for archive listing')
    rlist_group.add_argument(
        '--json', default=False, action='store_true', help='Output results as JSON'
    )
    rlist_group.add_argument(
        '-P', '--prefix', help='Deprecated. Only list archive names starting with this prefix'
    )
    rlist_group.add_argument(
        '-a',
        '--match-archives',
        '--glob-archives',
        metavar='PATTERN',
        help='Only list archive names matching this pattern',
    )
    rlist_group.add_argument(
        '--sort-by', metavar='KEYS', help='Comma-separated list of sorting keys'
    )
    rlist_group.add_argument(
        '--first', metavar='N', help='List first N archives after other filters are applied'
    )
    rlist_group.add_argument(
        '--last', metavar='N', help='List last N archives after other filters are applied'
    )
    rlist_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')

    list_parser = subparsers.add_parser(
        'list',
        aliases=SUBPARSER_ALIASES['list'],
        help='List archive',
        description='List the files in an archive or search for a file across archives',
        add_help=False,
    )
    list_group = list_parser.add_argument_group('list arguments')
    list_group.add_argument(
        '--repository',
        help='Path of repository containing archive to list, defaults to the configured repositories',
    )
    list_group.add_argument('--archive', help='Name of the archive to list (or "latest")')
    list_group.add_argument(
        '--path',
        metavar='PATH',
        nargs='+',
        dest='paths',
        help='Paths or patterns to list from a single selected archive (via "--archive"), defaults to listing the entire archive',
    )
    list_group.add_argument(
        '--find',
        metavar='PATH',
        nargs='+',
        dest='find_paths',
        help='Partial paths or patterns to search for and list across multiple archives',
    )
    list_group.add_argument(
        '--short', default=False, action='store_true', help='Output only path names'
    )
    list_group.add_argument('--format', help='Format for file listing')
    list_group.add_argument(
        '--json', default=False, action='store_true', help='Output results as JSON'
    )
    list_group.add_argument(
        '-P', '--prefix', help='Deprecated. Only list archive names starting with this prefix'
    )
    list_group.add_argument(
        '-a',
        '--match-archives',
        '--glob-archives',
        metavar='PATTERN',
        help='Only list archive names matching this pattern',
    )
    list_group.add_argument(
        '--successful',
        default=True,
        action='store_true',
        help='Deprecated; no effect. Newer versions of Borg shows successful (non-checkpoint) archives by default.',
    )
    list_group.add_argument(
        '--sort-by', metavar='KEYS', help='Comma-separated list of sorting keys'
    )
    list_group.add_argument(
        '--first', metavar='N', help='List first N archives after other filters are applied'
    )
    list_group.add_argument(
        '--last', metavar='N', help='List last N archives after other filters are applied'
    )
    list_group.add_argument(
        '-e', '--exclude', metavar='PATTERN', help='Exclude paths matching the pattern'
    )
    list_group.add_argument(
        '--exclude-from', metavar='FILENAME', help='Exclude paths from exclude file, one per line'
    )
    list_group.add_argument('--pattern', help='Include or exclude paths matching a pattern')
    list_group.add_argument(
        '--patterns-from',
        metavar='FILENAME',
        help='Include or exclude paths matching patterns from pattern file, one per line',
    )
    list_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')

    rinfo_parser = subparsers.add_parser(
        'rinfo',
        aliases=SUBPARSER_ALIASES['rinfo'],
        help='Show repository summary information such as disk space used',
        description='Show repository summary information such as disk space used',
        add_help=False,
    )
    rinfo_group = rinfo_parser.add_argument_group('rinfo arguments')
    rinfo_group.add_argument(
        '--repository',
        help='Path of repository to show info for, defaults to the configured repository if there is only one',
    )
    rinfo_group.add_argument(
        '--json', dest='json', default=False, action='store_true', help='Output results as JSON'
    )
    rinfo_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')

    info_parser = subparsers.add_parser(
        'info',
        aliases=SUBPARSER_ALIASES['info'],
        help='Show archive summary information such as disk space used',
        description='Show archive summary information such as disk space used',
        add_help=False,
    )
    info_group = info_parser.add_argument_group('info arguments')
    info_group.add_argument(
        '--repository',
        help='Path of repository containing archive to show info for, defaults to the configured repository if there is only one',
    )
    info_group.add_argument('--archive', help='Name of archive to show info for (or "latest")')
    info_group.add_argument(
        '--json', dest='json', default=False, action='store_true', help='Output results as JSON'
    )
    info_group.add_argument(
        '-P',
        '--prefix',
        help='Deprecated. Only show info for archive names starting with this prefix',
    )
    info_group.add_argument(
        '-a',
        '--match-archives',
        '--glob-archives',
        metavar='PATTERN',
        help='Only show info for archive names matching this pattern',
    )
    info_group.add_argument(
        '--sort-by', metavar='KEYS', help='Comma-separated list of sorting keys'
    )
    info_group.add_argument(
        '--first',
        metavar='N',
        help='Show info for first N archives after other filters are applied',
    )
    info_group.add_argument(
        '--last', metavar='N', help='Show info for last N archives after other filters are applied'
    )
    info_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')

    break_lock_parser = subparsers.add_parser(
        'break-lock',
        aliases=SUBPARSER_ALIASES['break-lock'],
        help='Break the repository and cache locks left behind by Borg aborting',
        description='Break Borg repository and cache locks left behind by Borg aborting',
        add_help=False,
    )
    break_lock_group = break_lock_parser.add_argument_group('break-lock arguments')
    break_lock_group.add_argument(
        '--repository',
        help='Path of repository to break the lock for, defaults to the configured repository if there is only one',
    )
    break_lock_group.add_argument(
        '-h', '--help', action='help', help='Show this help message and exit'
    )

    borg_parser = subparsers.add_parser(
        'borg',
        aliases=SUBPARSER_ALIASES['borg'],
        help='Run an arbitrary Borg command',
        description="Run an arbitrary Borg command based on borgmatic's configuration",
        add_help=False,
    )
    borg_group = borg_parser.add_argument_group('borg arguments')
    borg_group.add_argument(
        '--repository',
        help='Path of repository to pass to Borg, defaults to the configured repositories',
    )
    borg_group.add_argument('--archive', help='Name of archive to pass to Borg (or "latest")')
    borg_group.add_argument(
        '--',
        metavar='OPTION',
        dest='options',
        nargs='+',
        help='Options to pass to Borg, command first ("create", "list", etc). "--" is optional. To specify the repository or the archive, you must use --repository or --archive instead of providing them here.',
    )
    borg_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')

    return top_level_parser, subparsers


def parse_arguments(*unparsed_arguments):
    '''
    Given command-line arguments with which this script was invoked, parse the arguments and return
    them as a dict mapping from subparser name (or "global") to an argparse.Namespace instance.
    '''
    top_level_parser, subparsers = make_parsers()

    arguments, remaining_arguments = parse_subparser_arguments(
        unparsed_arguments, subparsers.choices
    )
    arguments['global'] = top_level_parser.parse_args(remaining_arguments)

    if arguments['global'].excludes_filename:
        raise ValueError(
            'The --excludes flag has been replaced with exclude_patterns in configuration.'
        )

    if 'create' in arguments and arguments['create'].list_files and arguments['create'].progress:
        raise ValueError(
            'With the create action, only one of --list (--files) and --progress flags can be used.'
        )

    if (
        ('list' in arguments and 'rinfo' in arguments and arguments['list'].json)
        or ('list' in arguments and 'info' in arguments and arguments['list'].json)
        or ('rinfo' in arguments and 'info' in arguments and arguments['rinfo'].json)
    ):
        raise ValueError('With the --json flag, multiple actions cannot be used together.')

    if (
        'transfer' in arguments
        and arguments['transfer'].archive
        and arguments['transfer'].match_archives
    ):
        raise ValueError(
            'With the transfer action, only one of --archive and --match-archives flags can be used.'
        )

    if 'list' in arguments and (arguments['list'].prefix and arguments['list'].match_archives):
        raise ValueError(
            'With the list action, only one of --prefix or --match-archives flags can be used.'
        )

    if 'rlist' in arguments and (arguments['rlist'].prefix and arguments['rlist'].match_archives):
        raise ValueError(
            'With the rlist action, only one of --prefix or --match-archives flags can be used.'
        )

    if 'info' in arguments and (
        (arguments['info'].archive and arguments['info'].prefix)
        or (arguments['info'].archive and arguments['info'].match_archives)
        or (arguments['info'].prefix and arguments['info'].match_archives)
    ):
        raise ValueError(
            'With the info action, only one of --archive, --prefix, or --match-archives flags can be used.'
        )

    return arguments
