"""
Intervention Dispatcher - Bridge between WebSocket push interventions and HandlerChain.

Receives push Intervention messages from GatewayWebSocketClient, converts them
to InterventionSignal format, queues them per trace_id, and exposes a non-blocking
poll API for integration handlers to consume.
"""

import asyncio
import logging
import threading
import uuid
from typing import Dict, List

from .handlers import HandlerChain, HandlerResult
from .validation import InterventionSignal
from .websocket_client import GatewayWebSocketClient, Intervention

logger = logging.getLogger("aigie.gateway.dispatcher")

# Map WS Intervention severity to InterventionSignal priority (lower = higher priority)
_SEVERITY_PRIORITY = {
    "critical": 1,
    "high": 3,
    "medium": 5,
    "low": 8,
}


class InterventionDispatcher:
    """Bridges gateway push interventions to the HandlerChain.

    - Registers an ``on_intervention`` callback on ``GatewayWebSocketClient``
    - Converts ``Intervention`` → ``InterventionSignal`` (adapter)
    - Queues pending interventions per ``trace_id``
    - Exposes ``pop_pending(trace_id)`` for handlers to poll (non-blocking)
    """

    def __init__(
        self,
        gateway_client: GatewayWebSocketClient,
        handler_chain: HandlerChain | None = None,
    ):
        self._gateway = gateway_client
        self._chain = handler_chain or HandlerChain.default()
        self._pending: Dict[str, List[InterventionSignal]] = {}
        self._lock = asyncio.Lock()
        self._sync_lock = threading.Lock()

        # Register as intervention callback on the WS client
        gateway_client.on_intervention(self._on_intervention)

    async def _on_intervention(self, intervention: Intervention) -> None:
        """Callback invoked by the WS client when a push intervention arrives."""
        signal = self._to_signal(intervention)
        with self._sync_lock:
            self._pending.setdefault(signal.trace_id, []).append(signal)

        # Acknowledge back to backend
        try:
            await self._gateway.acknowledge_intervention(intervention.intervention_id)
        except Exception as e:
            logger.debug(f"Failed to ack intervention {intervention.intervention_id}: {e}")

    def pop_pending(self, trace_id: str) -> InterventionSignal | None:
        """Return the highest-priority pending intervention for *trace_id*, or None.

        Thread-safe: uses a sync lock shared with ``_on_intervention()``.
        """
        with self._sync_lock:
            signals = self._pending.get(trace_id)
            if not signals:
                return None
            signals.sort(key=lambda s: s.priority)
            return signals.pop(0)

    async def process(
        self,
        signal: InterventionSignal,
        tool_name: str,
        tool_args: Dict,
    ) -> HandlerResult:
        """Run *signal* through the HandlerChain."""
        return await self._chain.handle(signal, tool_name, tool_args)

    def subscribe_trace(self, trace_id: str) -> None:
        """Subscribe to push interventions for *trace_id* on the gateway."""
        self._gateway.subscribe_to_trace(trace_id)

    def unsubscribe_trace(self, trace_id: str) -> None:
        """Unsubscribe and discard any queued interventions for *trace_id*."""
        self._gateway.unsubscribe_from_trace(trace_id)
        self._pending.pop(trace_id, None)

    # ------------------------------------------------------------------
    # Adapter: Intervention → InterventionSignal
    # ------------------------------------------------------------------

    @staticmethod
    def _to_signal(intervention: Intervention) -> InterventionSignal:
        """Convert a WS ``Intervention`` to an ``InterventionSignal``."""
        # Map WS intervention_type to handler-compatible type
        type_map = {
            "block": "break_loop",
            "modify": "inject_correction",
            "warn": "escalate",
            "escalate": "escalate",
            "trace_fix": "trace_fix",
        }
        intervention_type = type_map.get(
            intervention.intervention_type, intervention.intervention_type
        )

        # trace_fix interventions are always critical priority
        if intervention_type == "trace_fix":
            priority = _SEVERITY_PRIORITY["critical"]
        else:
            priority = _SEVERITY_PRIORITY.get(intervention.severity, 5)

        return InterventionSignal(
            id=intervention.intervention_id or str(uuid.uuid4()),
            trace_id=intervention.trace_id,
            span_id=None,
            intervention_type=intervention_type,
            confidence=1.0,
            reason=intervention.reason,
            evidence=intervention.metadata,
            payload=intervention.action or {},
            priority=priority,
        )
