import threading
import logging

import django.conf

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

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 util.daemon().gnupg.pub_key:
            self.add_pub_key(util.daemon().gnupg.pub_key)
        for r in models.Remote.mbd_get_active_or_auto_reactivate():
            self.add_pub_key(r.key)
            LOG.debug(f"Remote key added for '{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 = 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(f"Checking user: {u}")
            uploader = None
            try:
                uploader = u.uploader
            except BaseException as e:
                LOG.warning(f"User '{u}' does not have an uploader profile (deliberately removed?): {e}")

            if uploader and uploader.mbd_is_active() and uploader.may_upload_to.all().filter(identity=repo_identity):
                LOG.debug(f"Adding uploader key for '{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(f"Adding keyring: {line}")
                self.add_keyring(line)

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


START_STOP_LOCK = threading.Lock()


class Daemon(threads.EventThread, metaclass=util.Singleton):
    @property
    def model(self):
        return models.get_daemon()

    def __init__(self):
        self.builder = builder.Builder(max_parallel_builds=self.model.max_parallel_builds)
        self.packager = packager.Packager()
        self.ftpd = ftpd.FtpD(endpoint=self.model.mbd_get_ftp_endpoint())
        self.crontab = cron.Tab()
        super().__init__(subthreads=[self.ftpd, self.packager, self.builder, self.crontab], name=str(self.model))

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

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

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

    def sync(self):
        self.model.refresh_from_db()
        self.ftpd.endpoint = self.model.mbd_get_ftp_endpoint()

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

    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"{self.model.identity}@{config.HOSTNAME_FQDN}"

    def mbd_start(self):
        with START_STOP_LOCK:
            if not self.model.mbd_is_active():
                raise util.HTTPUnavailable("Can't start: Daemon instance is deactivated")
            if not self.is_alive():
                self.ftpd.bind(self.model.ftpd_options)
                self.start()

    def mbd_stop(self):
        with START_STOP_LOCK:
            if self.is_alive():
                self.shutdown()
                self.join()
                self.public_key_cache.close()
                Daemon.destroy()

    def mbd_inspect(self):
        util.models().Daemon.Admin.mbd_check(util.models().get_daemon(), 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)


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

    def __enter__(self):
        util.daemon().mbd_stop()

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