"""Trigger infrastructure — wires pathway triggers to execution.

``TriggerSpec`` defines the trigger configuration for a pathway.
``TriggerRouter`` scans deployed pathways for triggers and subscribes
to the appropriate event sources (webhook bus, etc.).
"""

from __future__ import annotations

import asyncio
import logging
from typing import TYPE_CHECKING, Literal

from pydantic import BaseModel, Field

from core.common import JsonObject
from core.pathways.streaming.event_bus import WEBHOOK_BUS

if TYPE_CHECKING:
    from core.pathways.service import PathwayService
    from core.pathways.vm.executor import PathwayVM

logger = logging.getLogger(__name__)


class TriggerSpec(BaseModel):
    """Trigger configuration for a pathway.

    Attributes
    ----------
    type
        Trigger type: ``webhook`` (WebhookBus topic), ``schedule``
        (cron-like — not yet implemented), or ``event`` (custom).
    config
        Type-specific configuration.  For ``webhook``: ``{"topic": "..."}``
    """

    type: Literal["webhook", "schedule", "event"]
    config: JsonObject = Field(default_factory=dict)


class TriggerRouter:
    """Scans deployed pathways for triggers and wires them to execution.

    Usage::

        router = TriggerRouter(vm, pathway_service)
        await router.start()
        # ... later ...
        await router.stop()
    """

    def __init__(self, vm: PathwayVM, pathway_service: PathwayService) -> None:
        self._vm = vm
        self._service = pathway_service
        self._tasks: list[asyncio.Task[None]] = []
        self._queues: list[tuple[str, asyncio.Queue[dict]]] = []

    async def start(self) -> int:
        """Scan deployed pathways and subscribe to triggers.

        Returns
        -------
        int
            Number of triggers activated.
        """
        count = 0
        for pathway in self._service.list_all():
            trigger_raw = pathway.trigger
            if trigger_raw is None:
                continue

            try:
                spec = TriggerSpec.model_validate(trigger_raw)
            except Exception:
                logger.warning(
                    "Invalid trigger spec on pathway %s: %s",
                    pathway.id,
                    trigger_raw,
                )
                continue

            if spec.type == "webhook":
                topic = spec.config.get("topic")
                if not isinstance(topic, str) or not topic:
                    logger.warning("Webhook trigger on %s missing 'topic'", pathway.id)
                    continue

                q = WEBHOOK_BUS.subscribe(topic)
                self._queues.append((topic, q))
                task = asyncio.create_task(
                    self._webhook_listener(pathway.id, topic, q),
                    name=f"trigger:{pathway.id}:{topic}",
                )
                self._tasks.append(task)
                count += 1
                logger.info(
                    "Activated webhook trigger for pathway %s on topic %s",
                    pathway.id,
                    topic,
                )

            elif spec.type == "schedule":
                # Schedule triggers require an external scheduler (cron, Prefect, etc.)
                logger.info(
                    "Schedule trigger on pathway %s — use an external scheduler",
                    pathway.id,
                )

            elif spec.type == "event":
                topic = spec.config.get("topic")
                if isinstance(topic, str) and topic:
                    q = WEBHOOK_BUS.subscribe(topic)
                    self._queues.append((topic, q))
                    task = asyncio.create_task(
                        self._webhook_listener(pathway.id, topic, q),
                        name=f"trigger:{pathway.id}:{topic}",
                    )
                    self._tasks.append(task)
                    count += 1

        return count

    async def stop(self) -> None:
        """Cancel all trigger listeners and unsubscribe."""
        for task in self._tasks:
            task.cancel()
        await asyncio.gather(*self._tasks, return_exceptions=True)
        self._tasks.clear()

        for topic, q in self._queues:
            WEBHOOK_BUS.unsubscribe(topic, q)
        self._queues.clear()

    async def _webhook_listener(
        self,
        pathway_id: str,
        topic: str,
        queue: asyncio.Queue[dict],
    ) -> None:
        """Listen for webhook events and execute the pathway."""
        while True:
            try:
                payload = await queue.get()
            except asyncio.CancelledError:
                break

            pathway = self._service.load(pathway_id)
            if pathway is None:
                logger.warning("Trigger pathway %s no longer deployed", pathway_id)
                continue

            try:
                await self._vm.execute(pathway, inputs=payload)
            except Exception:
                logger.exception(
                    "Trigger execution failed for pathway %s (topic %s)",
                    pathway_id,
                    topic,
                )


__all__ = ["TriggerRouter", "TriggerSpec"]
