import collections
import enum
import logging
import os
import tempfile

import borgmatic.borg.pattern

logger = logging.getLogger(__name__)


# See https://borgbackup.readthedocs.io/en/stable/usage/help.html#borg-help-patterns
class Pattern_type(enum.Enum):
    ROOT = 'R'  # A ROOT pattern always has a NONE pattern style.
    PATTERN_STYLE = 'P'
    EXCLUDE = '-'
    NO_RECURSE = '!'
    INCLUDE = '+'


class Pattern_style(enum.Enum):
    NONE = ''
    FNMATCH = 'fm'
    SHELL = 'sh'
    REGULAR_EXPRESSION = 're'
    PATH_PREFIX = 'pp'
    PATH_FULL_MATCH = 'pf'


class Pattern_source(enum.Enum):
    '''
    Where the pattern came from within borgmatic. This is important because certain use cases (like
    filesystem snapshotting) only want to consider patterns that the user actually put in a
    configuration file and not patterns from other sources.
    '''

    # The pattern is from a borgmatic configuration option, e.g. listed in "source_directories".
    CONFIG = 'config'

    # The pattern is generated internally within borgmatic, e.g. for special file excludes.
    INTERNAL = 'internal'

    # The pattern originates from within a borgmatic hook, e.g. a database hook that adds its dump
    # directory.
    HOOK = 'hook'


Pattern = collections.namedtuple(
    'Pattern',
    ('path', 'type', 'style', 'device', 'source'),
    defaults=(
        Pattern_type.ROOT,
        Pattern_style.NONE,
        None,
        Pattern_source.HOOK,
    ),
)


def write_patterns_file(patterns, borgmatic_runtime_directory, patterns_file=None):
    '''
    Given a sequence of patterns as borgmatic.borg.pattern.Pattern instances, write them to a named
    temporary file in the given borgmatic runtime directory and return the file object so it can
    continue to exist on disk as long as the caller needs it.

    If an optional open pattern file is given, append to it instead of making a new temporary file.
    Return None if no patterns are provided.
    '''
    if not patterns:
        return None

    if patterns_file is None:
        patterns_file = tempfile.NamedTemporaryFile('w', dir=borgmatic_runtime_directory)
        operation_name = 'Writing'
    else:
        patterns_file.write('\n')
        operation_name = 'Appending'

    patterns_output = '\n'.join(
        f'{pattern.type.value} {pattern.style.value}{":" if pattern.style.value else ""}{pattern.path}'
        for pattern in patterns
    )
    logger.debug(f'{operation_name} patterns to {patterns_file.name}:\n{patterns_output}')

    patterns_file.write(patterns_output)
    patterns_file.flush()

    return patterns_file


def check_all_root_patterns_exist(patterns):
    '''
    Given a sequence of borgmatic.borg.pattern.Pattern instances, check that all root pattern
    paths exist. If any don't, raise an exception.
    '''
    missing_paths = [
        pattern.path
        for pattern in patterns
        if pattern.type == borgmatic.borg.pattern.Pattern_type.ROOT
        if not os.path.exists(pattern.path)
    ]

    if missing_paths:
        raise ValueError(
            f"Source directories or root pattern paths do not exist: {', '.join(missing_paths)}"
        )