import logging
import threading

import django.conf

from mini_buildd import api, builder, config, cron, events, ftpd, gnupg, packager, threads, util, values

LOG = logging.getLogger(__name__)


class RemotesKeyring(gnupg.TmpGnuPG):
    """Remotes keyring to authorize buildrequests and buildresults"""

    def __init__(self):
        super().__init__(tmpdir_options={"prefix": "gnupg-remotes-keyring-"})
        if get().gnupg.pub_key:
            self.add_pub_key(get().gnupg.pub_key)
        for r in util.models().Remote.mbd_get_active_or_auto_reactivate():
            self.add_pub_key(r.key)
            LOG.debug("Remote key added for '%s': %s: %s", r, r.key_long_id, r.key_name)


class UploadersKeyring(gnupg.TmpGnuPG):
    """Uploader keyring for repository"""

    def __init__(self, repo_identity):
        super().__init__(tmpdir_options={"prefix": f"gnupg-uploaders-keyring-{repo_identity}-"})
        r = util.models().Repository.objects.get(pk=repo_identity)
        # Add keys from django users
        for u in django.contrib.auth.models.User.objects.filter(is_active=True):
            LOG.debug("Checking user: %s", u)
            uploader = None
            try:
                uploader = u.uploader
            except BaseException as e:
                LOG.warning("User '%s' does not have an uploader profile (deliberately removed?): %s", u, e)

            if uploader and uploader.mbd_is_active() and uploader.may_upload_to.all().filter(identity=repo_identity):
                LOG.debug("Adding uploader key for '%s': %s: %s", repo_identity, uploader.key_long_id, uploader.key_name)
                self.add_pub_key(uploader.key)

        # Add configured extra keyrings
        for line in r.extra_uploader_keyrings.splitlines():
            line = line.strip()
            if line and line[0] != "#":
                LOG.debug("Adding keyring: %s", line)
                self.add_keyring(line)

        # Always add our key too for internal builds
        if get().gnupg.pub_key:
            self.add_pub_key(get().gnupg.pub_key)


class Daemon(threads.EventThread):
    def __init__(self):
        self.builder = builder.Builder(max_parallel_builds=get_model().max_parallel_builds)
        self.packager = packager.Packager()
        self.ftpd = ftpd.FtpD(endpoint=get_model().mbd_get_ftp_endpoint())
        self.crontab = cron.Tab()
        super().__init__(subthreads=[self.ftpd, self.packager, self.builder, self.crontab])

        self.events = events.Queue.load(maxlen=get_model().event_queue_size)
        self.gnupg = get_model().mbd_gnupg()
        self.public_key_cache = gnupg.PublicKeyCache()

        self.report = util.attempt(api.Setup.report_load)

    def __str__(self):
        return str(get_model())

    def handshake_message(self):
        return self.gnupg.gpgme_sign(config.HANDSHAKE_MESSAGE)

    def join(self, timeout=None):
        super().join()
        self.events.shutdown()

    def run_event(self, event):
        raise util.HTTPInternal(f"Daemon thread accepts SHUTDOWN event only, got: {event}")

    def get_title(self):
        """Human-readable short title for this Daemon instance"""
        return f"{get_model().identity}@{config.HOSTNAME_FQDN}"

    def mbd_inspect(self):
        util.models().Daemon.Admin.mbd_check(get_model(), force=True)
        inspect = api.Setup.setup_inspect()
        inspect._run()  # pylint: disable=protected-access  # This should not be a "maintenance" call
        self.report = inspect.report
        util.attempt(inspect.report_save)


def get_model():
    model, created = util.models().Daemon.objects.get_or_create(id=1, defaults={"ftpd_bind": config.default_ftp_endpoint()})
    if created:
        LOG.info("New Daemon model instance created")
    return model


_DAEMON = None
_DAEMON_LOCK = threading.Lock()


def init():
    """Call once before you can use get(), start(), stop()"""
    # pylint: disable=global-statement
    global _DAEMON
    _DAEMON = Daemon()


def get():
    with _DAEMON_LOCK:
        return _DAEMON


def start():
    with _DAEMON_LOCK:
        if not get_model().mbd_is_active():
            raise util.HTTPUnavailable("Can't start: Daemon instance is deactivated")
        if not _DAEMON.is_alive():
            values.cache_clear()
            _DAEMON.ftpd.bind(get_model().ftpd_options)
            _DAEMON.start()


def stop():
    with _DAEMON_LOCK:
        if _DAEMON.is_alive():
            _DAEMON.shutdown()
            _DAEMON.join()
        _DAEMON.public_key_cache.close()
        values.cache_clear()
        init()


class Stopped():
    def __init__(self):
        self.start = get().is_alive()

    def __enter__(self):
        stop()

    def __exit__(self, type_, value_, tb_):
        if self.start:
            util.attempt(start)
