# SPDX-License-Identifier: Apache-2.0
# Standard
from enum import Enum, auto
import asyncio
import threading


class EventType(Enum):
    LOADING = auto()


class EventStatus(Enum):
    ONGOING = auto()
    DONE = auto()
    NOT_FOUND = auto()


class EventManager:
    """
    A thread-safe event manager to manage asynchronous events.
    Each event is identified by its type and a unique id.
    Events are organized by status for efficient counting.
    """

    def __init__(self) -> None:
        # Guard by lock
        # Structure: events[event_type][event_status][event_id] = future
        self.events: dict[EventType, dict[EventStatus, dict[str, asyncio.Future]]] = {
            et: {es: {} for es in EventStatus} for et in EventType
        }
        self.lock = threading.Lock()

    def add_event(
        self,
        event_type: EventType,
        event_id: str,
        future: asyncio.Future,
    ) -> None:
        """
        Add an event with the given type and id.
        """
        with self.lock:
            status_dict = self.events.get(event_type, None)
            assert status_dict is not None, (
                f"Invalid event type {event_type} in EventManager."
            )
            status_dict[EventStatus.ONGOING][event_id] = future

    def pop_event(
        self,
        event_type: EventType,
        event_id: str,
    ) -> asyncio.Future:
        """
        Pop and return the event with the given type and id.
        """
        with self.lock:
            status_dict = self.events.get(event_type, None)
            assert status_dict is not None, (
                f"Invalid event type {event_type} in EventManager."
            )
            done_events = status_dict[EventStatus.DONE]
            assert event_id in done_events, (
                f"Event {event_id} of type {event_type} is not done or not found."
            )
            return done_events.pop(event_id)

    def update_event_status(
        self,
        event_type: EventType,
        event_id: str,
        status: EventStatus,
    ) -> None:
        """
        Update the status of the event with the given type and id.
        """
        with self.lock:
            status_dict = self.events.get(event_type, None)
            assert status_dict is not None, (
                f"Invalid event type {event_type} in EventManager."
            )
            # Find the event in any status dict
            event = None
            for s in EventStatus:
                if event_id in status_dict[s]:
                    event = status_dict[s].pop(event_id)
                    break

            if event is None:
                raise KeyError(f"Event {event_id} of type {event_type} not found.")

            # Move to new status dict
            status_dict[status][event_id] = event

    def get_event_status(
        self,
        event_type: EventType,
        event_id: str,
    ) -> EventStatus:
        """
        Get the status of the event with the given type and id.
        """
        with self.lock:
            status_dict = self.events.get(event_type, None)
            assert status_dict is not None, (
                f"Invalid event type {event_type} in EventManager."
            )
            for status in EventStatus:
                if event_id in status_dict[status]:
                    return status
            return EventStatus.NOT_FOUND

    def get_events_count_by_status(
        self,
        event_type: EventType,
        status: EventStatus,
    ) -> int:
        """
        Get the count of events for the given event type and status.
        This is a lightweight O(1) operation using dict length.
        """
        with self.lock:
            status_dict = self.events.get(event_type, None)
            if status_dict is None:
                return 0
            return len(status_dict[status])
