"""TUI command routing and CLI launch integration."""

from __future__ import annotations

import json
from collections import defaultdict
from collections.abc import Callable
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any

from cleveragents.application.container import get_container
from cleveragents.tui.app import CleverAgentsTuiApp, textual_available
from cleveragents.tui.persona.registry import PersonaRegistry
from cleveragents.tui.persona.state import PersonaState
from cleveragents.tui.slash_catalog import SLASH_COMMAND_SPECS


@dataclass(slots=True)
class TuiCommandRouter:
    """Slash command router used by the TUI prompt.

    Parameters
    ----------
    persona_registry:
        Registry of available personas.
    persona_state:
        Mutable state tracking the active persona per session.
    container_factory:
        Optional callable that returns the DI container.  Defaults to
        :func:`get_container` when *None*.  Accepting this parameter
        enables constructor-based dependency injection, which is
        essential for test isolation under ``multiprocessing.fork()``
        where ``unittest.mock.patch`` context managers do not propagate
        to child workers.
    """

    persona_registry: PersonaRegistry
    persona_state: PersonaState
    container_factory: Callable[[], Any] | None = field(default=None, repr=False)

    def _resolve_container(self) -> Any:
        """Return the DI container, using the injected factory or the global default."""
        if self.container_factory is not None:
            return self.container_factory()
        return get_container()

    def handle(self, raw: str, *, session_id: str) -> str:
        tokens = raw.strip().split()
        if not tokens:
            return "Empty command"
        if tokens[0] == "persona":
            return self._persona_command(tokens[1:], session_id=session_id)
        if tokens[0] == "session":
            return self._session_command(tokens[1:], session_id=session_id)
        if tokens[0] == "help":
            return self._help_command(tokens[1:])
        return f"Unknown command: /{raw}"

    def _help_command(self, tokens: list[str]) -> str:
        """Handle /help [command].

        With no arguments, lists all catalogued slash commands grouped by
        namespace.  With a command argument (e.g. ``/help persona:list``),
        renders command-specific help (description, usage, aliases).
        """
        if not tokens:
            return self._help_list_all()
        return self._help_for_command(tokens[0])

    def _help_list_all(self) -> str:
        """Return a formatted listing of all commands grouped by namespace."""
        groups: dict[str, list[str]] = defaultdict(list)
        for spec in SLASH_COMMAND_SPECS:
            groups[spec.group].append(f"  /{spec.command} — {spec.description}")
        lines: list[str] = ["Available slash commands:"]
        for group in sorted(groups):
            lines.append(f"\n{group}:")
            lines.extend(groups[group])
        return "\n".join(lines)

    def _help_for_command(self, command: str) -> str:
        """Return help text for a specific command, or a not-found message."""
        # Strip leading slash if the user typed e.g. /help /persona:list
        cmd = command.lstrip("/")
        for spec in SLASH_COMMAND_SPECS:
            if spec.command == cmd:
                return (
                    f"/{spec.command}\n"
                    f"  Group:       {spec.group}\n"
                    f"  Description: {spec.description}"
                )
        return f"Unknown command: /{cmd}"

    def _persona_command(self, tokens: list[str], *, session_id: str) -> str:
        if not tokens or tokens[0] == "list":
            names = [persona.name for persona in self.persona_registry.list_personas()]
            return ", ".join(names) if names else "No personas"
        if tokens[0] == "set":
            if len(tokens) < 2:
                return "Usage: /persona set <name>"
            persona = self.persona_state.set_active_persona(session_id, tokens[1])
            return f"Active persona: {persona.name}"
        return f"Unknown persona command: {' '.join(tokens)}"

    def _session_command(self, tokens: list[str], *, session_id: str) -> str:
        if not tokens or tokens[0] == "show":
            return f"Current session: {session_id}"
        if tokens[0] == "export":
            return self._session_export(tokens[1:], session_id=session_id)
        if tokens[0] == "import":
            return self._session_import(tokens[1:])
        return f"Unknown session command: {' '.join(tokens)}"

    def _session_export(self, tokens: list[str], *, session_id: str) -> str:
        """Handle /session:export [--format md] [path].

        Exports the current session to JSON (default) or Markdown.
        If a path is provided the file is written there; otherwise a
        confirmation message is returned for display in the TUI.
        """
        fmt = "json"
        path: str | None = None

        # Parse optional --format flag and optional path
        i = 0
        while i < len(tokens):
            if tokens[i] == "--format" and i + 1 < len(tokens):
                fmt = tokens[i + 1]
                i += 2
            else:
                path = tokens[i]
                i += 1

        if fmt not in ("json", "md", "txt"):
            return f"Invalid format: {fmt!r}. Use 'json', 'md', or 'txt'."

        try:
            container = self._resolve_container()
            service = container.session_service()
            export_data = service.export_session(session_id)

            if fmt == "md":
                from cleveragents.domain.models.core.session import (
                    MessageRole,
                    SessionMessage,
                )

                session = service.get(session_id)
                messages = [
                    SessionMessage(
                        message_id=m["message_id"],
                        role=MessageRole(m["role"]),
                        content=m["content"],
                        sequence=m["sequence"],
                        timestamp=m["timestamp"],
                        metadata=m.get("metadata", {}),
                        tool_call_id=m.get("tool_call_id"),
                    )
                    for m in export_data.get("messages", [])
                ]
                session.messages = messages
                content = session.as_export_markdown()
            elif fmt == "txt":
                from cleveragents.domain.models.core.session import (
                    MessageRole,
                    SessionMessage,
                )

                session = service.get(session_id)
                messages = [
                    SessionMessage(
                        message_id=m["message_id"],
                        role=MessageRole(m["role"]),
                        content=m["content"],
                        sequence=m["sequence"],
                        timestamp=m["timestamp"],
                        metadata=m.get("metadata", {}),
                        tool_call_id=m.get("tool_call_id"),
                    )
                    for m in export_data.get("messages", [])
                ]
                session.messages = messages
                content = session.as_export_plain_text()
            else:
                content = json.dumps(export_data, indent=2, default=str)

            if path is not None:
                out = Path(path)
                out.parent.mkdir(parents=True, exist_ok=True)
                out.write_text(content, encoding="utf-8")
                return f"Session exported to {path}"
            return f"Session exported ({fmt.upper()})"

        except Exception as exc:
            return f"Export failed: {exc}"

    def _session_import(self, tokens: list[str]) -> str:
        """Handle /session:import <path>.

        Imports a session from a JSON file previously produced by
        ``/session:export`` or ``agents session export``.
        """
        if not tokens:
            return "Usage: /session:import <path>"

        path = Path(tokens[0])
        if not path.exists():
            return f"File not found: {path}"

        try:
            raw = path.read_text(encoding="utf-8")
            data = json.loads(raw)
        except json.JSONDecodeError as exc:
            return f"Invalid JSON: {exc}"

        try:
            container = self._resolve_container()
            service = container.session_service()
            session = service.import_session(data)
            return f"Session imported: {session.session_id}"
        except Exception as exc:
            return f"Import failed: {exc}"


def run_tui(*, headless: bool = False) -> int:
    """Run the Textual TUI app or a headless startup check."""
    container = get_container()
    registry = container.persona_registry()
    state = container.persona_state(registry=registry)
    router = TuiCommandRouter(persona_registry=registry, persona_state=state)

    if headless:
        payload = {
            "textual_available": textual_available(),
            "persona_count": len(registry.list_personas())
            if registry.personas_dir.exists()
            else 0,
            "default_persona": state.active_name("default"),
            "help": router.handle("help", session_id="default"),
        }
        print(json.dumps(payload, indent=2))
        return 0

    app = CleverAgentsTuiApp(command_router=router, persona_state=state)
    app.run()
    return 0
