#!/opt/alt/python35/bin/python3

import argparse
import inspect
import logging
import sys

import os
import yaml

from restore_infected import backup_backends, helpers, restore, log
from restore_infected.backup_backends_lib import BackendNonApplicableError

logger = logging.getLogger(log.CLI_LOGGER)
logger.setLevel(logging.DEBUG)


def action(func):
    spec = inspect.getfullargspec(func)

    def wrapper(extra_args, parser, **kwargs):
        if 'extra_args' not in spec.args and extra_args:
            raise parser.error('unrecognized arguments: ' +
                               ' '.join(extra_args))
        func(extra_args=extra_args, parser=parser, **kwargs)

    return wrapper


@action
def action_init(backend, extra_args, parser, **_):
    args, kwargs = helpers.process_args(extra_args)

    missing, unknown = helpers.validate_params(backend.init, args, kwargs)
    if missing:
        raise parser.error('init arguments required: ' + ' '.join(missing))
    if unknown:
        raise parser.error('init unknown keys: ' + ' '.join(unknown))

    backend.init(*args, **kwargs)


@action
def action_list(backend, until, **_):
    backups = backend.backups(until)

    if backups:
        logger.info(os.linesep.join([str(b) for b in backups]))
    else:
        sys.exit('No backups found')


@action
def action_restore(backend, files, until, **_):
    success, failed = restore.restore_infected(backend, files, until)

    if success:
        logger.info('Restore success:')
        logger.info(os.linesep.join(success))

    if failed:
        logger.info('Restore failed:')
        logger.info(os.linesep.join(failed))


@action
def action_cleanup(backend, **_):
    backend.cleanup()


@action
def action_info(backend, **_):
    info = backend.info()
    if info:
        logger.info(yaml.dump(info, default_flow_style=False))


@action
def action_extra(backend, extra_action, extra_args, parser, **_):
    extra_func = getattr(backend, extra_action, None)
    if not getattr(extra_func, 'extra', False):
        raise parser.error('invalid extra action: {0}'.format(extra_action))
    result = extra_func(*extra_args)
    if result:
        logger.info(result)


# @action
def action_none(parser, subparsers, **_):
    raise parser.error('one of the action must be specified: ' +
                       ' '.join(subparsers.choices))


def add_parser(name, subparsers, parent):
    return subparsers.add_parser(name, parents=[parent])


def setup_parsers():
    parent = argparse.ArgumentParser(add_help=False)
    parent.add_argument('-v', '--verbose', action='count', default=0,
                        help='Print more messages depending on flag count: '
                             'v=verbose, '
                             'vv=verbose with timestamps, '
                             'vvv=debug messages')
    parent.add_argument('-o', '--output', dest='output', metavar='PATH',
                        help='Write log to specified file')
    parser = argparse.ArgumentParser(parents=[parent])
    parser.set_defaults(action=action_none)
    parser.add_argument('backend', metavar='BACKEND',
                        help='Backup backend to use')
    return parser, parent


def setup_subparsers(parser, parent):
    subparsers = parser.add_subparsers()
    parser_init = add_parser('init', subparsers, parent)
    parser_init.set_defaults(action=action_init, action_repr='Init')

    parser_list = add_parser('list', subparsers, parent)
    parser_list.set_defaults(action=action_list,
                             action_repr='Getting backup list')
    parser_list.add_argument('-u', '--until', default=None, metavar='DATE',
                             type=helpers.DateTime,
                             help='Dig backups not earlier this date')

    parser_restore = add_parser('restore', subparsers, parent)
    parser_restore.set_defaults(action=action_restore, action_repr='Restore')
    parser_restore.add_argument('files', nargs='+', metavar='FILE',
                                help='List files to restore')
    parser_restore.add_argument('-u', '--until', default=None, metavar='DATE',
                                type=helpers.DateTime,
                                help='Dig backups not earlier this date')

    parser_cleanup = add_parser('cleanup', subparsers, parent)
    parser_cleanup.set_defaults(action=action_cleanup, action_repr='Cleanup')

    parser_info = add_parser('info', subparsers, parent)
    parser_info.set_defaults(action=action_info, action_repr='Info')

    parser_extra = add_parser('extra', subparsers, parent)
    parser_extra.set_defaults(action=action_extra, action_repr='Extra actions')
    parser_extra.add_argument('extra_action', help='Extra action for backend')
    return subparsers


def parse_args(parser, subparsers):
    namespace, namespace.extra_args = parser.parse_known_args()
    namespace.parser = parser
    namespace.subparsers = subparsers
    return namespace


def enable_logs(verbosity, output):
    if not verbosity and not output:
        log.log_only_cli_module()
        return

    log.log_to_console(verbosity)
    log.log_to_file(output)


def apply_action(backend_name, func, kwargs):
    try:
        backend = backup_backends.backend(backend_name)
        kwargs['backend'] = backend
        func(**kwargs)
    except BackendNonApplicableError as e:
        sys.exit(e)
    except helpers.ActionError as e:
        e.message and logger.error(e.message, file=sys.stderr)
        sys.exit(e.code)
    except Exception as e:
        logger.error('{} error: %s'.format(kwargs['action_repr']), str(e))
        sys.exit(1)


if __name__ == '__main__':
    arg_parser, parent_parser = setup_parsers()
    subs = setup_subparsers(arg_parser, parent_parser)
    ns = parse_args(arg_parser, subs)
    enable_logs(ns.verbose, ns.output)
    apply_action(ns.backend, ns.action, ns.__dict__)
