import os
import queue
import logging
from contextlib import closing

import mini_buildd.misc
import mini_buildd.changes
import mini_buildd.package
import mini_buildd.threads


LOG = logging.getLogger(__name__)


class Package():
    def __init__(self, event):
        self.upload = mini_buildd.changes.Upload(event, create_events=True)

        self.buildrequests, self.buildresults = {}, {}

        # Get/check repository, distribution and suite for changes
        self.repository, self.distribution, self.suite = mini_buildd.models.repository.parse_dist(self.upload.dist, check_uploadable=True)

        # Authenticate
        if self.repository.allow_unauthenticated_uploads:
            LOG.warning(f"Unauthenticated uploads allowed. Using '{self.upload.file_name}' unchecked")
        else:
            with closing(mini_buildd.daemon.UploadersKeyring(self.repository.identity)) as gpg:
                gpg.verify(self.upload.file_path)

        # Repository package prechecks
        self.repository.mbd_package_precheck(self.distribution, self.suite, self.upload["source"], self.upload["version"])

    def __str__(self):
        return self.upload.key

    def upload_buildrequests(self):
        self.buildrequests = self.upload.request_builds(self.repository, self.distribution, self.suite)
        mini_buildd.get_daemon().events_queue.log(mini_buildd.events.Type.PACKAGING, self.upload, extra={"buildrequests": {breq.get("architecture"): breq.to_event_json() for breq in self.buildrequests.values()}})

    def add_buildresult(self, bres):
        with closing(mini_buildd.daemon.RemotesKeyring()) as gpg:
            gpg.verify(bres.file_path)
        self.buildresults[bres["Architecture"]] = bres

    def auto_ports(self):
        """Try to serve all automatic ports. This will never fail -- failures will be event-logged only."""
        info = {}
        for to_dist_str in self.upload.options.get("auto-ports", default=[]):
            try:
                info[to_dist_str] = (True, mini_buildd.package.port(self.upload["Source"],
                                                                    self.upload.dist.get(),
                                                                    to_dist_str,
                                                                    self.upload["Version"]))
            except BaseException as e:
                mini_buildd.log_exception(LOG, "Auto port failed", e)
                info[to_dist_str] = (False, f"{mini_buildd.e2http(e)}")
        return info

    def finish(self):
        if self.buildrequests.keys() == self.buildresults.keys():
            succeeded = {arch: bres for arch, bres in self.buildresults.items() if bres.success(self.upload, self.distribution, ignore_checks=self.suite.experimental)}
            extra = {}

            try:
                self.repository.mbd_package_install(self.distribution, self.suite, self.upload, succeeded)
                status = mini_buildd.events.Type.INSTALLED
                extra.setdefault("auto_ports", self.auto_ports())
            except BaseException as e:
                status = mini_buildd.events.Type.FAILED
                extra["error"] = mini_buildd.e2http(e).rfc7807.to_json()
            finally:
                extra["buildresults"] = {arch: bres.to_event_json() for arch, bres in self.buildresults.items()}
                self.upload.move_to(self.upload.events_path)
                for c in list(self.buildresults.values()) + list(self.buildrequests.values()):
                    c.move_to(c.events_path)
                mini_buildd.get_daemon().events_queue.log(status, self.upload, extra=extra)
            return True
        return False


class Packager(mini_buildd.threads.EventThread):
    def __init__(self):
        self.queue = queue.Queue()
        super().__init__(events=self.queue, name="packager")
        self.packaging = {}

    def run_event(self, event):
        try:
            with self.lock:
                if mini_buildd.changes.Buildresult.match(event):
                    buildresult = mini_buildd.changes.Buildresult(event)
                    if buildresult.key not in self.packaging:
                        raise mini_buildd.HTTPBadRequest(f"Stray buildresult (not packaging here): {buildresult.bkey}")

                    package = self.packaging[buildresult.key]
                    package.add_buildresult(buildresult)
                    if package.finish():
                        del self.packaging[package.upload.key]

                else:
                    package = mini_buildd.packager.Package(event)
                    if package.upload.key in self.packaging:
                        raise mini_buildd.HTTPInternal(f"Stray upload (already packaging): {package.upload.key}")  # Incoming handler should always prevent this in the 1st place
                    package.upload_buildrequests()
                    self.packaging[package.upload.key] = package

        except BaseException as exception:
            mini_buildd.log_exception(LOG, f"Bogus incoming: {event}", exception)
            try:
                changes = mini_buildd.changes.Changes(event, create_events=True)
                changes.move_to(changes.events_path)
                mini_buildd.get_daemon().events_queue.log(mini_buildd.events.Type.REJECTED, changes, exception)
            except BaseException as e:
                mini_buildd.log_exception(LOG, "Parsing bogus incoming changes failed (removing file only)", e)
                mini_buildd.misc.attempt(os.remove, event)
