import logging
from contextlib import closing

from mini_buildd import changes, daemon, events, package, threads, util

LOG = logging.getLogger(__name__)


class Package():
    def __init__(self, upload):
        self.upload = upload

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

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

        # Authenticate
        if self.repository.allow_unauthenticated_uploads:
            LOG.warning("Unauthenticated uploads allowed. Using '%s' unchecked", self.upload.file_name())
        else:
            with closing(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)
        daemon.get().events.log(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(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 set(self.upload.options.get("auto-ports", default=[])):
            try:
                info[to_dist_str] = (True, package.port(self.upload["Source"],
                                                        self.upload.dist.get(),
                                                        to_dist_str,
                                                        self.upload["Version"]))
            except BaseException as e:
                util.log_exception(LOG, "Auto port failed", e)
                info[to_dist_str] = (False, f"{util.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 = {}

            status = events.Type.FAILED
            try:
                self.repository.mbd_package_install(self.distribution, self.suite, self.upload, succeeded)
                status = events.Type.INSTALLED
                extra.setdefault("auto_ports", self.auto_ports())
            except BaseException as e:
                extra["error"] = util.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.get_events_path())
                for c in list(self.buildresults.values()) + list(self.buildrequests.values()):
                    c.move_to(c.get_events_path())
                daemon.get().events.log(status, self.upload, extra=extra)
            return True
        return False


class Packager(threads.EventThread):
    def __init__(self):
        super().__init__()
        self.packaging = {}

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

                    pkg = self.packaging[event.key]
                    pkg.add_buildresult(event)
                    if pkg.finish():
                        del self.packaging[pkg.upload.key]

                elif isinstance(event, changes.Upload):
                    pkg = Package(event)
                    if pkg.upload.key in self.packaging:
                        raise util.HTTPInternal(f"Stray upload (already packaging): {pkg.upload.key}")  # Incoming handler should always prevent this in the 1st place
                    pkg.upload_buildrequests()
                    self.packaging[pkg.upload.key] = pkg
                else:
                    raise util.HTTPInternal(f"Package Queue: Only ``Upload`` or ``Buildresult`` instances allowed: {event}")
        except Exception as e:
            daemon.get().events.log(events.Type.REJECTED, event, e)
