import threading
import queue
import logging

import django.conf

import mini_buildd.config
import mini_buildd.misc
import mini_buildd.call
import mini_buildd.changes
import mini_buildd.gnupg
import mini_buildd.threads
import mini_buildd.ftpd
import mini_buildd.package
import mini_buildd.packager
import mini_buildd.builder
import mini_buildd.events
import mini_buildd.cron

import mini_buildd.models.daemon
import mini_buildd.models.repository
import mini_buildd.models.chroot
import mini_buildd.models.gnupg
import mini_buildd.models.subscription

LOG = logging.getLogger(__name__)


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

    def __init__(self):
        super().__init__(tmpdir_options={"prefix": "gnupg-remotes-keyring-"})
        our_pub_key = mini_buildd.models.daemon.get().mbd_get_pub_key()
        if our_pub_key:
            self.add_pub_key(our_pub_key)
        for r in mini_buildd.models.gnupg.Remote.mbd_get_active_or_auto_reactivate():
            self.add_pub_key(r.key)
            LOG.info(f"Remote key added for '{r}': {r.key_long_id}: {r.key_name}")


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

    def __init__(self, repo_identity):
        super().__init__(tmpdir_options={"prefix": f"gnupg-uploaders-keyring-{repo_identity}-"})
        r = mini_buildd.models.repository.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.info(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.info(f"Adding keyring: {line}")
                self.add_keyring(line)

        # Always add our key too for internal builds
        our_pub_key = mini_buildd.models.daemon.get().mbd_get_pub_key()
        if our_pub_key:
            self.add_pub_key(our_pub_key)


START_STOP_LOCK = threading.Lock()


class Daemon(mini_buildd.threads.EventThread, metaclass=mini_buildd.misc.Singleton):
    @property
    def model(self):
        return mini_buildd.models.daemon.get()

    def __init__(self):
        # Vars that are (re)generated when the daemon model is updated
        self.incoming_queue = queue.Queue()

        self.builder = mini_buildd.builder.Builder(max_parallel_builds=self.model.build_queue_size)
        self.packager = mini_buildd.packager.Packager()
        self.ftpd = mini_buildd.ftpd.FtpD(endpoint=self.model.mbd_get_ftp_endpoint(), queue=self.incoming_queue, handler_options=self.model.ftpd_options)
        self.crontab = mini_buildd.cron.Tab()

        super().__init__(subthreads=[self.ftpd, self.packager, self.builder, self.crontab], events=self.incoming_queue, name="daemon")

        # Events
        self.events_queue = mini_buildd.events.Queue(mini_buildd.events.load(), maxlen=self.model.show_last_packages)

        #: Dict of functions to acquire possible attention strings (HTML support in 'main_menu_item.html' include).
        self.attention = {
            "events": "",   # No use case (yet)
            "builds": "",   # No use case (yet)
            "repositories": mini_buildd.mdls().source.Source.mbd_attention,
            "builders": lambda: mini_buildd.mdls().chroot.Chroot.mbd_attention() + mini_buildd.mdls().gnupg.Remote.mbd_attention(),
            "crontab": self.crontab.attention,
        }

    def __str__(self):
        return f"{'UP' if self.is_alive() else 'DOWN'}: {self.model}"

    def handshake_message(self):
        return self.model.mbd_gnupg().gpgme_sign(mini_buildd.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_queue.shutdown()
        self.sync()

    def run_event(self, event):
        if mini_buildd.changes.Buildrequest.match(event):
            self.builder.queue.put(event)
        else:
            self.packager.queue.put(event)  # User upload or build result: packager

    def check(self):
        """Global check -- check Daemon instance and implicitly all it's dependencies."""
        mini_buildd.models.daemon.Daemon.Admin.mbd_action(None, (self.model,), "check")

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

    def mbd_start(self):
        with START_STOP_LOCK:
            if not self.is_alive() and self.model.mbd_is_active():
                self.start()

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


class Stopped():
    def __init__(self, force_start=False):
        self.start = force_start or mini_buildd.get_daemon().is_alive()

    def __enter__(self):
        mini_buildd.get_daemon().mbd_stop()

    def __exit__(self, type_, value_, tb_):
        if self.start:
            mini_buildd.get_daemon().mbd_start()
