#!/usr/bin/env python
"""
buffalofq_mover:  A simple and reliable file movement tool.

usage: buffalofq_mover [-h] [--limit-total LIMIT_TOTAL] [--force]
                       [--log-dir LOG_DIR]
                       [--log-level {debug,info,warning,error,critical}]
                       [--console-log] [--no-console-log] [--version]
                       [--config-name CONFIG_NAME] [--config-fqfn CONFIG_FQFN]
                       [--long-help]

optional arguments:
  -h, --help            show minimal help and exit
  --long-help           show verbose help and exit
  --limit-total LIMIT_TOTAL
                        -1 = no limit - so run continuously,
                        0 = no limit - run until you are out of files,
                        > 0 = process this many files then quit,
                        default is 0
  --force               forces immediate run even if polling duration not met
  --log-dir LOG_DIR
  --log-level {debug,info,warning,error,critical}
                        logging level - overrides config, default is debug
  --console-log         log to stdout as well as to file system
  --no-console-log      suppress logging to stdout
  --version             displays version number
  --config-name CONFIG_NAME
                        Identifies the config by name within the xdg config dir.
                        On linux this directory would be:
                            $HOME/.config/buffalofq_mover
  --config-fqfn CONFIG_FQFN
                        Identifies the config by fully-qualified file name

Configuration of buffalofq is simple, but is required.  Most configuration
items default to intuitive settings (ex: 22 for port), but some information
will always be required.  Examples of required fields include:
   - source_dir         source file directory
   - source_fn          source filename - wildcarded in quotes
   - dest_host          destination host name
   - dest_dir           destination directory name
   - dest_fn            destination filename - None defaults to source filename

Please see the project wiki for documentation, examples, and a FAQ:
    https://github.com/kenfar/buffalofq/wiki

"""

import os, sys, time
import argparse, getpass, logging
import errno
from pprint import pprint as pp
from os.path import isfile, isdir, exists, dirname, basename, join as pjoin

import paramiko
import appdirs
import cletus.cletus_job    as job
import cletus.cletus_supp   as supp
import cletus.cletus_config as conf
import cletus.cletus_log    as log

#--- our modules -------------------
# get path set for running code out of project structure & testing
sys.path.insert(0, dirname(dirname(os.path.abspath(__file__))))

import buffalofq.bfq_auditor   as bfq_auditor
import buffalofq.bfq_buffguts  as bfq_buffguts
from buffalofq._version import __version__

logger   = None   # will get set to logging api later
APP_NAME = 'buffalofq_mover'
USER = getpass.getuser()



def main():
    global logger

    # get required startup info
    args            = get_args()
    if args.get('config_fqfn'):
        audit_dir = config_dir = dirname(args['config_fqfn'])
    else:
        audit_dir = config_dir = appdirs.user_config_dir(APP_NAME)

    # get config info
    config      = setup_config(args, APP_NAME)
    log_dir     = config['log_dir']
    config_name = config.get('config_name') or os.path.splitext(basename(config.get('config_fqfn')))[0]
    instance_name = APP_NAME + '_' + config_name

    # setup logging
    # since paramiko is so verbose, we're going to set it to just errors
    paramiko.util.log_to_file(pjoin(log_dir, 'buffalofq_paramiko.log'), level='ERROR')
    logger    = setup_logger(log_to_console=True, log_level=config['log_level'], log_dir=log_dir)

    # Check to see if it's already running:
    jobcheck = is_running(instance_name)
    if not jobcheck.lock_pidfile():
        logger.warning('buffalofq_mover is already running for this config - this instance will terminate')
        sys.exit(0)

    # Check to see if it has been suppressed - or put on hold:
    suppcheck = is_suppressed(APP_NAME)
    if suppcheck.suppressed(APP_NAME):
        logger.warning('buffalofq_mover has been suppressed - will terminate')
        sys.exit(0)

    # move files for all feeds
    logger.info('Buffalofq starting now')
    logger.info('config_dir:         %s', config_dir)
    logger.info('audit_dir:          %s', audit_dir)
    logger.info('config_name:        %s', config_name)
    logger.info('limit_total:        %d', config['limit_total'])
    logger.info('polling_seconds:    %d', config['polling_seconds'])
    logger.info('source_host:        %s', config['source_host'])
    logger.info('source_dir:         %s', config['source_dir'])
    logger.info('source_post_dir:    %s', config['source_post_dir'])
    logger.info('source_post_action: %s', config['source_post_action'])
    logger.info('dest_host:          %s', config['dest_host'])
    logger.info('dest_dir:           %s', config['dest_dir'])
    logger.info('dest_post_action:   %s', config['dest_post_action'])

    one_feed = bfq_buffguts.HandleOneFeed(config,
                                          audit_dir,
                                          limit_total=config['limit_total'],
                                          config_name=config_name,
                                          key_filename=config['key_filename'])
    one_feed.run(args['force'], suppcheck)

    # termination & housekeeping
    jobcheck.close()
    logger.info('Buffalofq terminating now')




def get_args():
    parser = argparse.ArgumentParser(description='A simple and reliable file movement utility')
    parser.add_argument('--limit-total',
                        type=int,
                        help=('-1 = no limit - so run continuously, '
                              ' 0 = no limit - run until you are out of files, '
                              ' > 0 = process this many files then quit, '
                              ' default is 0'))
    parser.add_argument('--force',
                        action='store_true',
                        default=False,
                        help='forces immediate run even if polling duration not met')
    parser.add_argument('--log-dir')
    parser.add_argument('--log-level',
                        dest='log_level',
                        choices=['debug','info','warning','error','critical'],
                        help="logging level - overrides config, default is debug")
    parser.add_argument('--console-log',
                        action='store_true',
                        dest='log_to_console')
    parser.add_argument('--no-console-log',
                        action='store_false',
                        dest='log_to_console')
    parser.add_argument('--version',
                        action='version',
                        version=__version__,
                        help='displays version number')
    parser.add_argument('--config-name',
                        help='Identifies the config by name within the xdg config dir')
    parser.add_argument('--config-fqfn',
                        help='Identifies the config by file name')
    parser.add_argument('--long-help',
                        action='store_true',
                        help='Provides more verbose help')
    args   = parser.parse_args()

    if args.long_help:
        print(__doc__)
        sys.exit(0)

    return vars(args)


def setup_config(args, name):

    config_schema = {'type':  'object',
                     'properties': {
                           'name':           {'required':  True,
                                              'type':      'string',
                                              'maxLength': 50,
                                              'blank':     False },
                           'status':          {'required': True,
                                               'enum': ['enabled','disabled'] },
                           'key_filename':    {'required': True},
                           'limit_total':     {'required': True,
                                               'type':     'integer',
                                               'minimum': -1},
                           'polling_seconds': {'required': True,
                                               'type': 'integer',
                                               'minimum': 1,
                                               'maximum': 3600,
                                               'blank':   False },
                           'port':            {'required': True,
                                               'type':     'integer',
                                               'minimum':  0,
                                               'maximum':  65535,
                                               'blank':    False },
                           'log_dir':          {},
                           'log_level':       {'required': True,
                                               'enum': ['debug', 'info', 'warning', 'error', 'critical']},
                           'log_to_console':  {'required': True,
                                               'type':     'boolean'},
                           'long_help':       {'required': True,
                                               'type':     'boolean'},
                           'sort_key':        {'required': True},
                           'force':           {'required': True,
                                               'type':     'boolean'},
                           'config_name':     {'required': True},
                           'config_fqfn':     {'required': True},
                           'source_host':     {'required': True,
                                               'type':     'string',
                                               'blank':    False },
                           'source_user':     {'required': True,
                                               'type':     'string',
                                               'blank':    False },
                           'source_dir':      {'required': True,
                                               'type':     'string',
                                               'blank':    False},
                           'source_fn':       {'required': True,
                                               'type':     'string',
                                               'blank':    False},
                           'source_post_dir': {'required': False,
                                               'type':     [None, 'string'],
                                               'blank':    True},
                           'source_post_action': {'required': True,
                                                'enum': [None, 'delete','move'] },
                           'dest_host':       {'required': True,
                                               'type':     'string',
                                               'blank':    False },
                           'dest_user':       {'required': True,
                                               'type':     'string',
                                               'blank':    False },
                           'dest_dir':        {'required': True,
                                               'type':     'string',
                                               'blank':    False},
                           'dest_post_fn':    {'required': False,
                                               'type':     [None, 'string']},
                           'dest_post_dir':   {'required': False,
                                               'type':     [None, 'string']},
                           'dest_post_action': {'required': True,
                                                'enum': [None, 'move', 'symlink'] }
                                        },
                        'additionalProperties':  False
                    }
    config_defaults = {'status':          'enabled',
                       'polling_seconds': 300,
                       'port':            22,
                       'limit_total':     0,
                       'source_host':     'localhost',
                       'source_user':     USER,
                       'dest_user':       USER,
                       'dest_post_dir':   None,
                       'dest_post_fn':    None,
                       'key_filename':    'id_buffalofq_rsa',
                       'log_level':       'debug',
                       'sort_key':        None }

    config = conf.ConfigManager(config_schema)

    #--- if args refer to a regular config file - then load that file now ---
    if args.get('config_fqfn', None):
        config.add_file(config_fqfn=args['config_fqfn'])
    elif args.get('config_name', None):
        config.add_file(app_name=name,
                        config_fn='%s.yml' % args['config_name'])
    else:
        print('No config provided')

    #--- add arg dictionary to config's namespace:
    config.add_iterable(args)

    #--- add defaults:
    config.add_defaults(config_defaults)
    if config.cm_config['log_dir']:
        assert isdir(config.cm_config['log_dir'])

    #--- validate the consolidated config:
    if not config.cm_config['config_name'] and not config.cm_config['config_fqfn']:
        print('CRITICAL: a config file must be provided')
        sys.exit(1)

    config.validate()

    if config.cm_config['source_host'] != 'localhost':
        print('CRITICAL: Source_host of other than localhost not yet supported')
        sys.exit(1)

    return config.cm_config




def setup_logger(log_to_console, log_level, log_dir, log_count=50):
    cletus_logger = log.LogManager(log_name='__main__',
                                   log_to_console=log_to_console,
                                   log_count=log_count,
                                   log_dir=log_dir,
                                   log_fn='buffalofq_mover.log')
    cletus_logger.logger.setLevel('DEBUG' if log_level is None else log_level.upper())
    bfq_buffguts.setup_logging('__main__')
    return cletus_logger.logger


def is_running(name):
    jobchecker = job.JobCheck(app_name=name)
    return jobchecker

def is_suppressed(name):
    suppchecker = supp.SuppressCheck(app_name=name, silent=True)
    return suppchecker



if __name__ == '__main__':
    sys.exit(main())
