"""
Native REPL Core - Interactive REPL with full TUI integration

The main REPL class that integrates all components:
- Input processing (slash commands, shell, file references)
- TUI components (keybindings, fuzzy search, session management)
- Response rendering (Markdown, streaming)
- Tool execution with permissions
- Session persistence

Examples:
    # Basic usage
    repl = NativeREPL(project_path=Path.cwd())
    repl.run()

    # Custom configuration
    config = REPLConfig(
        model="gpt-4o",
        stream_responses=True,
        auto_save=True,
    )
    repl = NativeREPL(project_path=Path.cwd(), config=config)
    repl.run()
"""

import sys
from pathlib import Path
from typing import Any, Optional

from rich.console import Console

from groknroll import __version__
from groknroll.repl.config import InputType, ProcessedInput, REPLConfig, REPLState
from groknroll.repl.input_processor import InputProcessor
from groknroll.repl.renderer import ResponseRenderer
from groknroll.repl.session import REPLSessionManager
from groknroll.repl.streaming import StreamingRLMHandler
from groknroll.repl.tool_executor import ToolExecutor
from groknroll.tui.fuzzy_search import FuzzyFileSearch
from groknroll.tui.keybindings import KeybindingManager
from groknroll.tui.shell_runner import ShellRunner
from groknroll.tui.slash_commands import CommandType, SlashCommandHandler


class NativeREPL:
    """
    Native REPL with full TUI integration

    Provides an interactive command-line interface with:
    - Slash commands (/help, /save, /agent, /quit)
    - Shell commands (!ls, !git status)
    - File references (@filename)
    - Streaming LLM responses
    - Session persistence with auto-save

    Example:
        repl = NativeREPL(project_path=Path.cwd())
        repl.run()

        # Or with custom config
        config = REPLConfig(model="gpt-4o", stream_responses=True)
        repl = NativeREPL(project_path=Path.cwd(), config=config)
        repl.run()
    """

    def __init__(
        self,
        project_path: Optional[Path] = None,
        config: Optional[REPLConfig] = None,
        console: Optional[Console] = None,
    ):
        """
        Initialize Native REPL

        Args:
            project_path: Project root path
            config: REPL configuration
            console: Rich console instance
        """
        self.project_path = project_path or Path.cwd()
        self.config = config or REPLConfig()
        self.console = console or Console()

        # Initialize state
        self.state = REPLState()
        self.state.current_agent = self.config.default_agent

        # Initialize components
        self._init_components()

        # Setup callbacks
        self._setup_callbacks()

    def _init_components(self) -> None:
        """Initialize all REPL components"""
        # Input processing
        self.fuzzy_search = FuzzyFileSearch(workspace_root=self.project_path)
        self.input_processor = InputProcessor(
            workspace_root=self.project_path,
            fuzzy_search=self.fuzzy_search,
        )

        # TUI components
        self.slash_handler = SlashCommandHandler(
            available_agents=["build", "plan", "oracle"]
        )
        self.shell_runner = ShellRunner(
            cwd=self.project_path,
            console=self.console,
        )
        self.keybinding_manager = KeybindingManager()

        # Response rendering
        self.renderer = ResponseRenderer(
            console=self.console,
            stream_enabled=self.config.stream_responses,
        )

        # Tool execution
        self.tool_executor = ToolExecutor(
            project_path=self.project_path,
            console=self.console,
        )

        # Streaming LLM handler
        self.streaming_handler = StreamingRLMHandler(
            project_path=self.project_path,
            model=self.config.model,
        )

        # Session management
        self.session_manager = REPLSessionManager(
            project_path=self.project_path,
            auto_save=self.config.auto_save,
            auto_save_interval=self.config.auto_save_interval,
        )

    def _setup_callbacks(self) -> None:
        """Setup callbacks for TUI components"""
        # Slash command callbacks
        self.slash_handler.on_new_session = self._on_new_session
        self.slash_handler.on_save_session = self._on_save_session
        self.slash_handler.on_export_session = self._on_export_session
        self.slash_handler.on_switch_agent = self._on_switch_agent
        self.slash_handler.on_clear = self._on_clear
        self.slash_handler.on_quit = self._on_quit

        # Keybinding callbacks
        self.keybinding_manager.register_action("help", self._show_help)
        self.keybinding_manager.register_action("save", lambda: self._on_save_session(None))
        self.keybinding_manager.register_action("new", lambda: self._on_new_session(None))
        self.keybinding_manager.register_action("quit", self._on_quit)
        self.keybinding_manager.register_action("clear", self._on_clear)

    def run(self) -> None:
        """
        Run the main REPL loop

        Shows welcome banner and enters the interactive loop.
        Handles KeyboardInterrupt (Ctrl+C) gracefully.
        """
        # Show welcome banner
        self._show_welcome()

        # Start session
        self.session_manager.start_session(agent=self.state.current_agent)

        # Main loop
        while self.state.running:
            try:
                user_input = self._get_input()

                if not user_input:
                    continue

                self.process_input(user_input)

            except KeyboardInterrupt:
                self.console.print("\n[dim]Use /quit to exit[/dim]")
                continue

            except EOFError:
                self._on_quit()
                break

            except Exception as e:
                self.renderer.render_error(str(e))
                continue

        # Cleanup
        self._cleanup()

    def process_input(self, text: str) -> None:
        """
        Process user input and route to appropriate handler

        Args:
            text: Raw user input
        """
        # Preprocess input
        processed = self.input_processor.preprocess(text)

        # Route based on input type
        if processed.input_type == InputType.SLASH_COMMAND:
            self._handle_slash_command(processed)

        elif processed.input_type == InputType.SHELL_COMMAND:
            self._handle_shell_command(processed)

        elif processed.input_type == InputType.FILE_REFERENCE:
            self._handle_natural_language(processed)

        else:  # NATURAL_LANGUAGE
            self._handle_natural_language(processed)

    def _get_input(self) -> str:
        """
        Get user input with prompt

        Returns:
            User input string
        """
        # Show prompt
        agent_indicator = f"[dim]({self.state.current_agent})[/dim] "
        self.console.print(f"{agent_indicator}[bold cyan]>[/bold cyan] ", end="")

        # Get input
        return input().strip()

    def _show_welcome(self) -> None:
        """Show welcome banner"""
        self.renderer.render_welcome(
            version=__version__,
            agent=self.state.current_agent,
            custom_message=self.config.welcome_message,
        )

    def _show_help(self) -> None:
        """Show help information"""
        commands = [
            {"name": "/help", "description": "Show this help", "usage": "/help [command]"},
            {"name": "/new", "description": "Start new session", "usage": "/new [name]"},
            {"name": "/save", "description": "Save current session", "usage": "/save [name]"},
            {"name": "/export", "description": "Export to Markdown", "usage": "/export [file]"},
            {"name": "/agent", "description": "Switch agent", "usage": "/agent <name>"},
            {"name": "/clear", "description": "Clear screen", "usage": "/clear"},
            {"name": "/quit", "description": "Exit REPL", "usage": "/quit"},
            {"name": "!cmd", "description": "Run shell command", "usage": "!ls -la"},
            {"name": "@file", "description": "Reference file", "usage": "explain @main.py"},
        ]
        self.renderer.render_help(commands)

    def _handle_slash_command(self, processed: ProcessedInput) -> None:
        """
        Handle slash command

        Args:
            processed: Processed input
        """
        result = self.slash_handler.process(processed.original)

        if result.command_type == CommandType.HELP:
            if result.success:
                self._show_help()
            else:
                self.renderer.render_status(result.output, "warning")

        elif result.command_type == CommandType.QUIT:
            # Handled by callback
            pass

        elif result.command_type == CommandType.CLEAR:
            # Handled by callback
            pass

        elif result.command_type == CommandType.UNKNOWN:
            self.renderer.render_status(result.output, "warning")

        elif result.success:
            self.renderer.render_status(result.output, "success")

        else:
            self.renderer.render_status(result.output, "error")

    def _handle_shell_command(self, processed: ProcessedInput) -> None:
        """
        Handle shell command

        Args:
            processed: Processed input
        """
        command = processed.metadata.get("command", "")
        if not command:
            return

        result = self.shell_runner.run(command, show_output=True)

        # Track command execution
        if result.execution:
            self.session_manager.add_command(
                command=command,
                exit_code=result.execution.exit_code,
                output=result.execution.output,
            )

    def _handle_natural_language(self, processed: ProcessedInput) -> None:
        """
        Handle natural language input

        Args:
            processed: Processed input
        """
        # Build context with file references
        context = {}
        if processed.file_references:
            files = {}
            for file_path in processed.file_references:
                try:
                    full_path = self.project_path / file_path
                    if full_path.exists():
                        files[file_path] = full_path.read_text(encoding="utf-8")
                except Exception:
                    pass
            if files:
                context["files"] = files

        # Show processing indicator
        with self.renderer.show_progress("Thinking...") as progress:
            task = progress.add_task("Working...", total=None)

            # Get streaming response
            if self.config.stream_responses:
                tokens = []

                def on_token(token: str) -> None:
                    tokens.append(token)
                    # Update progress with token count
                    progress.update(task, description=f"Tokens: {len(tokens)}")

                result = self.streaming_handler.complete_streaming_sync(
                    prompt=processed.processed,
                    context=context,
                    on_token=on_token,
                )
            else:
                result = self.streaming_handler.complete_streaming_sync(
                    prompt=processed.processed,
                    context=context,
                )

        if result.success:
            # Render response
            self.renderer.render_response(result.response)

            # Update state
            self.state.update_usage(cost=result.cost, tokens=result.tokens)

            # Add turn to session
            self.session_manager.add_turn(
                user_input=processed.original,
                response=result.response,
                cost=result.cost,
                tokens=result.tokens,
            )

            # Show usage if configured
            if self.config.show_cost or self.config.show_tokens:
                usage_info = []
                if self.config.show_tokens:
                    usage_info.append(f"Tokens: {result.tokens}")
                if self.config.show_cost:
                    usage_info.append(f"Cost: ${result.cost:.4f}")
                self.renderer.render_status(" | ".join(usage_info), "info")

        else:
            self.renderer.render_error(
                "Failed to get response",
                details=result.error,
            )

    # Callback methods

    def _on_new_session(self, name: Optional[str]) -> Any:
        """Handle new session request"""
        # Save current session first
        if self.session_manager.has_unsaved_changes():
            self.session_manager.save()

        # Start new session
        session = self.session_manager.start_session(
            name=name,
            agent=self.state.current_agent,
        )

        # Reset state
        self.state.conversation_history.clear()
        self.state.total_cost = 0.0
        self.state.total_tokens = 0
        self.state.turn_count = 0

        return session

    def _on_save_session(self, name: Optional[str]) -> Any:
        """Handle save session request"""
        path = self.session_manager.save(name)
        if path:
            self.renderer.render_status(f"Session saved to {path}", "success")
        return path

    def _on_export_session(self, filename: Optional[str]) -> Any:
        """Handle export session request"""
        content = self.session_manager.export_session(format="markdown")
        if content:
            if filename:
                path = Path(filename)
                path.write_text(content)
                self.renderer.render_status(f"Exported to {path}", "success")
            else:
                self.console.print(content)
        return content

    def _on_switch_agent(self, agent: str) -> Any:
        """Handle agent switch request"""
        self.state.current_agent = agent
        self.session_manager.switch_agent(agent)
        self.tool_executor.set_agent(agent)
        return agent

    def _on_clear(self) -> None:
        """Handle clear request"""
        self.renderer.clear()

    def _on_quit(self) -> None:
        """Handle quit request"""
        self.state.running = False
        self.renderer.render_goodbye()

    def _cleanup(self) -> None:
        """Cleanup resources on exit"""
        # Close session manager (handles final save)
        self.session_manager.close()

    # Public API methods

    def switch_agent(self, agent: str) -> None:
        """
        Switch to a different agent

        Args:
            agent: Agent name (build, plan, oracle)
        """
        self._on_switch_agent(agent)
        self.renderer.render_status(f"Switched to {agent} agent", "info")

    def get_session_info(self) -> dict[str, Any]:
        """
        Get current session information

        Returns:
            Session info dict
        """
        session = self.session_manager.get_current_session()
        if session is None:
            return {}

        return {
            "name": session.name,
            "agent": session.agent,
            "turns": self.session_manager.get_turn_count(),
            "messages": len(session.messages),
            "files_changed": len(session.file_changes),
            "commands_run": len(session.commands),
            "total_cost": self.state.total_cost,
            "total_tokens": self.state.total_tokens,
        }

    def get_history(self, limit: int = 10) -> list[dict]:
        """
        Get conversation history

        Args:
            limit: Maximum turns to return

        Returns:
            List of turn dicts
        """
        turns = self.session_manager.get_turns(limit=limit)
        return [turn.to_dict() for turn in turns]


def run_repl(
    project_path: Optional[Path] = None,
    config: Optional[REPLConfig] = None,
) -> None:
    """
    Run the Native REPL (convenience function)

    Args:
        project_path: Project path
        config: REPL configuration
    """
    repl = NativeREPL(project_path=project_path, config=config)
    repl.run()
