import os
import sys
import configparser
import inspect
import argparse
import abc
import logging
import logging.handlers
import warnings

import argcomplete

from mini_buildd import util, net, config

LOG = logging.getLogger(__name__)

#: Generic log format
LOG_FORMAT = "%(levelname).1s: %(message)s [%(name)s:%(lineno)d]"

#: Log levels that we use throughout the code (python defaults), mapped to numerical counterparts used in syslog/systemd
LOG_LEVELS = {
    logging.getLevelName(logging.CRITICAL): "2",
    logging.getLevelName(logging.ERROR): "3",
    logging.getLevelName(logging.WARNING): "4",
    logging.getLevelName(logging.INFO): "6",
    logging.getLevelName(logging.DEBUG): "7",
}


def log_test():
    """For debugging only"""
    LOG.debug("debug")
    LOG.info("info")
    LOG.warning("warning")
    LOG.error("error")
    LOG.critical("critical")


def log_clone(dst, level=None, src="mini_buildd"):
    """Set up logger named 'dst' with the same handlers and loglevel as the logger named 'src'"""
    src_log = logging.getLogger(src)
    dst_log = logging.getLogger(dst)
    dst_log.handlers = []
    for h in src_log.handlers:
        dst_log.addHandler(h)
    dst_log.setLevel(src_log.getEffectiveLevel() if level is None else level)


class ConsoleHandler(logging.StreamHandler):
    def __init__(self):
        super().__init__(stream=sys.stderr)
        self.setFormatter(logging.Formatter(LOG_FORMAT))


class ConsoleSystemDHandler(ConsoleHandler):
    def __init__(self):
        super().__init__()
        self.setFormatter(logging.Formatter(LOG_FORMAT))

    def emit(self, record):
        try:
            self.stream.write(f"<{LOG_LEVELS[record.levelname]}>{self.format(record)}" + self.terminator)
            self.flush()
        except Exception:
            self.handleError(record)


class DaemonLogHandler(logging.handlers.RotatingFileHandler):
    def __init__(self):
        super().__init__(config.ROUTES["log"].path.join(config.LOG_FILE), maxBytes=5000000, backupCount=9, encoding=config.CHAR_ENCODING)
        self.setFormatter(logging.Formatter("%(asctime)s " + LOG_FORMAT))


class DputCf():
    """Guess possible mini-buildd targets and their URL endpoints"""

    DEFAULT_PATH = "~/.dput.cf"

    def __init__(self, config_path=DEFAULT_PATH):
        self.config = os.path.expanduser(config_path)
        dput_cf = configparser.ConfigParser(interpolation=None)
        dput_cf.read(self.config)

        self.parsed = {}
        for section in dput_cf.sections():
            cfg = dput_cf[section]
            method = cfg.get("method")
            host, dummy, ftp_port = cfg.get("fqdn").partition(":")
            if section.startswith("mini-buildd") and method in ["ftp", "ftps"]:
                http_port = int(ftp_port) - 1
                http_method = "https" if method == "ftps" else "http"
                self.parsed[section] = {}
                self.parsed[section]["http_url"] = f"{http_method}://{host}:{http_port}"
                self.parsed[section]["ftp_url"] = f"{method}://{host}:{ftp_port}"

    def first_target(self):
        return next(iter(self.parsed))

    def target_completer(self, **_kwargs):
        return list(self.parsed.keys())

    def first_http_url(self):
        return self.parsed[self.first_target()]["http_url"]

    def http_url_completer(self, **_kwargs):
        return [target["http_url"] for target in self.parsed.values()]

    def get_target_ftp_url(self, target):
        return self.parsed[target]["ftp_url"]

    def get_target_http_url(self, target):
        return self.parsed[target]["http_url"]


class ArgumentDefaultsRawTextHelpFormatter(argparse.RawTextHelpFormatter, argparse.ArgumentDefaultsHelpFormatter):
    """Custom argparse (for mini-buildd[-tool]) help formatter (mixin): We like to use raw text, but also have default values shown"""


class CLI():
    def __init__(self, prog, description, epilog=None, allow_unknown=False):
        self.prog = prog
        self.args = None
        self.unknown_args = []
        self.allow_unknown = allow_unknown

        self.parser = argparse.ArgumentParser(prog=prog, description=description, epilog=epilog,
                                              formatter_class=ArgumentDefaultsRawTextHelpFormatter)
        self.parser.add_argument("--version", action="version", version=util.__version__)
        self.parser.add_argument("-l", "--log-level", choices=LOG_LEVELS.keys(), action="store", default="INFO", help="set log level (DEBUG will enable exception tracebacks and python warnings)")
        self.parser.add_argument("-v", "--verbose", dest="verbosity", action="count", default=0,
                                 help="DEPRECATED (use --log-level): increase log level. Give twice for max logs")
        self.parser.add_argument("-q", "--quiet", dest="terseness", action="count", default=0,
                                 help="DEPRECATED (use --log-level): decrease log level. Give twice for min logs")
        self.logger = logging.getLogger("mini_buildd")
        self.wlogger = logging.getLogger("py.warnings")

    @classmethod
    def _add_endpoint(cls, parser):
        parser.add_argument("endpoint", action="store",
                            metavar="ENDPOINT",
                            help=f"HTTP target endpoint: {inspect.getdoc(net.ClientEndpoint)}").completer = DputCf().http_url_completer

    @classmethod
    def _add_subparser(cls, subparser, cmd, doc):
        return subparser.add_parser(cmd, help=doc, formatter_class=ArgumentDefaultsRawTextHelpFormatter)

    @classmethod
    def loggers(cls):
        """Overwrite this method for custom subset of handler classes from this module"""
        return [ConsoleHandler()]

    def setup(self):
        pass

    @abc.abstractmethod
    def runcli(self):
        pass

    def run(self):
        argcomplete.autocomplete(self.parser)
        if self.allow_unknown:
            self.args, self.unknown_args = self.parser.parse_known_args()
        else:
            self.args = self.parser.parse_args()

        # .. attention: **compat 1.x**: --verbose, --quiet
        if (self.args.verbosity != 0 or self.args.terseness != 0):
            LOG.warning("Deprecated command line options ``--verbose, --quiet``: Use ``--log-level <level>`` instead")
            self.args.log_level = logging.WARNING - (10 * (min(2, self.args.verbosity) - min(2, self.args.terseness)))

        self.setup()

        self.logger.handlers = self.loggers()
        self.wlogger.handlers = self.loggers()

        self.logger.setLevel(self.args.log_level)
        self.wlogger.setLevel(self.args.log_level)
        logging.captureWarnings(self.logger.isEnabledFor(logging.DEBUG))
        if not sys.warnoptions:
            warnings.simplefilter("default")
        logging.raiseExceptions = self.logger.isEnabledFor(logging.DEBUG)  # Properly set raiseException (see https://docs.python.org/3.5/howto/logging.html#exceptions-raised-during-logging)

        try:
            self.runcli()
        except BaseException as e:
            util.log_exception(LOG, f"{self.prog} failed (try '--log-level DEBUG')", e, level=logging.ERROR)
            sys.exit(1)
