"""
Genesis Task Queue — High-level TaskManager interface.

Provides a clean API for enqueueing tasks by name, monitoring queue depths,
and inspecting dead-letter state without requiring callers to import Dramatiq
actors directly.

Usage
-----
    from core.task_queue import configure_broker, TaskManager

    configure_broker()
    manager = TaskManager()

    manager.enqueue("send_notification", "email", "user@example.com", {"msg": "Hi"})
    depths = manager.get_all_queue_depths()

Or use the module-level convenience function::

    from core.task_queue import enqueue
    enqueue("process_epoch_task", "2026-W09", queue="default")

# VERIFICATION_STAMP
# Story: M6.03 — manager.py — TaskManager and enqueue()
# Verified By: parallel-builder
# Verified At: 2026-02-25T00:00:00Z
# Tests: 9/9
# Coverage: 100%
"""
from __future__ import annotations

import logging
from typing import Any

logger = logging.getLogger(__name__)

# Queue names recognised by Genesis
VALID_QUEUES: frozenset[str] = frozenset({"critical", "high", "default", "low"})


class TaskManager:
    """
    High-level interface for the Genesis task queue.

    Decouples call sites from Dramatiq internals and provides queue depth
    inspection.  Designed to be instantiated per-request or as a singleton.

    Parameters
    ----------
    broker : optional
        A pre-configured Dramatiq broker.  When ``None`` (the default) the
        manager uses whatever broker was registered via ``configure_broker()``.
    """

    def __init__(self, broker: Any = None) -> None:
        self._broker = broker

    # ------------------------------------------------------------------
    # Enqueueing
    # ------------------------------------------------------------------

    def enqueue(
        self,
        task_name: str,
        *args: Any,
        queue: str = "default",
        **kwargs: Any,
    ) -> bool:
        """
        Dispatch a named task to the specified queue.

        Looks up the actor function by name in ``core.task_queue.workers``,
        then calls ``.send_with_options()`` (preferred — allows queue override)
        or ``.send()`` as a fallback.

        Parameters
        ----------
        task_name : str
            Name of the registered Dramatiq actor (e.g. ``"send_notification"``).
        *args :
            Positional arguments forwarded to the actor.
        queue : str
            Destination queue.  Must be one of ``critical``, ``high``,
            ``default``, ``low``.  Defaults to ``"default"``.
        **kwargs :
            Keyword arguments forwarded to the actor.

        Returns
        -------
        bool
            ``True`` if the task was dispatched successfully, ``False`` on any
            error (unknown actor, serialisation failure, broker unreachable).
        """
        if queue not in VALID_QUEUES:
            logger.error(
                "TaskManager.enqueue: invalid queue '%s'. Valid queues: %s",
                queue,
                sorted(VALID_QUEUES),
            )
            return False

        try:
            from core.task_queue import workers

            actor_fn = getattr(workers, task_name, None)
            if actor_fn is None:
                logger.error(
                    "TaskManager.enqueue: unknown task '%s'. "
                    "Register it as a @actor in core/task_queue/workers.py.",
                    task_name,
                )
                return False

            # send_with_options lets us override the queue at dispatch time
            if hasattr(actor_fn, "send_with_options"):
                actor_fn.send_with_options(args=args, kwargs=kwargs, queue_name=queue)
            elif hasattr(actor_fn, "send"):
                actor_fn.send(*args, **kwargs)
            else:
                logger.error(
                    "TaskManager.enqueue: '%s' is not a Dramatiq actor "
                    "(missing .send / .send_with_options).",
                    task_name,
                )
                return False

            logger.info(
                "TaskManager.enqueue: dispatched '%s' to queue '%s'",
                task_name,
                queue,
            )
            return True

        except Exception:
            logger.exception(
                "TaskManager.enqueue: unexpected error dispatching '%s'",
                task_name,
            )
            return False

    # ------------------------------------------------------------------
    # Queue depth monitoring
    # ------------------------------------------------------------------

    def get_queue_depth(self, queue_name: str = "default") -> int:
        """
        Return the current number of messages in the named queue.

        Uses the Redis broker's underlying client to call ``LLEN`` on the
        Dramatiq queue key (``dramatiq:<queue_name>``).

        Returns ``0`` when the broker is unavailable or an error occurs so
        callers can treat this as a safe, non-raising call.

        Parameters
        ----------
        queue_name : str
            One of ``critical``, ``high``, ``default``, ``low``.

        Returns
        -------
        int
            Queue depth (0 if unknown / unavailable).
        """
        broker = self._broker or _get_configured_broker()
        if broker is None:
            return 0

        try:
            redis_client = broker.client
            key = f"dramatiq:{queue_name}"
            depth = redis_client.llen(key)
            return depth if depth is not None else 0
        except Exception:
            logger.exception(
                "TaskManager.get_queue_depth: failed for queue '%s'",
                queue_name,
            )
            return 0

    def get_all_queue_depths(self) -> dict[str, int]:
        """
        Return the depth of all 4 priority queues in a single call.

        Returns
        -------
        dict[str, int]
            Mapping of queue name to depth, e.g.
            ``{"critical": 0, "high": 2, "default": 5, "low": 0}``.
        """
        return {q: self.get_queue_depth(q) for q in ("critical", "high", "default", "low")}


# ---------------------------------------------------------------------------
# Module-level convenience function
# ---------------------------------------------------------------------------


def enqueue(
    task_name: str,
    *args: Any,
    queue: str = "default",
    **kwargs: Any,
) -> bool:
    """
    Module-level convenience wrapper around ``TaskManager.enqueue()``.

    Instantiates a transient ``TaskManager`` and delegates immediately.
    Suitable for one-off dispatch calls where holding a long-lived manager
    instance is unnecessary.

    Parameters
    ----------
    task_name : str
        Name of the Dramatiq actor.
    *args :
        Positional arguments for the task.
    queue : str
        Destination queue (default: ``"default"``).
    **kwargs :
        Keyword arguments for the task.

    Returns
    -------
    bool
        ``True`` on successful dispatch, ``False`` on failure.
    """
    return TaskManager().enqueue(task_name, *args, queue=queue, **kwargs)


# ---------------------------------------------------------------------------
# Internal helper
# ---------------------------------------------------------------------------


def _get_configured_broker() -> Any:
    """Return the broker registered via configure_broker(), or None."""
    try:
        from core.task_queue.broker import get_broker
        return get_broker()
    except Exception:
        return None
