"""
Module: repl.session
Description: Main REPL session loop using prompt_toolkit for interactive command testing.

Implements:
    - docs/cli/reference.md#interactive-repl
    - docs/cli/reference.md#huitzo-pack-dev

See Also:
    - docs/cli/reference.md#repl-commands
    - docs/cli/reference.md#repl-features
"""

from __future__ import annotations

import asyncio
import json
import os
from pathlib import Path
from typing import Any

from prompt_toolkit import PromptSession
from prompt_toolkit.history import FileHistory
from rich.console import Console

from huitzo_cli.errors import (
    AuthenticationError,
    CommandNotFoundError,
    CommandValidationError,
    HuitzoCliError,
    NetworkError,
    SandboxCommandError,
    ServerError,
)
from huitzo_cli.repl.client import REPLClient
from huitzo_cli.repl.completer import CommandCompleter
from huitzo_cli.repl.formatter import OutputFormatter

# Persistent history file
_HISTORY_DIR = Path.home() / ".huitzo"
_HISTORY_FILE = _HISTORY_DIR / "repl_history"


class REPLSession:
    """Interactive REPL session for testing pack commands.

    Provides a prompt_toolkit-based loop with:
    - Meta-commands: help, list, describe, exec, exit/quit
    - Shorthand command execution: <cmd> <json-args>
    - Tab completion for meta-commands and pack commands
    - Persistent command history across sessions
    - Syntax-highlighted JSON output via rich
    """

    def __init__(
        self,
        base_url: str,
        auth_token: str | None = None,
        console: Console | None = None,
        verify: str | bool = True,
    ) -> None:
        """Initialize a REPL session.

        Args:
            base_url: Sandbox server URL (e.g. https://localhost:8080)
            auth_token: Bearer token for sandbox authentication
            console: Rich Console instance (creates one if not provided)
            verify: TLS verification — True, False, or path to CA cert file
        """
        self._client = REPLClient(base_url, auth_token, verify=verify)
        self._console = console or Console()
        self._formatter = OutputFormatter(self._console)
        self._completer = CommandCompleter()
        self._running = False
        self._pack_commands: list[str] = []
        self._command_metadata: dict[str, dict[str, Any]] = {}

    async def _refresh_commands(self) -> None:
        """Fetch the current command list from the sandbox and update the completer."""
        try:
            data = await self._client.list_commands()
            commands: list[dict[str, Any]] = data.get("commands", [])
            self._pack_commands = [c.get("name", "") for c in commands if c.get("name")]
            self._command_metadata = {c["name"]: c for c in commands if c.get("name")}
            self._completer.update_commands(self._pack_commands)
        except Exception:
            # Non-fatal: completion just won't have pack commands
            pass

    def _get_args_schema(self, name: str) -> dict[str, Any] | None:
        """Look up cached args schema for a command."""
        meta = self._command_metadata.get(name)
        if meta:
            return meta.get("args_schema")
        return None

    async def _handle_help(self) -> None:
        """Print REPL help."""
        self._console.print("\n[bold]REPL Commands:[/bold]")
        self._console.print("  [cyan]help[/cyan]                  Show this help message")
        self._console.print("  [cyan]list[/cyan]                  List available commands")
        self._console.print("  [cyan]describe <cmd>[/cyan]        Show command details")
        self._console.print("  [cyan]exec <cmd> <json>[/cyan]     Execute a command")
        self._console.print("  [cyan]<cmd> <json>[/cyan]          Shorthand execution")
        self._console.print("  [cyan]upload <path>[/cyan]         Upload a file to sandbox")
        self._console.print("  [cyan]files[/cyan]                 List uploaded files")
        self._console.print("  [cyan]clear-files[/cyan]           Delete uploaded files")
        self._console.print("  [cyan]exit[/cyan] / [cyan]quit[/cyan]           Exit the REPL")
        self._console.print()

    async def _handle_list(self) -> None:
        """List available pack commands."""
        try:
            data = await self._client.list_commands()
            commands: list[dict[str, Any]] = data.get("commands", [])
            self._formatter.format_command_list(commands)
        except (AuthenticationError, NetworkError, ServerError) as e:
            self._formatter.format_error(e.user_message)
        except Exception:
            self._formatter.format_error("Failed to list commands.")

    async def _handle_describe(self, name: str) -> None:
        """Describe a specific command.

        Args:
            name: Command name to describe
        """
        if not name:
            self._formatter.format_error("Usage: describe <command-name>")
            return

        try:
            cmd = await self._client.describe_command(name)
            if cmd is None:
                self._formatter.format_command_not_found(name, self._pack_commands)
            else:
                self._formatter.format_command_detail(cmd)
        except (AuthenticationError, NetworkError, ServerError) as e:
            self._formatter.format_error(e.user_message)
        except Exception:
            self._formatter.format_error("Failed to describe command.")

    async def _handle_exec(self, name: str, args_str: str) -> None:
        """Execute a pack command.

        Args:
            name: Command name
            args_str: JSON string of arguments
        """
        if not name:
            self._formatter.format_error("Usage: exec <command-name> [json-args]")
            return

        # Parse args
        args: dict[str, Any] = {}
        if args_str.strip():
            try:
                parsed = json.loads(args_str)
                if not isinstance(parsed, dict):
                    self._formatter.format_error("Arguments must be a JSON object")
                    return
                args = parsed
            except json.JSONDecodeError as e:
                self._formatter.format_error(f"Invalid JSON: {e}")
                return

        try:
            result = await self._client.execute_command(name, args)
            data = result.get("data", result)
            self._formatter.format_json(data)
        except SandboxCommandError as e:
            self._formatter.format_command_error(e.error_type, e.user_message, e.command)
        except CommandValidationError as e:
            schema = self._get_args_schema(name)
            self._formatter.format_validation_error(e.detail, args_schema=schema)
        except CommandNotFoundError:
            self._formatter.format_command_not_found(name, self._pack_commands)
        except (AuthenticationError, NetworkError, ServerError) as e:
            self._formatter.format_error(e.user_message)
        except Exception:
            self._formatter.format_error("An unexpected error occurred.")

    async def _handle_upload(self, path_str: str) -> None:
        """Upload a local file to the sandbox.

        Args:
            path_str: Local file path provided by the user
        """
        if not path_str:
            self._formatter.format_error("Usage: upload <file-path>")
            return

        # Expand ~ and resolve the path
        local_path = Path(path_str).expanduser().resolve()

        if not local_path.exists():
            self._formatter.format_error(f"File not found: {local_path}")
            return

        if not local_path.is_file():
            self._formatter.format_error(f"Not a file: {local_path}")
            return

        try:
            result = await self._client.upload_file(local_path)
            sandbox_path = result.get("path", "unknown")
            self._formatter.format_success(f"Uploaded as: {sandbox_path}")
        except (AuthenticationError, NetworkError, ServerError, HuitzoCliError) as e:
            self._formatter.format_error(e.user_message)
        except Exception:
            self._formatter.format_error("Upload failed.")

    async def _handle_files(self) -> None:
        """List files uploaded to the sandbox."""
        try:
            data = await self._client.list_files()
            files: list[dict[str, Any]] = data.get("files", [])
            self._formatter.format_file_list(files)
        except (AuthenticationError, NetworkError, ServerError, HuitzoCliError) as e:
            self._formatter.format_error(e.user_message)
        except Exception:
            self._formatter.format_error("Failed to list files.")

    async def _handle_clear_files(self) -> None:
        """Delete all uploaded files from the sandbox."""
        try:
            result = await self._client.clear_files()
            count = result.get("deleted", 0)
            self._formatter.format_success(f"Deleted {count} file(s).")
        except (AuthenticationError, NetworkError, ServerError, HuitzoCliError) as e:
            self._formatter.format_error(e.user_message)
        except Exception:
            self._formatter.format_error("Failed to clear files.")

    async def _dispatch(self, line: str) -> None:
        """Parse and dispatch a single REPL input line.

        Args:
            line: Raw input from the user
        """
        stripped = line.strip()
        if not stripped:
            return

        parts = stripped.split(maxsplit=1)
        cmd = parts[0].lower()
        rest = parts[1] if len(parts) > 1 else ""

        if cmd == "help":
            await self._handle_help()
        elif cmd == "list":
            await self._handle_list()
        elif cmd == "describe":
            await self._handle_describe(rest.strip())
        elif cmd == "exec":
            exec_parts = rest.split(maxsplit=1)
            exec_name = exec_parts[0] if exec_parts else ""
            exec_args = exec_parts[1] if len(exec_parts) > 1 else ""
            await self._handle_exec(exec_name, exec_args)
        elif cmd in ("exit", "quit"):
            self._running = False
        elif cmd == "upload":
            await self._handle_upload(rest.strip())
        elif cmd == "files":
            await self._handle_files()
        elif cmd == "clear-files":
            await self._handle_clear_files()
        elif cmd in self._pack_commands:
            # Shorthand: <command-name> <json-args>
            await self._handle_exec(cmd, rest)
        else:
            # Try as a command name anyway (the sandbox may have it)
            await self._handle_exec(cmd, rest)

    async def run(self) -> None:
        """Start the interactive REPL loop.

        Creates a prompt_toolkit session with persistent history and
        tab completion, then loops reading input and dispatching commands
        until the user types exit/quit or presses Ctrl-D.
        """
        # Ensure history directory exists
        _HISTORY_DIR.mkdir(parents=True, exist_ok=True)

        # Restrict history directory permissions on Unix
        if os.name != "nt":
            _HISTORY_DIR.chmod(0o700)

        history = FileHistory(str(_HISTORY_FILE))
        session: PromptSession[str] = PromptSession(
            history=history,
            completer=self._completer,
        )

        # Fetch initial command list for completions
        await self._refresh_commands()

        cmd_count = len(self._pack_commands)
        cmd_label = f"{cmd_count} command(s) loaded" if cmd_count else "no commands found"
        from rich.panel import Panel

        self._console.print(
            Panel(
                f"You're in an interactive testing environment\n"
                f"for your Intelligence Pack commands ({cmd_label}).\n"
                f"\n"
                f"Type a command name to execute it, or use:\n"
                f"  [cyan]list[/cyan]       - see available commands\n"
                f"  [cyan]help[/cyan]       - see all REPL commands\n"
                f"  [cyan]exit[/cyan]       - leave the sandbox",
                title="[bold]huitzo pack dev Sandbox[/bold]",
                border_style="green",
            )
        )
        self._running = True

        while self._running:
            try:
                # Run prompt_toolkit in a thread to avoid blocking the event loop
                line = await asyncio.get_running_loop().run_in_executor(
                    None, lambda: session.prompt("huitzo> ")
                )
                await self._dispatch(line)
            except KeyboardInterrupt:
                # Ctrl-C cancels current input, doesn't exit
                self._console.print()
                continue
            except EOFError:
                # Ctrl-D exits the REPL
                self._running = False

        await self._client.close()
        self._console.print("Goodbye!")
