#!/usr/bin/python3
import signal
import os
import pwd
import inspect
import contextlib
import logging

import mini_buildd.misc
import mini_buildd.cli
import mini_buildd.net
import mini_buildd.config
import mini_buildd.httpd
import mini_buildd.django_settings


LOG = logging.getLogger("mini_buildd")

#: Needed for man page hack in setup.py
DESCRIPTION = "Once minimal Debian build and repository daemon"


class HttpDRun:
    """Small wrapper allowing us to use contextlib.closing()."""

    def __init__(self, webapp):
        self.httpd = mini_buildd.httpd.HttpD(wsgi_app=webapp, minthreads=mini_buildd.config.MIN_HTTPD_THREADS, maxthreads=mini_buildd.config.MAX_HTTPD_THREADS)
        self.httpd.start()

    def close(self):
        self.httpd.shutdown()
        self.httpd.join()


class CLI(mini_buildd.cli.CLI):
    SHUTDOWN = [signal.SIGTERM, signal.SIGINT, signal.SIGHUP]

    def _setup(self):
        """Set global variables that really make no sense to propagate through."""
        mini_buildd.config.DEBUG = self.args.debug.split(",")
        mini_buildd.config.HOSTNAME_FQDN = self.args.hostname
        mini_buildd.config.HOSTNAME = mini_buildd.config.HOSTNAME_FQDN.partition(".")[0]

        #: .. attention:: 'default' option in add_argument() can't be used, see https://bugs.python.org/issue16399
        if self.args.http_endpoint is not None:
            mini_buildd.config.HTTP_ENDPOINTS = self.args.http_endpoint
        mini_buildd.config.ROUTES = mini_buildd.config.Routes(self.args.home)

        # Overwrite default shutdown signal handlers with no-ops (else, SIGHUP would just crash it).
        for s in self.SHUTDOWN:
            signal.signal(s, lambda x, y: None)

    def _setup_environment(self):
        os.environ.clear()
        os.environ["HOME"] = self.args.home
        os.environ["PATH"] = "/usr/bin:/bin:/usr/sbin:/sbin"
        os.environ["LANG"] = "C.UTF-8"
        for name in ["USER", "LOGNAME"]:
            os.environ[name] = self._user

    def __init__(self):
        self._user = pwd.getpwuid(os.getuid())[0]

        super().__init__("mini-buildd", DESCRIPTION,
                         "See also: Online manual, usually at http://localhost:8066/doc/.")

        group_conf = self.parser.add_argument_group("daemon arguments")

        group_conf.add_argument("-N", "--hostname",
                                action="store",
                                default=mini_buildd.config.HOSTNAME_FQDN,
                                help="Public (fully qualified) hostname used for all services.")

        group_conf.add_argument("-E", "--http-endpoint", action="store", nargs="+",
                                help=f"""\
HTTP Server Endpoint:
{inspect.getdoc(mini_buildd.net.ServerEndpoint)}
May be given multiple times (to server multiple endpoints). The first
endpoint given will be the primary.

(default: {mini_buildd.config.HTTP_ENDPOINTS}), not""")
        group_conf.add_argument("-W", "--httpd-bind", action="store", default=":::8066",
                                help="DEPRECATED (use '--http-endpoint' instead): Web Server IP/Hostname and port to bind to.")
        group_conf.add_argument("-S", "--smtp", action="store", default=":@smtp://localhost:25",
                                help="SMTP credentials in format '[USER]:[PASSWORD]@smtp|ssmtp://HOST:PORT'.")
        group_conf.add_argument("-U", "--dedicated-user", action="store", default="mini-buildd",
                                help="Force a custom dedicated user name (to run as a different user than 'mini-buildd').")
        group_conf.add_argument("-H", "--home", action="store", default="~",
                                help="Run with this home dir (you may use '~' for user expansion). The only use case to change this for debugging, really.")

        group_log = self.parser.add_argument_group("logging and debugging arguments")
        group_log.add_argument("-d", "--debug", action="store", default="", metavar="OPTION,..",
                               help="""\
Comma-separated list of special debugging options:
'warnings' (show all warnings from python's warnings module in log),
'exception' (log tracebacks in exception handlers),
'http' (put http server in debug mode),
'webapp' (put web application [django] in debug mode),
'sbuild' (run sbuild in debug mode),
'keep' (keep spool and temporary directories).""")

        group_db = self.parser.add_argument_group("database arguments")
        group_db.add_argument("-P", "--set-admin-password", action="store", metavar="PASSWORD",
                              help="Update password for superuser ('admin'); user is created if non-existent yet.")
        group_db.add_argument("-D", "--dumpdata", action="store", metavar="APP[.MODEL]",
                              help="Dump database contents for app[.MODEL] as JSON file (see 'django-admin dumpdata').")
        group_db.add_argument("-L", "--loaddata", action="store", metavar="FILE",
                              help="INTERNAL USE ONLY, use with care! Load JSON file into database (see 'django-admin loaddata').")
        group_db.add_argument("-R", "--remove-system-artifacts", action="store_true",
                              help="INTERNAL USE ONLY, use with care! Bulk-remove associated data of all objects that might have produced artifacts on the system.")

    def setup(self):
        # reproducible builds: Expand these for run, leave static for usage (as this is used to build man pages) (thx to Chris Lamb, see Debian Bug #833340)
        self.args.home = os.path.expanduser(self.args.home)

        # Warn when deprecated --httpd-bind is still used (unless you use the default)
        if self.parser.get_default("httpd_bind") != self.args.httpd_bind:
            self.args.http_endpoint = [self.args.httpd_bind]
            LOG.warning("Option '--httpd-bind' is deprecated. Please use '--http-endpoint' instead (see 'mini-buildd --help').")

        # User sanity check
        if self.args.dedicated_user != self._user:
            raise Exception(f"Run as dedicated user only (use '--dedicated-user={self._user}' if you really want this, will write to that user's $HOME!)")

        self._setup()
        self._setup_environment()

        # Configure django
        mini_buildd.django_settings.configure(self.args.smtp)

    @classmethod
    def loggers(cls):
        return [mini_buildd.cli.ConsoleSystemDHandler(), mini_buildd.cli.DaemonLogHandler()]

    def runcli(self):
        # import here: We cannot import anything 'django' prior to django's configuration
        from mini_buildd.webapp import WebApp

        webapp = WebApp()

        if self.args.set_admin_password:
            webapp.set_admin_password(self.args.set_admin_password)
        elif self.args.remove_system_artifacts:
            webapp.remove_system_artifacts()
        elif self.args.loaddata:
            webapp.loaddata(self.args.loaddata)
        elif self.args.dumpdata:
            webapp.dumpdata(self.args.dumpdata)
        else:
            with contextlib.closing(HttpDRun(webapp)):
                mini_buildd.misc.attempt(mini_buildd.get_daemon().mbd_start)
                signal.sigwait(self.SHUTDOWN)
                mini_buildd.get_daemon().mbd_stop()


CLI().run()
