"""
Live Narrator: Streams worker actions to the user in real-time.

Instead of:
  - User waits 30-60 seconds in silence
  - Gets a wall of text at the end
  - Or gets a voice-generated progress ping every 60s (which costs an LLM call)

Now:
  - User sees each action as it happens: "Reading config.py...", "Writing output.txt..."
  - No LLM calls needed — actions are narrated from tool metadata
  - Batched into short bursts so we don't spam the channel

This replaces the voice-based progress loop entirely for active tasks.
"""

import asyncio
import time
from collections import defaultdict
from typing import Callable, Awaitable

from loguru import logger


class LiveNarrator:
    """
    Collects worker actions and flushes them to the user periodically.

    Actions are buffered and sent as a single message every few seconds,
    so the user sees a live feed without getting spammed.
    """

    def __init__(
        self,
        flush_callback: Callable[[str, str, str], Awaitable[None]],
        flush_interval: float = 4.0,
        persona_name: str = "",
    ):
        """
        Args:
            flush_callback: async fn(channel, chat_id, message) to send updates
            flush_interval: seconds between flushes (lower = more real-time)
            persona_name: bot name for light personality in narration
        """
        self._callback = flush_callback
        self._interval = flush_interval
        self._persona = persona_name
        # task_id → list of (timestamp, action_text)
        self._buffers: dict[str, list[tuple[float, str]]] = defaultdict(list)
        # task_id → (channel, chat_id)
        self._origins: dict[str, tuple[str, str]] = {}
        self._running = False
        self._task: asyncio.Task | None = None

    def start(self) -> None:
        """Start the flush loop."""
        if not self._running:
            self._running = True
            self._task = asyncio.create_task(self._flush_loop())

    def stop(self) -> None:
        """Stop the flush loop."""
        self._running = False
        if self._task:
            self._task.cancel()

    def register_task(self, task_id: str, channel: str, chat_id: str) -> None:
        """Register a task's origin for routing narration messages."""
        self._origins[task_id] = (channel, chat_id)

    def unregister_task(self, task_id: str) -> None:
        """Clean up when a task completes."""
        self._buffers.pop(task_id, None)
        self._origins.pop(task_id, None)

    def narrate(self, task_id: str, action: str) -> None:
        """
        Record an action for narration.

        This is called synchronously from the worker's tool execution path.
        The flush loop will batch and send these asynchronously.
        """
        self._buffers[task_id].append((time.time(), action))

    async def _flush_loop(self) -> None:
        """Periodically flush buffered actions to users."""
        while self._running:
            try:
                await asyncio.sleep(self._interval)
                await self._flush_all()
            except asyncio.CancelledError:
                # Final flush on shutdown
                await self._flush_all()
                break
            except Exception as e:
                logger.error(f"Narrator flush error: {e}")

    async def _flush_all(self) -> None:
        """Flush all buffered actions."""
        for task_id in list(self._buffers.keys()):
            actions = self._buffers.pop(task_id, [])
            if not actions:
                continue

            origin = self._origins.get(task_id)
            if not origin:
                continue

            channel, chat_id = origin

            # Skip dashboard — it has its own polling
            if channel == "dashboard":
                continue

            # Format the actions into a compact update
            message = self._format_actions(actions)
            if message:
                try:
                    await self._callback(channel, chat_id, message)
                except Exception as e:
                    logger.warning(f"Failed to send narration for task {task_id}: {e}")

    def _format_actions(self, actions: list[tuple[float, str]]) -> str:
        """Format buffered actions into a single compact message."""
        if not actions:
            return ""

        # Deduplicate similar consecutive actions
        unique: list[str] = []
        for _, text in actions:
            if not unique or unique[-1] != text:
                unique.append(text)

        if len(unique) == 1:
            return f"⚙️ {unique[0]}"

        lines = [f"⚙️ {a}" for a in unique[-5:]]  # Show last 5 max
        if len(unique) > 5:
            lines.insert(0, f"  _{len(unique) - 5} more steps..._")

        return "\n".join(lines)
