import enum
import queue
import datetime
import threading
import collections
import weakref
import logging


import debian.debian_support

LOG = logging.getLogger(__name__)


class _NoLock():
    """
    Debug/temporary only: Experienced dead locks -- not reliably reproducible for now.

    May-be-related to our locking here. Use this to exclude our locking once we can
    reproduce the lockdown.
    """

    def acquire(self):
        pass

    def release(self):
        pass

    def __enter__(self):
        pass

    def __exit__(self, _type, _value, _traceback):
        pass


class Type(enum.Enum):
    BUILT = enum.auto()
    INSTALLED = enum.auto()
    FAILED = enum.auto()
    MIGRATED = enum.auto()
    REMOVED = enum.auto()


def to_type(typ):
    return typ if isinstance(typ, Type) else Type[typ]


class Event():
    def __init__(self, typ, package=None, version=None, distribution=None, extra=None, user=None, info=None, timestamp=None):
        self.type = to_type(typ)
        self.package = package
        self.version = version
        self.distribution = distribution
        self.extra = extra
        self.user = user
        self.info = info
        self.timestamp = timestamp or datetime.datetime.now(datetime.timezone.utc)

    def __str__(self):
        return f"{self.type.name}: {self.package}-{self.version} ({self.distribution}): {self.info}"

    def match(self, types=None, package=None, version=None, minimal_version=None, distribution=None):
        def match(match, value):
            return match is None or match == value

        # pylint: disable=too-many-boolean-expressions
        return \
            (types is None or self.type in [to_type(typ) for typ in types]) and \
            match(package, self.package) and \
            match(version, self.version) and \
            (minimal_version is None or debian.debian_support.Version(self.version) >= debian.debian_support.Version(minimal_version)) and \
            match(distribution, self.distribution)

    def to_json(self):
        return {"type": self.type.name,
                "package": self.package,
                "version": self.version,
                "distribution": self.distribution,
                "extra": self.extra,
                "user": self.user,
                "info": self.info,
                "timestamp": self.timestamp.isoformat()}

    @classmethod
    def from_json(cls, event):
        return Event(event["type"], package=event["package"], version=event["version"], distribution=event["distribution"], extra=event["extra"], user=event["user"], info=event["info"],
                     timestamp=datetime.datetime.fromisoformat(event["timestamp"]))


class EventException(BaseException):
    """Helper to be able to deliver events via exceptions. Packager should be eventually redesigned to make this obsolete."""

    def __init__(self, event):
        super().__init__()
        self.event = event


class Events(collections.deque):
    MAX_CLIENTS = 100

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._clients = {}
        self._lock = threading.Lock()

    def logevent(self, event):
        LOG.debug(f"Logging event: {event.type} {event.package}")

        with self._lock:
            self.appendleft(event)

            for _key, (weakref_obj, q) in self._clients.items():
                if weakref_obj() is not None:
                    LOG.info(f"Queuing event for object '{weakref_obj}'")
                    q.put(event)

    def log(self, *args, **kwargs):
        self.logevent(Event(*args, **kwargs))

    def attach(self, obj, since=None):
        with self._lock:
            # Garbage collection
            for key in [key for key, (weakref_obj, _q) in self._clients.items() if weakref_obj() is None]:
                LOG.info(f"Detach event queue from object '{key}': Dead.")
                del self._clients[key]

            if id(obj) not in self._clients:
                if len(self._clients) >= Events.MAX_CLIENTS:
                    raise Exception(f"Events: Max client limit ({Events.MAX_CLIENTS}) exceeded")
                LOG.info(f"Attaching new event queue to object: {obj}")
                self._clients[id(obj)] = (weakref.ref(obj), queue.Queue())
                if since:
                    for event in self:
                        if event.timestamp >= since:
                            self._clients[id(obj)][1].put(event)

        return self._clients[id(obj)][1]

    def get_last_packages(self):
        return sorted({event.package for event in self if event.package})

    def get_last_versions(self, package):
        return sorted({event.version for event in self if event.package == package})

    def to_json(self):
        return [event.to_json() for event in self]

    @classmethod
    def from_json(cls, events, maxlen):
        return Events([Event.from_json(event) for event in events], maxlen=maxlen)


class Attach():
    def __init__(self, events, *args, **kwargs):
        self.events = events.attach(self, *args, **kwargs)

    def get(self):
        return self.events.get()


def filter_iter(events_iter, types=None, package=None, version=None, minimal_version=None, distribution=None, stop=False):
    for event in events_iter():
        if event.match(types=types, package=package, version=version, minimal_version=minimal_version, distribution=distribution):
            yield event
            if stop:
                break
