import collections
import enum
import logging
import os
import tempfile

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 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, encoding='utf-8'
        )
        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, working_directory):
    '''
    Given a sequence of Pattern instances and the current working directory, 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 == Pattern_type.ROOT
        if not os.path.exists(os.path.join(working_directory or '', pattern.path))
    ]

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