"""Composite entity combining queue buffering with worker logic.

QueuedResource is a convenience base class that wires together the
Queue + QueueDriver + Worker pattern into a single entity. Users
subclass QueuedResource and implement handle_queued_event() to define
the work processing logic.

Internal structure:
- Queue: Buffers incoming events according to the configured policy
- QueueDriver: Polls the queue and feeds work to the worker
- Worker Adapter: Internal entity that delegates to handle_queued_event()

This composition is transparent to external code. Events sent to the
QueuedResource are automatically buffered and processed in order.
"""

from __future__ import annotations

import logging
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import TYPE_CHECKING

from happysimulator.components.queue import Queue
from happysimulator.components.queue_driver import QueueDriver
from happysimulator.components.queue_policy import FIFOQueue, QueuePolicy
from happysimulator.core.entity import Entity

if TYPE_CHECKING:
    from collections.abc import Generator

    from happysimulator.core.clock import Clock
    from happysimulator.core.event import Event

logger = logging.getLogger(__name__)


@dataclass
class _QueuedResourceWorkerAdapter(Entity):
    """Internal entity that bridges the queue driver to the resource's logic."""

    name: str
    _resource: QueuedResource

    def handle_event(self, event: Event):
        return self._resource.handle_queued_event(event)

    def has_capacity(self) -> bool:
        return self._resource.has_capacity()


class QueuedResource(Entity, ABC):
    """An entity fronted by a queue.

    External events should target the resource instance directly; the resource
    enqueues them and processes them via its internal queue/driver.

    Subclasses implement :meth:`handle_queued_event` as the resource's work logic.
    """

    def __init__(
        self,
        name: str,
        *,
        policy: QueuePolicy | None = None,
        queue_name: str | None = None,
        driver_name: str | None = None,
        worker_name: str | None = None,
    ):
        super().__init__(name)

        self._queue = Queue(
            name=queue_name or f"{name}.queue",
            egress=None,
            policy=policy if policy is not None else FIFOQueue(),
        )
        self._worker = _QueuedResourceWorkerAdapter(
            name=worker_name or f"{name}.worker",
            _resource=self,
        )
        self._driver = QueueDriver(
            name=driver_name or f"{name}.driver",
            queue=self._queue,
            target=self._worker,
        )
        self._queue.egress = self._driver
        logger.debug(
            "[%s] QueuedResource initialized with queue=%s driver=%s",
            name,
            self._queue.name,
            self._driver.name,
        )

    @property
    def queue(self) -> Queue:
        """Expose the underlying queue for advanced use-cases."""

        return self._queue

    @property
    def driver(self) -> QueueDriver:
        """Expose the underlying queue driver for advanced use-cases."""

        return self._driver

    @property
    def worker(self) -> Entity:
        """The internal worker entity receiving dequeued work."""

        return self._worker

    @property
    def depth(self) -> int:
        """Current queue depth (useful for :class:`~happysimulator.data.probe.Probe`)."""

        return self._queue.depth

    @property
    def stats_accepted(self) -> int:
        return self._queue.stats_accepted

    @property
    def stats_dropped(self) -> int:
        return self._queue.stats_dropped

    def set_clock(self, clock: Clock):
        """Attach a simulation clock to this resource and its internal components."""

        super().set_clock(clock)

        # Simulation only sets clocks for entities it is given. To make the
        # composition transparent, propagate the clock into the internal queue,
        # driver, and worker adapter.
        self._queue.set_clock(clock)
        self._driver.set_clock(clock)
        self._worker.set_clock(clock)

    def handle_event(self, event: Event):
        """Enqueue incoming work and notify the driver when needed."""

        # The queue stores the event as a payload (even though its `target` is
        # this resource). The driver later retargets it to the worker adapter.
        return self._queue.handle_event(event)

    @abstractmethod
    def handle_queued_event(
        self, event: Event
    ) -> (
        Generator[
            float | tuple[float, list[Event] | Event | None], None, list[Event] | Event | None
        ]
        | list[Event]
        | Event
        | None
    ):
        """Handle a dequeued work item.

        Subclasses implement their server behavior here.
        """

        raise NotImplementedError
