#!python
# -*- coding: utf-8 -*-
# Copyright (C) 2018-2022 fjord-technologies
# SPDX-License-Identifier: GPL-3.0-or-later
"""autond"""

__version__ = '1.0.0'

import argparse
import logging
import grp
import os
import pwd
import six

from dwho.modules import * # XXX
from dwho.config import init_logger, make_piddir, make_logdir
from httpdis.ext import httpdis_json
from sonicprobe.libs import daemonize

from auton.classes.config import DWHO_THREADS, load_conf, start_endpoints
from auton.classes.http import AutonHttpReqHandler
from auton.classes.auth_store import PersistentAuthentication
from auton.classes.auth_audit import AuthenticationAudit
from auton.classes.job_store import SQLiteJobStore
from auton.classes.jobs import DEFAULT_MAX_OUTPUT_BYTES
from auton.classes.plugins import ENDPOINTS
from dwho.classes.modules import MODULES
from auton.modules import * # XXX
from auton.plugins import * # XXX


SYSLOG_NAME       = "autond"
LOG               = logging.getLogger(SYSLOG_NAME)

DEFAULT_PIDFILE   = "/run/auton/autond.pid"
DEFAULT_LOGFILE   = "/var/log/autond/daemon.log"
DEFAULT_USER      = "auton"
DEFAULT_GROUP     = "auton"

try:
    AUTON_USER  = pwd.getpwnam(os.environ.get('AUTON_USER') or DEFAULT_USER).pw_name
except KeyError:
    AUTON_USER  = pwd.getpwuid(os.geteuid())[0]

try:
    AUTON_GROUP = grp.getgrnam(os.environ.get('AUTON_GROUP') or DEFAULT_GROUP).gr_name
except KeyError:
    AUTON_GROUP = grp.getgrgid(os.getegid())[0]

AUTOND_PIDFILE = os.environ.get('AUTOND_PIDFILE') or DEFAULT_PIDFILE
AUTOND_LOGFILE = os.environ.get('AUTOND_LOGFILE') or DEFAULT_LOGFILE


def argv_parse_check():
    """
    Parse (and check a little) command line parameters
    """
    parser        = argparse.ArgumentParser()

    parser.add_argument("-l",
                        dest      = 'loglevel',
                        default   = 'info',   # warning: see affectation under
                        choices   = ('critical', 'error', 'warning', 'info', 'debug'),
                        help      = ("Emit traces with LOGLEVEL details, must be one of:\t"
                                     "critical, error, warning, info, debug"))
    parser.add_argument("-d",
                        action    = 'store_true',
                        dest      = 'dontlauchmain',
                        default   = False,
                        help      = "Don't call the main function, for installation test purposes")
    parser.add_argument("-f",
                        action    = 'store_true',
                        dest      = 'foreground',
                        default   = False,
                        help      = "Foreground, don't daemonize")
    parser.add_argument("-c",
                        dest      = 'conffile',
                        type      = six.ensure_text,
                        default   = '/etc/auton/auton.yml',
                        help      = "Use configuration file <conffile> instead of %(default)s")
    parser.add_argument("-p",
                        dest      = 'pidfile',
                        type      = six.ensure_text,
                        default   = AUTOND_PIDFILE,
                        help      = "Use PID file <pidfile> instead of %(default)s")
    parser.add_argument("-u",
                        dest      = 'username',
                        type      = six.ensure_text,
                        default   = AUTON_USER,
                        help      = "Use username for the process instead of %(default)s")
    parser.add_argument("-g",
                        dest      = 'groupname',
                        type      = six.ensure_text,
                        default   = AUTON_GROUP,
                        help      = "Use groupname for the process instead of %(default)s")
    parser.add_argument("--logfile",
                        dest      = 'logfile',
                        type      = six.ensure_text,
                        default   = AUTOND_LOGFILE,
                        help      = "Use log file <logfile> instead of %(default)s")
    parser.add_argument("--listen-addr",
                        dest      = 'listen_addr',
                        type      = six.ensure_text,
                        help      = "Listen on address <listen_addr>")
    parser.add_argument("--listen-port",
                        dest      = 'listen_port',
                        type      = int,
                        help      = "Listen on port <listen_port>")

    args          = parser.parse_args()
    args.loglevel = getattr(logging, args.loglevel.upper(), logging.INFO)

    return args


def main(options):
    """
    Main function; start the server
    """
    uid = pwd.getpwnam(options.username)[2]
    gid = grp.getgrnam(options.groupname)[2]

    make_piddir(options.pidfile, uid, gid)
    make_logdir(options.logfile, uid, gid)

    root_logger = init_logger(options.logfile, SYSLOG_NAME)
    options     = load_conf(options.conffile, options, envvar = 'AUTOND_CONFIG')

    setattr(options, 'server_version', "%s/%s" % (SYSLOG_NAME, __version__))
    setattr(options, 'sys_version', '')

    # Basic files may be readable only before dropping privileges. SQLite is
    # opened later, under the daemon account and after daemonization.
    settings = options.configuration['general'].get('authentication')
    if not settings:
        AutonHttpReqHandler.configure_auth(getattr(options, 'auth_basic_file', None),
                                          getattr(options, 'auth_basic', None),
                                          required=options.configuration['general'].get('auth_mode') == 'required')

    if not options.foreground:
        LOG.info("Transforming into a daemon from hell")
        daemonize.daemonize()

    LOG.info("locking PID")
    daemonize.lock_pidfile_or_die(options.pidfile)

    authentication = None
    history = None
    try:
        LOG.info("pidfile ok")
        root_logger.setLevel(options.loglevel)
        os.chown(options.pidfile, uid, gid)
        os.setgid(gid)
        os.setuid(uid)
        os.umask(0o22)

        audit_settings = options.configuration['general'].get('auth_audit')
        audit = AuthenticationAudit(**audit_settings) if audit_settings else None
        handler = type('DaemonHttpReqHandler', (AutonHttpReqHandler,), {'auth_audit': audit})
        if settings:
            authentication = PersistentAuthentication(settings, audit=audit.service_event if audit else None)
            options.auth_provider = authentication.provider
            if options.configuration['general'].get('web_enabled'):
                from httpdis.auth_browser import BrowserAuthProvider
                options.auth_provider = BrowserAuthProvider(authentication.service,
                    options.configuration['general']['web_origin'], cookie_prefix='autond')
                MODULES['job'].browser_provider = options.auth_provider
        storage = options.configuration['general'].get('job_storage')
        if storage:
            history = SQLiteJobStore(storage['path'], timeout=storage['timeout'],
                max_output_bytes=options.configuration['general'].get('max_output_bytes', DEFAULT_MAX_OUTPUT_BYTES))
            MODULES['job'].history_store = history
        httpdis_json.init(options, False)
        DWHO_THREADS.append(httpdis_json.stop)
        start_endpoints()
        httpdis_json.run(options, http_req_handler=handler)
    except (KeyboardInterrupt, SystemExit):
        pass
    except Exception:
        LOG.exception("bad things happen")
    finally:
        try:
            httpdis_json.stop()
        finally:
            try:
                for endpoint in ENDPOINTS.values():
                    try:
                        endpoint.at_stop()
                    except Exception:
                        LOG.error('endpoint shutdown failed')
                try:
                    if history is not None:
                        history.close()
                finally:
                    if authentication is not None:
                        authentication.close()
            finally:
                daemonize.unlock_pidfile(options.pidfile)

if __name__ == '__main__':
    def _start():
        "entry point"
        options = argv_parse_check()
        if not options.dontlauchmain:
            main(options)
    _start()
