"""
Trigger queue - priority queue with sequence number assignment.

All operations flow through this queue:
- WebSocket messages from users
- Cron scheduled tasks
- DataSource bar updates
- Manual triggers

Queue assigns seq numbers on dequeue, executes triggers, and submits to coordinator.
"""

import asyncio
import logging
from typing import Optional

from .context import ExecutionContext, clear_execution_context, set_execution_context
from .coordinator import CommitCoordinator
from .types import Priority, PriorityTuple, Trigger

logger = logging.getLogger(__name__)


class TriggerQueue:
    """
    Priority queue for trigger execution.

    Key responsibilities:
    - Maintain priority queue (high priority dequeued first)
    - Assign sequence numbers on dequeue (determines commit order)
    - Execute triggers with context set
    - Submit results to CommitCoordinator
    - Handle execution errors gracefully
    """

    def __init__(self, coordinator: CommitCoordinator):
        """
        Initialize trigger queue.

        Args:
            coordinator: CommitCoordinator for handling commits
        """
        self._coordinator = coordinator
        self._queue: asyncio.PriorityQueue = asyncio.PriorityQueue()
        self._seq_counter = 0
        self._seq_lock = asyncio.Lock()
        self._processor_task: Optional[asyncio.Task] = None
        self._running = False

    async def start(self) -> None:
        """Start the queue processor"""
        if self._running:
            logger.warning("TriggerQueue already running")
            return

        self._running = True
        self._processor_task = asyncio.create_task(self._process_loop())
        logger.info("TriggerQueue started")

    async def stop(self) -> None:
        """Stop the queue processor gracefully"""
        if not self._running:
            return

        self._running = False

        if self._processor_task:
            self._processor_task.cancel()
            try:
                await self._processor_task
            except asyncio.CancelledError:
                pass

        logger.info("TriggerQueue stopped")

    async def enqueue(
        self,
        trigger: Trigger,
        priority_override: Optional[Priority | PriorityTuple] = None
    ) -> int:
        """
        Add a trigger to the queue.

        Args:
            trigger: Trigger to execute
            priority_override: Override priority (simple Priority or tuple)
                If None, uses trigger's priority/priority_tuple
                If Priority enum, creates single-element tuple
                If tuple, uses as-is

        Returns:
            Queue sequence number (appended to priority tuple)

        Examples:
            # Simple priority
            await queue.enqueue(trigger, Priority.USER_AGENT)
            # Results in: (Priority.USER_AGENT, queue_seq)

            # Tuple priority with event time
            await queue.enqueue(
                trigger,
                (Priority.DATA_SOURCE, bar_data['time'])
            )
            # Results in: (Priority.DATA_SOURCE, bar_time, queue_seq)

            # Let trigger decide
            await queue.enqueue(trigger)
        """
        # Get monotonic seq for queue ordering (appended to tuple)
        async with self._seq_lock:
            queue_seq = self._seq_counter
            self._seq_counter += 1

        # Determine priority tuple
        if priority_override is not None:
            if isinstance(priority_override, Priority):
                # Convert simple priority to tuple
                priority_tuple = (priority_override.value, queue_seq)
            else:
                # Use provided tuple, append queue_seq
                priority_tuple = priority_override + (queue_seq,)
        else:
            # Let trigger determine its own priority tuple
            priority_tuple = trigger.get_priority_tuple(queue_seq)

        # Priority queue: (priority_tuple, trigger)
        # Python's PriorityQueue compares tuples element-by-element
        await self._queue.put((priority_tuple, trigger))

        logger.debug(
            f"Enqueued: {trigger.name} with priority_tuple={priority_tuple}"
        )

        return queue_seq

    async def _process_loop(self) -> None:
        """
        Main processing loop.

        Dequeues triggers, assigns execution seq, executes, and submits to coordinator.
        """
        execution_seq = 0  # Separate counter for execution sequence

        while self._running:
            try:
                # Wait for next trigger (with timeout to check _running flag)
                try:
                    priority_tuple, trigger = await asyncio.wait_for(
                        self._queue.get(), timeout=1.0
                    )
                except asyncio.TimeoutError:
                    continue

                # Assign execution sequence number
                execution_seq += 1

                logger.info(
                    f"Dequeued: seq={execution_seq}, trigger={trigger.name}, "
                    f"priority_tuple={priority_tuple}"
                )

                # Execute in background (don't block queue)
                asyncio.create_task(
                    self._execute_trigger(execution_seq, trigger)
                )

            except Exception as e:
                logger.error(f"Error in process loop: {e}", exc_info=True)

    async def _execute_trigger(self, seq: int, trigger: Trigger) -> None:
        """
        Execute a trigger with proper context and error handling.

        Args:
            seq: Execution sequence number
            trigger: Trigger to execute
        """
        # Set up execution context
        ctx = ExecutionContext(
            seq=seq,
            trigger_name=trigger.name,
        )
        set_execution_context(ctx)

        # Record execution start with coordinator
        await self._coordinator.start_execution(seq, trigger)

        try:
            logger.info(f"Executing: seq={seq}, trigger={trigger.name}")

            # Execute trigger (can be long-running)
            commit_intents = await trigger.execute()

            logger.info(
                f"Execution complete: seq={seq}, {len(commit_intents)} commit intents"
            )

            # Submit for sequential commit
            await self._coordinator.submit_for_commit(seq, commit_intents)

        except Exception as e:
            logger.error(
                f"Execution failed: seq={seq}, trigger={trigger.name}, error={e}",
                exc_info=True,
            )

            # Notify coordinator of failure
            await self._coordinator.execution_failed(seq, e)

        finally:
            clear_execution_context()

    def get_queue_size(self) -> int:
        """Get current queue size (approximate)"""
        return self._queue.qsize()

    def is_running(self) -> bool:
        """Check if queue processor is running"""
        return self._running

    def __repr__(self) -> str:
        return (
            f"TriggerQueue(running={self._running}, queue_size={self.get_queue_size()})"
        )
