import argparse
import json
import logging
import shlex
import subprocess

import borgmatic.config.paths
import borgmatic.logger
from borgmatic.borg import environment, feature, flags, repo_info
from borgmatic.execute import DO_NOT_CAPTURE, execute_command

logger = logging.getLogger(__name__)


REPO_INFO_REPOSITORY_NOT_FOUND_EXIT_CODES = {2, 13, 15}


def create_repository(
    dry_run,
    repository_path,
    config,
    local_borg_version,
    global_arguments,
    encryption_mode,
    id_hash=None,
    key_location=None,
    source_repository=None,
    from_borg1=False,
    copy_crypt_key=False,
    append_only=None,
    storage_quota=None,
    make_parent_directories=False,
    local_path='borg',
    remote_path=None,
):
    '''
    Given a dry-run flag, a local or remote repository path, a configuration dict, the local Borg
    version, a Borg encryption mode, a Borg ID hash function name, a key location ("repokey" or
    "keyfile"), the path to another repo whose key material should be reused, whether that source
    repository is a Borg 1 repository, whether to copy the crypt key from the source repository,
    whether the repository should be append-only, the storage quota to use, whether to create any
    missing parent directories for the repository directory, the local Borg path, and the remote
    Borg path, create the repository. If the repository already exists, then log and skip creation.

    Raise ValueError if the requested encryption mode does not match that of the repository.
    Raise json.decoder.JSONDecodeError if the "borg info" JSON outputcannot be decoded.
    Raise subprocess.CalledProcessError if "borg info" returns an error exit code.
    '''
    try:
        # Suppress Borg's "repository does not exist" error log, so the user isn't confused by
        # seeing an error during successful repository creation.
        with borgmatic.logger.Logs_suppressed(msgid='Repository.DoesNotExist'):
            info_data = json.loads(
                repo_info.display_repository_info(
                    repository_path,
                    config,
                    local_borg_version,
                    argparse.Namespace(json=True),
                    global_arguments,
                    local_path,
                    remote_path,
                ),
            )

        encryption_data = info_data.get('encryption', {})
        repository_encryption_mode = encryption_data.get('mode', encryption_data.get('encryption'))
        repository_id_hash = encryption_data.get('id_hash')

        if encryption_mode != repository_encryption_mode:
            raise ValueError(
                f'Requested encryption mode "{encryption_mode}" does not match existing repository encryption mode "{repository_encryption_mode}"',
            )

        if id_hash and id_hash != repository_id_hash:
            raise ValueError(
                f'Requested ID hash function "{id_hash}" does not match existing repository ID hash function "{repository_id_hash}"',
            )

        logger.info('Repository already exists. Skipping creation.')
        return
    except subprocess.CalledProcessError as error:
        if error.returncode not in REPO_INFO_REPOSITORY_NOT_FOUND_EXIT_CODES:
            raise

    lock_wait = config.get('lock_wait')
    umask = config.get('umask')
    extra_borg_options_from_init = config.get('extra_borg_options', {}).get('init', '')
    extra_borg_options = config.get('extra_borg_options', {}).get('repo-create', '')

    if extra_borg_options_from_init:
        logger.warning(
            'The "init" option in "extra_borg_options" is deprecated and will be removed from a future release; use "repo_create" instead.'
        )

    repo_create_command = (
        (local_path,)
        + (
            ('repo-create',)
            if feature.available(feature.Feature.REPO_CREATE, local_borg_version)
            else ('init',)
        )
        + (('--encryption', encryption_mode) if encryption_mode else ())
        + (('--id-hash', id_hash) if id_hash else ())
        + (('--key-location', key_location) if key_location else ())
        + (('--other-repo', source_repository) if source_repository else ())
        + (('--from-borg1',) if from_borg1 else ())
        + (('--copy-crypt-key',) if copy_crypt_key else ())
        + (('--append-only',) if append_only else ())
        + (('--storage-quota', storage_quota) if storage_quota else ())
        + (('--make-parent-dirs',) if make_parent_directories else ())
        + (('--info',) if logger.getEffectiveLevel() == logging.INFO else ())
        + (('--debug',) if logger.isEnabledFor(logging.DEBUG) else ())
        + (('--lock-wait', str(lock_wait)) if lock_wait else ())
        + (('--remote-path', remote_path) if remote_path else ())
        + (('--umask', str(umask)) if umask else ())
        + (tuple(shlex.split(extra_borg_options)) if extra_borg_options else ())
        + (tuple(shlex.split(extra_borg_options_from_init)) if extra_borg_options_from_init else ())
        + flags.make_repository_flags(repository_path, local_borg_version)
    )

    if dry_run:
        logger.info('Skipping repository creation (dry run)')
        return

    # Do not capture output here, so as to support interactive prompts.
    execute_command(
        repo_create_command,
        output_file=DO_NOT_CAPTURE,
        environment=environment.make_environment(config),
        working_directory=borgmatic.config.paths.get_working_directory(config),
        borg_local_path=local_path,
        borg_exit_codes=config.get('borg_exit_codes'),
    )
