"""Production-friendly pretty terminal output.

This module provides beautiful terminal output when LOG_LEVEL is not set to DEBUG.
It replaces verbose logging with clean, informative UI elements.
"""

import os
import json
import time
from typing import Optional, Any
from dataclasses import dataclass, field
from contextlib import contextmanager

from rich.console import Console
from rich.panel import Panel
from rich.table import Table
from rich.text import Text
from rich.live import Live
from rich.spinner import Spinner
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn
from rich.markdown import Markdown
from rich.style import Style
from rich.box import ROUNDED, MINIMAL, SIMPLE


# Check if we're in production mode (no DEBUG logging)
def is_production_mode() -> bool:
    """Check if we should use pretty output (production mode)."""
    log_level = os.environ.get("LOG_LEVEL", "INFO").upper()
    return log_level not in ("DEBUG", "TRACE")


# Singleton console for consistent output
_console: Optional[Console] = None


def get_console() -> Console:
    """Get the shared console instance."""
    global _console
    if _console is None:
        _console = Console(highlight=False)
    return _console


@dataclass
class SessionInfo:
    """Information about the current session."""
    model: str
    provider: str
    context_window: int
    vision_support: bool = False
    reasoning_support: bool = False


@dataclass
class ToolCallInfo:
    """Information about a tool call."""
    name: str
    arguments: dict
    start_time: float = field(default_factory=time.time)
    end_time: Optional[float] = None
    result: Optional[str] = None
    success: bool = True

    @property
    def duration_ms(self) -> int:
        """Get duration in milliseconds."""
        if self.end_time is None:
            return int((time.time() - self.start_time) * 1000)
        return int((self.end_time - self.start_time) * 1000)


class PrettyOutput:
    """Production-friendly pretty terminal output."""

    # Tool name display mappings (shorter, friendlier names)
    TOOL_DISPLAY_NAMES = {
        "read_file": "📄 Read",
        "write_file": "✏️ Write",
        "edit_file": "🔧 Edit",
        "semantic_search": "🔍 Search",
        "text_search": "🔎 Text Search",
        "list_files": "📁 List",
        "execute_command": "⚡ Run",
        "grep_search": "🔍 Grep",
        "find_files": "📁 Find",
        "get_callers": "📞 Callers",
        "get_callees": "📤 Callees",
        "get_neighbors": "🔗 Neighbors",
        "expand_node": "🌳 Expand",
        "get_communities": "👥 Communities",
        "get_class_hierarchy": "🏛️ Classes",
        "get_file_dependencies": "📦 Deps",
        "get_impact_analysis": "💥 Impact",
        "web_search": "🌐 Web",
        "fetch_url": "🌐 Fetch",
        "ask_user": "❓ Ask",
        "create_todo": "✅ Todo",
        "update_todo": "✅ Todo",
    }

    # Tool argument display (which args to show)
    TOOL_DISPLAY_ARGS = {
        "read_file": ["file_path"],
        "write_file": ["file_path"],
        "edit_file": ["file_path"],
        "semantic_search": ["query"],
        "text_search": ["query"],
        "list_files": ["directory"],
        "execute_command": ["command"],
        "grep_search": ["pattern"],
        "find_files": ["pattern"],
        "get_callers": ["function_name"],
        "get_callees": ["function_name"],
        "get_neighbors": ["node_name"],
        "expand_node": ["node_id"],
        "web_search": ["query"],
        "fetch_url": ["url"],
    }

    def __init__(self):
        self.console = get_console()
        self._live: Optional[Live] = None
        self._current_tool: Optional[ToolCallInfo] = None
        self._tool_history: list[ToolCallInfo] = []
        self._session_info: Optional[SessionInfo] = None

    def show_session_header(
        self,
        model: str,
        provider: str = "openai",
        context_window: int = 128000,
        vision_support: bool = False,
        agent_name: Optional[str] = None,
    ):
        """Show a nice session header with model info.

        Args:
            model: Model name/ID
            provider: Provider name
            context_window: Context window size
            vision_support: Whether model supports vision
            agent_name: Optional custom agent name
        """
        if not is_production_mode():
            return

        self._session_info = SessionInfo(
            model=model,
            provider=provider,
            context_window=context_window,
            vision_support=vision_support,
        )

        # Create a nice header
        self.console.print()

        # Model info line
        model_short = self._shorten_model_name(model)
        provider_icon = self._get_provider_icon(provider)

        info_parts = [
            f"{provider_icon} [bold cyan]{model_short}[/bold cyan]",
        ]

        # Add capabilities
        caps = []
        if vision_support:
            caps.append("👁️ vision")
        if context_window >= 100000:
            caps.append(f"📚 {context_window // 1000}K ctx")

        if caps:
            info_parts.append(f"[dim]({', '.join(caps)})[/dim]")

        self.console.print(" ".join(info_parts))

        # Agent name if custom
        if agent_name:
            self.console.print(f"[dim]Agent:[/dim] [bold yellow]{agent_name}[/bold yellow]")

        self.console.print()

    def _shorten_model_name(self, model: str) -> str:
        """Shorten model name for display."""
        # Remove common prefixes/suffixes
        short = model

        # Handle OpenAI models
        if "gpt-4o" in model.lower():
            short = "GPT-4o"
        elif "gpt-4" in model.lower():
            short = "GPT-4"
        elif "gpt-3.5" in model.lower():
            short = "GPT-3.5"
        elif "o1" in model.lower() or "o3" in model.lower():
            if "o1-mini" in model.lower():
                short = "o1-mini"
            elif "o1" in model.lower():
                short = "o1"
            elif "o3-mini" in model.lower():
                short = "o3-mini"

        # Handle Anthropic models
        elif "claude" in model.lower():
            if "opus" in model.lower():
                short = "Claude Opus"
            elif "sonnet" in model.lower():
                short = "Claude Sonnet"
            elif "haiku" in model.lower():
                short = "Claude Haiku"
            else:
                short = "Claude"

        # Handle Fireworks models
        elif "accounts/fireworks" in model:
            short = model.split("/")[-1]

        return short

    def _get_provider_icon(self, provider: str) -> str:
        """Get icon for provider."""
        icons = {
            "openai": "🤖",
            "anthropic": "🧠",
            "fireworks": "🔥",
        }
        return icons.get(provider.lower(), "🤖")

    def start_tool_call(self, name: str, arguments: dict) -> ToolCallInfo:
        """Start tracking a tool call with a spinner.

        Args:
            name: Tool name
            arguments: Tool arguments dict

        Returns:
            ToolCallInfo for tracking
        """
        tool_info = ToolCallInfo(name=name, arguments=arguments)
        self._current_tool = tool_info

        if not is_production_mode():
            return tool_info

        # Show condensed tool call line
        display_name = self.TOOL_DISPLAY_NAMES.get(name, f"🔧 {name}")
        display_args = self._format_tool_args(name, arguments)

        # Show spinner with tool info
        self.console.print(
            f"[dim]├─[/dim] {display_name} [dim]{display_args}[/dim]",
            end="",
        )

        return tool_info

    def end_tool_call(
        self,
        tool_info: ToolCallInfo,
        result: Optional[str] = None,
        success: bool = True,
        show_result_preview: bool = True,
    ):
        """End a tool call and show result summary.

        Args:
            tool_info: The tool call info from start_tool_call
            result: Tool result string
            success: Whether the tool succeeded
            show_result_preview: Whether to show a preview of the result
        """
        tool_info.end_time = time.time()
        tool_info.result = result
        tool_info.success = success
        self._tool_history.append(tool_info)
        self._current_tool = None

        if not is_production_mode():
            return

        # Complete the line with duration and status
        duration = tool_info.duration_ms
        status_icon = "✓" if success else "✗"
        status_color = "green" if success else "red"

        # Build result info
        result_info = ""
        if result and show_result_preview:
            result_info = self._summarize_result(tool_info.name, result)

        self.console.print(
            f" [{status_color}]{status_icon}[/{status_color}] "
            f"[dim]{duration}ms[/dim]"
            f"{result_info}"
        )

    def _format_tool_args(self, name: str, arguments: dict) -> str:
        """Format tool arguments for display."""
        display_keys = self.TOOL_DISPLAY_ARGS.get(name, list(arguments.keys())[:2])

        parts = []
        for key in display_keys:
            if key in arguments:
                value = arguments[key]
                # Truncate long values
                if isinstance(value, str):
                    if len(value) > 50:
                        value = value[:47] + "..."
                    # For file paths, show just the filename
                    if "/" in value and key in ("file_path", "path", "directory"):
                        value = "..." + value.rsplit("/", 2)[-2] + "/" + value.rsplit("/", 1)[-1] if "/" in value else value
                        if len(value) > 40:
                            value = "..." + value[-37:]
                parts.append(str(value))

        return " ".join(parts) if parts else ""

    def _summarize_result(self, tool_name: str, result: str) -> str:
        """Summarize a tool result for display."""
        if not result:
            return ""

        # Try to parse as JSON for structured results
        try:
            data = json.loads(result)

            # Handle common result patterns
            if isinstance(data, dict):
                if "matches" in data:
                    count = len(data["matches"]) if isinstance(data["matches"], list) else data["matches"]
                    return f" [dim]→ {count} matches[/dim]"
                elif "files" in data:
                    count = len(data["files"]) if isinstance(data["files"], list) else data["files"]
                    return f" [dim]→ {count} files[/dim]"
                elif "results" in data:
                    count = len(data["results"]) if isinstance(data["results"], list) else data["results"]
                    return f" [dim]→ {count} results[/dim]"
                elif "content" in data:
                    lines = data["content"].count("\n") + 1 if isinstance(data["content"], str) else 0
                    return f" [dim]→ {lines} lines[/dim]"
                elif "error" in data:
                    return f" [dim red]→ error[/dim red]"
                elif "success" in data:
                    return " [dim]→ done[/dim]"
            elif isinstance(data, list):
                return f" [dim]→ {len(data)} items[/dim]"

        except (json.JSONDecodeError, TypeError):
            pass

        # For plain text, show line count
        if isinstance(result, str):
            lines = result.count("\n") + 1
            if lines > 1:
                return f" [dim]→ {lines} lines[/dim]"
            elif len(result) > 100:
                return f" [dim]→ {len(result)} chars[/dim]"

        return ""

    def show_thinking(self, message: str = "Thinking"):
        """Show a thinking indicator."""
        if not is_production_mode():
            return
        self.console.print(f"[dim]💭 {message}...[/dim]")

    def show_response_start(self):
        """Indicate that the LLM response is starting."""
        if not is_production_mode():
            return
        self.console.print()  # Add spacing before response

    def show_llm_stats(
        self,
        input_tokens: int,
        output_tokens: int,
        duration_ms: Optional[int] = None,
    ):
        """Show LLM call statistics.

        Args:
            input_tokens: Number of input tokens
            output_tokens: Number of output tokens
            duration_ms: Optional duration in milliseconds
        """
        if not is_production_mode():
            return

        # Format tokens
        def fmt(n: int) -> str:
            if n >= 1000:
                return f"{n/1000:.1f}K"
            return str(n)

        parts = [
            f"↑{fmt(input_tokens)}",
            f"↓{fmt(output_tokens)}",
        ]

        if duration_ms:
            parts.append(f"{duration_ms}ms")

        self.console.print(f"[dim]└─ {' · '.join(parts)}[/dim]")
        self.console.print()

    def show_tool_summary(self, count: int):
        """Show a summary of tool calls made."""
        if not is_production_mode() or count == 0:
            return

        self.console.print(f"[dim]Used {count} tool{'s' if count != 1 else ''}[/dim]")

    def show_error(self, message: str, details: Optional[str] = None):
        """Show an error message nicely."""
        self.console.print(f"[red]✗ {message}[/red]")
        if details and is_production_mode():
            self.console.print(f"[dim red]  {details}[/dim red]")

    def show_warning(self, message: str):
        """Show a warning message."""
        self.console.print(f"[yellow]⚠ {message}[/yellow]")

    def show_success(self, message: str):
        """Show a success message."""
        self.console.print(f"[green]✓ {message}[/green]")

    def show_info(self, message: str):
        """Show an info message."""
        if is_production_mode():
            self.console.print(f"[dim]{message}[/dim]")

    @contextmanager
    def progress(self, description: str):
        """Show a progress spinner.

        Usage:
            with pretty.progress("Loading"):
                do_something()
        """
        if not is_production_mode():
            yield
            return

        with self.console.status(f"[dim]{description}...[/dim]"):
            yield

    def clear_tool_history(self):
        """Clear the tool call history."""
        self._tool_history = []


# Singleton instance
_pretty_output: Optional[PrettyOutput] = None


def get_pretty_output() -> PrettyOutput:
    """Get the shared PrettyOutput instance."""
    global _pretty_output
    if _pretty_output is None:
        _pretty_output = PrettyOutput()
    return _pretty_output


# Convenience functions
def show_session_header(**kwargs):
    """Show session header."""
    get_pretty_output().show_session_header(**kwargs)


def start_tool_call(name: str, arguments: dict) -> ToolCallInfo:
    """Start a tool call."""
    return get_pretty_output().start_tool_call(name, arguments)


def end_tool_call(tool_info: ToolCallInfo, **kwargs):
    """End a tool call."""
    get_pretty_output().end_tool_call(tool_info, **kwargs)


def show_llm_stats(**kwargs):
    """Show LLM stats."""
    get_pretty_output().show_llm_stats(**kwargs)


def show_error(message: str, details: Optional[str] = None):
    """Show error."""
    get_pretty_output().show_error(message, details)


def show_warning(message: str):
    """Show warning."""
    get_pretty_output().show_warning(message)


def show_success(message: str):
    """Show success."""
    get_pretty_output().show_success(message)


def show_info(message: str):
    """Show info."""
    get_pretty_output().show_info(message)
