"""
Doom Loop Detection

Detects repeated identical tool calls to prevent infinite loops.
"""

import hashlib
import json
from collections import deque
from dataclasses import dataclass
from datetime import datetime
from typing import Any, Literal, Optional

from rich.console import Console
from rich.panel import Panel
from rich.prompt import Prompt

LoopAction = Literal["continue", "stop", "debug"]


@dataclass
class ToolExecution:
    """
    Record of a tool execution.

    Attributes:
        tool: Tool name
        args_hash: Hash of tool arguments for comparison
        timestamp: When the tool was called
        args: Original arguments (for debugging)
    """

    tool: str
    args_hash: str
    timestamp: datetime
    args: dict[str, Any]


class DoomLoopDetector:
    """
    Detects repeated identical tool calls to prevent infinite loops.

    Features:
    - Tracks last 10 tool executions
    - Detects 3+ identical calls in a row
    - Warns user about potential loop
    - Provides Continue/Stop/Debug options
    - Clears history on successful execution
    """

    def __init__(
        self,
        max_history: int = 10,
        threshold: int = 3,
        console: Optional[Console] = None,
    ):
        """
        Initialize doom loop detector.

        Args:
            max_history: Maximum number of executions to track (default: 10)
            threshold: Number of identical calls to trigger warning (default: 3)
            console: Rich Console instance (creates default if None)
        """
        self.max_history = max_history
        self.threshold = threshold
        self.console = console or Console()

        # Track recent executions
        self.history: deque[ToolExecution] = deque(maxlen=max_history)

    def record(self, tool_name: str, **kwargs) -> None:
        """
        Record a tool execution.

        Args:
            tool_name: Name of tool being executed
            **kwargs: Tool arguments
        """
        # Hash arguments for comparison
        args_hash = self._hash_args(kwargs)

        # Create execution record
        execution = ToolExecution(
            tool=tool_name,
            args_hash=args_hash,
            timestamp=datetime.now(),
            args=kwargs,
        )

        # Add to history
        self.history.append(execution)

    def check_for_loop(self, tool_name: str, **kwargs) -> Optional[LoopAction]:
        """
        Check if current execution would create a doom loop.

        Args:
            tool_name: Name of tool about to be executed
            **kwargs: Tool arguments

        Returns:
            None if no loop detected, otherwise user's chosen action
        """
        # Hash current args
        current_hash = self._hash_args(kwargs)

        # Check for repeated calls
        repeated_count = self._count_repeated_calls(tool_name, current_hash)

        # Trigger warning if threshold reached
        if repeated_count >= self.threshold - 1:  # -1 because we haven't added current call yet
            return self._warn_user(tool_name, kwargs, repeated_count + 1)

        return None

    def clear_history(self) -> None:
        """Clear execution history."""
        self.history.clear()

    def _hash_args(self, args: dict[str, Any]) -> str:
        """
        Hash tool arguments for comparison.

        Args:
            args: Tool arguments

        Returns:
            Hash string
        """
        # Convert args to stable JSON string
        try:
            args_json = json.dumps(args, sort_keys=True, default=str)
        except (TypeError, ValueError):
            # Fallback for non-serializable args
            args_json = str(sorted(args.items()))

        # Hash the JSON string
        return hashlib.sha256(args_json.encode()).hexdigest()

    def _count_repeated_calls(self, tool_name: str, args_hash: str) -> int:
        """
        Count consecutive repeated calls from the end of history.

        Args:
            tool_name: Tool name
            args_hash: Hashed arguments

        Returns:
            Number of consecutive repeated calls
        """
        count = 0

        # Count from most recent backwards
        for execution in reversed(self.history):
            if execution.tool == tool_name and execution.args_hash == args_hash:
                count += 1
            else:
                # Stop at first non-match
                break

        return count

    def _warn_user(
        self,
        tool_name: str,
        kwargs: dict[str, Any],
        repeat_count: int,
    ) -> LoopAction:
        """
        Warn user about potential doom loop.

        Args:
            tool_name: Tool name
            kwargs: Tool arguments
            repeat_count: Number of repeated calls

        Returns:
            User's chosen action
        """
        self.console.print()

        # Build warning panel content
        content_lines = []
        content_lines.append(f"[bold yellow]⚠ Potential Infinite Loop Detected![/bold yellow]")
        content_lines.append("")
        content_lines.append(f"[bold cyan]Tool:[/bold cyan] {tool_name}")
        content_lines.append(f"[bold cyan]Repeated:[/bold cyan] {repeat_count} times in a row")
        content_lines.append("")
        content_lines.append("[dim]This may indicate an infinite loop or stuck agent.[/dim]")

        # Show args if not too many
        if len(kwargs) <= 5:
            content_lines.append("")
            content_lines.append("[bold cyan]Arguments:[/bold cyan]")
            for key, value in kwargs.items():
                value_str = str(value)
                if len(value_str) > 60:
                    value_str = value_str[:57] + "..."
                content_lines.append(f"  [yellow]{key}:[/yellow] {value_str}")

        content = "\n".join(content_lines)

        # Display warning panel
        panel = Panel(
            content,
            title="[bold red]Doom Loop Detection[/bold red]",
            border_style="red",
            padding=(1, 2),
        )

        self.console.print(panel)

        # Get user action
        self.console.print()
        self.console.print("[bold]What would you like to do?[/bold]")
        self.console.print("  [green]1[/green] or [green]c[/green] - Continue (allow this execution)")
        self.console.print("  [red]2[/red] or [red]s[/red] - Stop (cancel execution)")
        self.console.print("  [cyan]3[/cyan] or [cyan]d[/cyan] - Debug (show execution history)")
        self.console.print()

        # Get choice with validation
        valid_choices = ["1", "2", "3", "c", "s", "d"]
        choice = Prompt.ask(
            "Choose",
            choices=valid_choices,
            default="2",  # Default to stop for safety
        )

        # Map to action
        choice_map = {
            "1": "continue",
            "c": "continue",
            "2": "stop",
            "s": "stop",
            "3": "debug",
            "d": "debug",
        }

        action = choice_map[choice]

        # Show debug info if requested
        if action == "debug":
            self._show_debug_info()
            # Ask again after showing debug info
            return self._warn_user(tool_name, kwargs, repeat_count)

        return action  # type: ignore

    def _show_debug_info(self) -> None:
        """Show execution history for debugging."""
        self.console.print()
        self.console.print("[bold cyan]Execution History:[/bold cyan]")
        self.console.print()

        if not self.history:
            self.console.print("[dim]  No executions recorded yet.[/dim]")
            return

        # Show each execution
        for i, execution in enumerate(self.history, 1):
            time_str = execution.timestamp.strftime("%H:%M:%S")
            self.console.print(f"[bold]{i}.[/bold] [{time_str}] [yellow]{execution.tool}[/yellow]")

            # Show args briefly
            if execution.args:
                args_preview = ", ".join(
                    f"{k}={str(v)[:30]}..." if len(str(v)) > 30 else f"{k}={v}"
                    for k, v in list(execution.args.items())[:3]
                )
                if len(execution.args) > 3:
                    args_preview += f", ... ({len(execution.args) - 3} more)"
                self.console.print(f"   [dim]{args_preview}[/dim]")

        self.console.print()


# Module-level singleton for convenience
_default_detector = DoomLoopDetector()


def check_for_loop(tool_name: str, **kwargs) -> Optional[LoopAction]:
    """
    Convenience function to check for doom loop.

    Args:
        tool_name: Name of tool about to be executed
        **kwargs: Tool arguments

    Returns:
        None if no loop detected, otherwise user's chosen action

    Examples:
        >>> action = check_for_loop("bash", command="echo hello")
        >>> if action == "stop":
        ...     return  # Cancel execution
    """
    return _default_detector.check_for_loop(tool_name, **kwargs)


def record_execution(tool_name: str, **kwargs) -> None:
    """
    Convenience function to record a tool execution.

    Args:
        tool_name: Name of tool executed
        **kwargs: Tool arguments

    Examples:
        >>> record_execution("read", path="file.txt")
    """
    _default_detector.record(tool_name, **kwargs)


def clear_history() -> None:
    """
    Convenience function to clear execution history.

    Examples:
        >>> clear_history()  # Reset after successful operation
    """
    _default_detector.clear_history()
