import logging
import sys
import multiprocessing
import threading
from optparse import OptionParser

from yandextank.contrib.netort.netort.resource import manager as resource_manager
from yandextank.core.tankworker import TankWorker
from yandextank.core.tankcore import LockError
from yandextank.validator.validator import ValidationError
from yandextank.version import VERSION


def main():
    parser = OptionParser()
    parser.add_option(
        '-c',
        '--config',
        action='append',
        help="Path to INI file containing run options, multiple options accepted",
        default=[]
    )
    parser.add_option(
        '-f',
        '--fail-lock',
        action='store_true',
        dest='lock_fail',
        help="Don't wait for lock to release, fail test instead")
    parser.add_option(
        '-i',
        '--ignore-lock',
        action='store_true',
        dest='ignore_lock',
        help="Ignore lock files from concurrent instances, has precedence before --lock-fail"
    )
    parser.add_option(
        '-k',
        '--lock-dir',
        action='store',
        dest='lock_dir',
        type="string",
        help="Directory for lock file")
    parser.add_option(
        '-l',
        '--log',
        action='store',
        default="tank.log",
        help="Tank log file location")
    parser.add_option(
        '--error_log',
        action='store',
        dest='error_log',
        default="tank_errors.log",
        help="Tank errors log file location")
    parser.add_option(
        '-m',
        '--manual-start',
        action='store_true',
        dest='manual_start',
        help="Wait for Enter key to start the test")
    parser.add_option(
        '-n',
        '--no-rc',
        action='store_true',
        dest='no_rc',
        help="Don't load config files from /etc/yandex-tank and ~/.yandex-tank")
    parser.add_option(
        '-o',
        '--option',
        action='append',
        help="Set config option, multiple options accepted, example: -o 'shellexec.start=pwd'"
    )
    parser.add_option(
        '-q',
        '--quiet',
        action='store_true',
        help="Less console output, only errors and warnings")
    parser.add_option(
        '-s',
        '--scheduled-start',
        action='store',
        dest='scheduled_start',
        help="Start test at specified time, format 'YYYY-MM-DD hh:mm:ss', date part is optional"
    )
    parser.add_option(
        '-v',
        '--verbose',
        action='store_true',
        help="More console output, +debug messages")
    parser.add_option(
        '-p',
        '--patch-cfg',
        action='append',
        help='Patch config with yaml snippet (similar to -o, but has full compatibility to\
        and the exact scheme of yaml format config)',
        dest='patches'
    )
    parser.add_option(
        '--version',
        action='store_true',
        dest='version'
    )
    # FIXME: restore completion helper
    # completion_helper = CompletionHelperOptionParser()
    # completion_helper.handle_request(parser)

    options, ammofiles = parser.parse_args()
    if options.version:
        print('YandexTank/{}'.format(VERSION))
        return

    ammofile = ammofiles[0] if len(ammofiles) > 0 else None

    handlers = init_logging(options.error_log, options.verbose, options.quiet)

    cli_kwargs = {'core': {'lock_dir': options.lock_dir}} if options.lock_dir else {}
    if options.ignore_lock:
        cli_kwargs.setdefault('core', {})['ignore_lock'] = options.ignore_lock

    if ammofile:
        logging.debug("Ammofile: %s", ammofile)
        cli_kwargs['phantom'] = {
            'use_caching': False,
            'ammofile': ammofile
        }

    run_shooting_event = multiprocessing.Event() if options.manual_start else None

    try:
        worker = TankWorker([resource_manager.resource_filename(cfg) for cfg in options.config],
                            options.option,
                            options.patches,
                            [cli_kwargs],
                            options.no_rc,
                            ammo_file=ammofile if ammofile else None,
                            log_handlers=handlers,
                            debug=options.verbose,
                            run_shooting_event=run_shooting_event,
                            )
    except (ValidationError, LockError) as e:
        logging.error(f'Config validation error:\n{e}')
        return
    worker.start()
    try:
        if run_shooting_event is not None:
            # Parallel process(TankWorker) is logging.
            # So, prompt should be repeated.
            # Otherwise prompt will be lost among parallel logging.
            def wait_blocking():
                input()
                run_shooting_event.set()
            threading.Thread(target=wait_blocking).start()
            while not run_shooting_event.wait(3):
                logging.warning('Press Enter to start shooting')
        while True:
            worker.join(timeout=2)
            if not worker.is_alive():
                break
    except KeyboardInterrupt:
        worker.stop()
        worker.join()
    sys.exit(worker.retcode)


def init_logging(events_log_fname, verbose, quiet):
    """ Set up logging, as it is very important for console tool """
    logger = logging.getLogger('')
    logger.setLevel(logging.DEBUG if verbose else logging.INFO)

    # create file handler which logs error messages

    # create console handler with a higher log level
    console_handler = logging.StreamHandler(sys.stdout)
    stderr_hdl = logging.StreamHandler(sys.stderr)

    fmt_verbose = logging.Formatter(
        "%(asctime)s [%(levelname)s] %(name)s %(filename)s:%(lineno)d\t%(message)s"
    )
    fmt_regular = logging.Formatter(
        "%(asctime)s [%(levelname)s] %(message)s", "%H:%M:%S")

    if verbose:
        console_handler.setLevel(logging.DEBUG)
        console_handler.setFormatter(fmt_verbose)
        stderr_hdl.setFormatter(fmt_verbose)
    elif quiet:
        console_handler.setLevel(logging.WARNING)
        console_handler.setFormatter(fmt_regular)
        stderr_hdl.setFormatter(fmt_regular)
    else:
        console_handler.setLevel(logging.INFO)
        console_handler.setFormatter(fmt_regular)
        stderr_hdl.setFormatter(fmt_regular)

    f_err = SingleLevelFilter(logging.ERROR, True)
    f_warn = SingleLevelFilter(logging.WARNING, True)
    f_crit = SingleLevelFilter(logging.CRITICAL, True)
    console_handler.addFilter(f_err)
    console_handler.addFilter(f_warn)
    console_handler.addFilter(f_crit)
    logger.addHandler(console_handler)

    f_info = SingleLevelFilter(logging.INFO, True)
    f_debug = SingleLevelFilter(logging.DEBUG, True)
    stderr_hdl.addFilter(f_info)
    stderr_hdl.addFilter(f_debug)
    logger.addHandler(stderr_hdl)

    if events_log_fname:
        err_file_handler = logging.FileHandler(events_log_fname)
        err_file_handler.setLevel(logging.WARNING)
        err_file_handler.setFormatter(
            logging.Formatter(
                "%(asctime)s\t%(message)s"
            ))
        return [err_file_handler, console_handler, stderr_hdl]
    else:
        return [console_handler, stderr_hdl]
        # logger.addHandler(err_file_handler)


class SingleLevelFilter(logging.Filter):
    """Exclude or approve one msg type at a time.    """

    def __init__(self, passlevel, reject):
        logging.Filter.__init__(self)
        self.passlevel = passlevel
        self.reject = reject

    def filter(self, record):
        if self.reject:
            return record.levelno != self.passlevel
        else:
            return record.levelno == self.passlevel


if __name__ == '__main__':
    main()
