"""Event handlers for rendering agent events to different outputs.

This module provides handlers that consume AgentEvents and render them
appropriately for different interfaces (CLI terminal, JSON stream for UI, etc.).
"""

import json
import sys
from typing import TextIO

from rich.console import Console
from rich.panel import Panel
from rich.markdown import Markdown
from rich.text import Text

from emdash.agent.events import AgentEvent, EventType


class RichConsoleHandler:
    """Renders agent events to terminal with Rich formatting.
    
    This handler produces the beautiful CLI output with panels, colors,
    and formatted text that users see when running emdash commands.
    """
    
    def __init__(
        self,
        console: Console | None = None,
        verbose: bool = True,
        show_tool_results: bool = False,
    ):
        """Initialize the Rich console handler.
        
        Args:
            console: Rich Console instance (creates new one if None)
            verbose: Whether to show detailed output
            show_tool_results: Whether to show full tool result data
        """
        self.console = console or Console()
        self.verbose = verbose
        self.show_tool_results = show_tool_results
    
    def handle(self, event: AgentEvent) -> None:
        """Handle an event by rendering it to the console."""
        if event.type == EventType.TOOL_START:
            self._handle_tool_start(event)
        elif event.type == EventType.TOOL_RESULT:
            self._handle_tool_result(event)
        elif event.type == EventType.THINKING:
            self._handle_thinking(event)
        elif event.type == EventType.PROGRESS:
            self._handle_progress(event)
        elif event.type == EventType.RESPONSE:
            self._handle_response(event)
        elif event.type == EventType.PARTIAL_RESPONSE:
            self._handle_partial_response(event)
        elif event.type == EventType.CLARIFICATION:
            self._handle_clarification(event)
        elif event.type == EventType.ERROR:
            self._handle_error(event)
        elif event.type == EventType.WARNING:
            self._handle_warning(event)
        elif event.type == EventType.SESSION_START:
            self._handle_session_start(event)
        elif event.type == EventType.SESSION_END:
            self._handle_session_end(event)
    
    def _handle_tool_start(self, event: AgentEvent) -> None:
        """Render tool start event."""
        if not self.verbose:
            return
        
        name = event.data.get("name", "unknown")
        args = event.data.get("args", {})
        
        # Format args for display (truncate long values)
        args_display = []
        for k, v in args.items():
            v_str = str(v)
            if len(v_str) > 50:
                v_str = v_str[:47] + "..."
            args_display.append(f"{k}: {v_str}")
        
        args_text = ", ".join(args_display) if args_display else ""
        
        self.console.print(Panel(
            f"[bold]{name}[/bold]\n[dim]{args_text}[/dim]" if args_text else f"[bold]{name}[/bold]",
            title="[cyan]Tool Call[/cyan]",
            border_style="dim cyan",
            padding=(0, 1),
        ))
    
    def _handle_tool_result(self, event: AgentEvent) -> None:
        """Render tool result event."""
        if not self.verbose:
            return
        
        name = event.data.get("name", "unknown")
        success = event.data.get("success", False)
        summary = event.data.get("summary", "")
        data = event.data.get("data")
        
        status = "[green]✓[/green]" if success else "[red]✗[/red]"
        
        content_parts = [f"{status} [bold]{name}[/bold]"]
        if summary:
            content_parts.append(f"\n[dim]{summary}[/dim]")
        
        # Show full data if verbose and show_tool_results is enabled
        if self.show_tool_results and data:
            data_str = json.dumps(data, indent=2, default=str)
            if len(data_str) > 2000:
                data_str = data_str[:2000] + "\n... (truncated)"
            content_parts.append(f"\n[dim]```\n{data_str}\n```[/dim]")
        
        self.console.print(Panel(
            "".join(content_parts),
            border_style="dim",
            padding=(0, 1),
        ))
    
    def _handle_thinking(self, event: AgentEvent) -> None:
        """Render thinking/progress message."""
        if not self.verbose:
            return
        
        message = event.data.get("message", "")
        self.console.print(f"[dim italic]💭 {message}[/dim italic]")
    
    def _handle_progress(self, event: AgentEvent) -> None:
        """Render progress update."""
        if not self.verbose:
            return
        
        message = event.data.get("message", "")
        percent = event.data.get("percent")
        
        if percent is not None:
            self.console.print(f"[dim]⏳ {message} ({percent:.0f}%)[/dim]")
        else:
            self.console.print(f"[dim]⏳ {message}[/dim]")
    
    def _handle_response(self, event: AgentEvent) -> None:
        """Render final response."""
        content = event.data.get("content", "")
        if content:
            self.console.print()
            try:
                self.console.print(Markdown(content))
            except Exception:
                # Fall back to plain text
                self.console.print(content)
    
    def _handle_partial_response(self, event: AgentEvent) -> None:
        """Render partial/streaming response."""
        content = event.data.get("content", "")
        if content:
            # Print without newline for streaming effect
            self.console.print(content, end="")
    
    def _handle_clarification(self, event: AgentEvent) -> None:
        """Render clarification request."""
        question = event.data.get("question", "")
        context = event.data.get("context", "")
        options = event.data.get("options", [])
        
        content_parts = [f"[bold yellow]❓ {question}[/bold yellow]"]
        if context:
            content_parts.append(f"\n[dim]{context}[/dim]")
        if options:
            content_parts.append("\n[dim]Options:[/dim]")
            for i, opt in enumerate(options, 1):
                content_parts.append(f"\n  {i}. {opt}")
        
        self.console.print(Panel(
            "".join(content_parts),
            title="[yellow]Clarification Needed[/yellow]",
            border_style="yellow",
        ))
    
    def _handle_error(self, event: AgentEvent) -> None:
        """Render error."""
        message = event.data.get("message", "Unknown error")
        details = event.data.get("details", "")
        
        content = f"[bold red]✗[/bold red] {message}"
        if details and self.verbose:
            content += f"\n[dim]{details}[/dim]"
        
        self.console.print(Panel(
            content,
            title="[red]Error[/red]",
            border_style="red",
        ))
    
    def _handle_warning(self, event: AgentEvent) -> None:
        """Render warning."""
        message = event.data.get("message", "")
        self.console.print(f"[yellow]⚠ {message}[/yellow]")
    
    def _handle_session_start(self, event: AgentEvent) -> None:
        """Render session start."""
        if not self.verbose:
            return
        
        agent_name = event.data.get("agent_name", "Agent")
        model = event.data.get("model", "")
        
        self.console.print()
        self.console.print(f"[bold cyan]{agent_name}[/bold cyan]")
        if model:
            self.console.print(f"[dim]Model: {model}[/dim]")
        self.console.print()
    
    def _handle_session_end(self, event: AgentEvent) -> None:
        """Render session end."""
        if not self.verbose:
            return
        
        self.console.print()
        self.console.print("[dim]─" * 40 + "[/dim]")


class JSONStreamHandler:
    """Outputs events as newline-delimited JSON for UI consumption.
    
    This handler is used when the CLI is invoked with --output-format json,
    allowing the VS Code extension (or other UIs) to parse structured events.
    
    Each event is output as a single JSON line (NDJSON format):
    {"type": "tool_start", "data": {...}, "timestamp": "...", "agent_name": "..."}
    """
    
    def __init__(
        self,
        output: TextIO | None = None,
        include_timestamp: bool = True,
        include_agent_name: bool = True,
    ):
        """Initialize the JSON stream handler.
        
        Args:
            output: Output stream (defaults to sys.stdout)
            include_timestamp: Whether to include timestamps in output
            include_agent_name: Whether to include agent name in output
        """
        self.output = output or sys.stdout
        self.include_timestamp = include_timestamp
        self.include_agent_name = include_agent_name
    
    def handle(self, event: AgentEvent) -> None:
        """Handle an event by outputting it as JSON."""
        output_dict = {
            "type": event.type.value,
            "data": self._serialize_data(event.data),
        }
        
        if self.include_timestamp:
            output_dict["timestamp"] = event.timestamp.isoformat()
        
        if self.include_agent_name and event.agent_name:
            output_dict["agent_name"] = event.agent_name
        
        try:
            json_line = json.dumps(output_dict, default=str)
            print(json_line, file=self.output, flush=True)
        except Exception:
            # Don't let serialization errors break the agent
            pass
    
    def _serialize_data(self, data: dict) -> dict:
        """Serialize data dict, handling non-JSON-serializable types."""
        result = {}
        for key, value in data.items():
            try:
                # Test if value is JSON serializable
                json.dumps(value)
                result[key] = value
            except (TypeError, ValueError):
                # Convert to string if not serializable
                result[key] = str(value)
        return result


class CollectingHandler:
    """Collects all events in memory for later retrieval.
    
    Useful for testing or when you want to process events after
    the agent has finished.
    """
    
    def __init__(self):
        """Initialize the collecting handler."""
        self.events: list[AgentEvent] = []
    
    def handle(self, event: AgentEvent) -> None:
        """Handle an event by storing it."""
        self.events.append(event)
    
    def get_events(self) -> list[AgentEvent]:
        """Get all collected events."""
        return self.events.copy()
    
    def get_events_by_type(self, event_type: EventType) -> list[AgentEvent]:
        """Get events of a specific type."""
        return [e for e in self.events if e.type == event_type]
    
    def clear(self) -> None:
        """Clear all collected events."""
        self.events.clear()


class CompositeHandler:
    """Dispatches events to multiple handlers.
    
    Use this when you want events to go to multiple destinations,
    e.g., both console output and a log file.
    """
    
    def __init__(self, handlers: list | None = None):
        """Initialize with a list of handlers.
        
        Args:
            handlers: List of handlers to dispatch to
        """
        self.handlers = handlers or []
    
    def add_handler(self, handler) -> None:
        """Add a handler."""
        self.handlers.append(handler)
    
    def handle(self, event: AgentEvent) -> None:
        """Handle an event by dispatching to all handlers."""
        for handler in self.handlers:
            try:
                handler.handle(event)
            except Exception:
                pass


