"""Built-in slash commands for Navi Code TUI."""

from __future__ import annotations

import datetime
import os
import subprocess
import sys

from clients.terminal import api
from clients.terminal.config import settings
from clients.terminal.tui.commands.base import BaseCommand, CommandMeta
from clients.terminal.tui.context import TuiContext
from clients.terminal.tui.events import SessionInfo, SessionListUpdated
from clients.terminal.tui.settings import get_tui_settings


class HelpCommand(BaseCommand):
    meta = CommandMeta(
        name="help",
        aliases=(),
        description="Show available slash commands.",
        keybind=None,
    )

    async def execute(self, ctx: TuiContext, args: str) -> None:
        from clients.terminal.tui.commands.registry import get_registry

        registry = get_registry()
        lines = ["[b]Slash commands[/b]"]
        for cmd in registry.all():
            aliases = f" ({', '.join(cmd.meta.aliases)})" if cmd.meta.aliases else ""
            key = f" [{cmd.meta.keybind}]" if cmd.meta.keybind else ""
            lines.append(f"  /{cmd.meta.name}{aliases}{key} — {cmd.meta.description}")
        ctx.chat_panel.handle_ws_event({"type": "status", "content": "\n".join(lines)})


class NewCommand(BaseCommand):
    meta = CommandMeta(
        name="new",
        aliases=("clear",),
        description="Start a new session.",
        keybind="ctrl+x n",
    )

    async def execute(self, ctx: TuiContext, args: str) -> None:
        try:
            session = api.create_session(settings.default_profile_id)
        except Exception as exc:
            ctx.chat_panel.handle_ws_event(
                {"type": "error", "message": f"Failed to create session: {exc}"}
            )
            return
        ctx.session_id = session["session_id"]
        ctx.profile_id = session.get("profile_id")
        ctx.state.set_session_id(session["session_id"])
        ctx.status_panel.set_session(session["session_id"])
        ctx.status_panel.set_profile(ctx.profile_id or settings.default_profile_id)
        ctx.chat_panel.handle_ws_event(
            {"type": "status", "content": f"Created session {session['session_id'][:8]}"}
        )
        await _broadcast_session_list(ctx)
        await _reconnect_ws(ctx)


class SessionsCommand(BaseCommand):
    meta = CommandMeta(
        name="sessions",
        aliases=("resume", "continue"),
        description="List and switch between sessions.",
        keybind="ctrl+x l",
    )

    async def execute(self, ctx: TuiContext, args: str) -> None:
        try:
            sessions = api.list_sessions()
        except Exception as exc:
            ctx.chat_panel.handle_ws_event(
                {"type": "error", "message": f"Failed to list sessions: {exc}"}
            )
            return
        lines = ["[b]Sessions[/b]"]
        for s in sessions:
            sid = s.get("session_id", "")
            marker = "● " if sid == ctx.session_id else "  "
            title = s.get("name", "") or s.get("preview", "")
            lines.append(f"{marker}{sid[:8]}  {s.get('profile_id', 'unknown')}  {title}")
        ctx.chat_panel.handle_ws_event({"type": "status", "content": "\n".join(lines)})
        await _broadcast_session_list(ctx)


class SwitchCommand(BaseCommand):
    meta = CommandMeta(
        name="switch",
        aliases=(),
        description="Switch to another session by id or prefix.",
        keybind=None,
    )

    async def execute(self, ctx: TuiContext, args: str) -> None:
        target = args.strip()
        if not target:
            ctx.chat_panel.handle_ws_event(
                {"type": "error", "message": "Usage: /switch <session_id_or_prefix>"}
            )
            return
        try:
            session = api.get_session(target)
        except Exception:
            try:
                sessions = api.list_sessions()
                matches = [s for s in sessions if s.get("session_id", "").startswith(target)]
                if len(matches) == 1:
                    session = matches[0]
                else:
                    raise Exception("no unique match")
            except Exception as exc:
                ctx.chat_panel.handle_ws_event(
                    {"type": "error", "message": f"Session not found: {target} ({exc})"}
                )
                return
        ctx.session_id = session["session_id"]
        ctx.profile_id = session.get("profile_id")
        ctx.state.set_session_id(session["session_id"])
        ctx.status_panel.set_session(session["session_id"])
        ctx.status_panel.set_profile(ctx.profile_id or settings.default_profile_id)
        ctx.chat_panel.handle_ws_event(
            {"type": "status", "content": f"Switched to {session['session_id'][:8]}"}
        )
        await _broadcast_session_list(ctx)
        await _reconnect_ws(ctx)


class ProfileCommand(BaseCommand):
    meta = CommandMeta(
        name="profile",
        aliases=(),
        description="Show current session profile.",
        keybind=None,
    )

    async def execute(self, ctx: TuiContext, args: str) -> None:
        if not ctx.session_id:
            ctx.chat_panel.handle_ws_event({"type": "error", "message": "No active session"})
            return
        try:
            session = api.get_session(ctx.session_id)
        except Exception as exc:
            ctx.chat_panel.handle_ws_event(
                {"type": "error", "message": f"Failed to get session: {exc}"}
            )
            return
        ctx.chat_panel.handle_ws_event(
            {
                "type": "status",
                "content": f"Profile: {session.get('profile_id')}\nSession: {session.get('session_id')}",
            }
        )


class QuitCommand(BaseCommand):
    meta = CommandMeta(
        name="quit",
        aliases=("exit", "q"),
        description="Exit Navi Code.",
        keybind="ctrl+x q",
    )

    async def execute(self, ctx: TuiContext, args: str) -> None:
        app = ctx.app()
        app.exit()


class ThinkingCommand(BaseCommand):
    meta = CommandMeta(
        name="thinking",
        aliases=(),
        description="Toggle thinking block visibility.",
        keybind=None,
    )

    async def execute(self, ctx: TuiContext, args: str) -> None:
        settings.show_thinking = not settings.show_thinking
        ctx.chat_panel.handle_ws_event(
            {
                "type": "status",
                "content": f"Thinking blocks: {'on' if settings.show_thinking else 'off'}",
            }
        )


class CompactCommand(BaseCommand):
    meta = CommandMeta(
        name="compact",
        aliases=(),
        description="Compact the current session (ask model to summarize context).",
        keybind="ctrl+x c",
    )

    async def execute(self, ctx: TuiContext, args: str) -> None:
        if ctx.ws_client:
            ctx.ws_client.enqueue("Please summarize and compact our conversation so far.")


class ThemesCommand(BaseCommand):
    meta = CommandMeta(
        name="themes",
        aliases=(),
        description="Open the theme picker.",
        keybind=None,
    )

    async def execute(self, ctx: TuiContext, args: str) -> None:
        app = ctx.app()
        current = getattr(app, "_theme_name", "gnexus-dark")

        def on_picked(theme_name: str | None) -> None:
            if theme_name is None:
                ctx.chat_panel.handle_ws_event(
                    {"type": "status", "content": "Theme selection cancelled"}
                )
                return
            app = ctx.app()
            app._theme_name = theme_name
            app._apply_theme()
            tui_settings = ctx.settings or get_tui_settings()
            tui_settings.theme = theme_name
            tui_settings.save()
            ctx.chat_panel.handle_ws_event(
                {"type": "status", "content": f"Theme set to {theme_name}"}
            )

        from clients.terminal.tui.screens.theme_picker import ThemePickerScreen

        app.push_screen(ThemePickerScreen(current), callback=on_picked)


class MouseCommand(BaseCommand):
    meta = CommandMeta(
        name="mouse",
        aliases=(),
        description="Toggle mouse support in the TUI (requires restart).",
        keybind=None,
    )

    async def execute(self, ctx: TuiContext, args: str) -> None:
        tui_settings = ctx.settings or get_tui_settings()
        if args.strip().lower() in ("on", "true", "1", "yes"):
            tui_settings.mouse = True
        elif args.strip().lower() in ("off", "false", "0", "no"):
            tui_settings.mouse = False
        else:
            tui_settings.mouse = not tui_settings.mouse
        tui_settings.save()
        state = "on" if tui_settings.mouse else "off"
        ctx.chat_panel.handle_ws_event(
            {
                "type": "status",
                "content": f"Mouse support set to {state}. Restart navi-code to apply.",
            }
        )


class ExportCommand(BaseCommand):
    meta = CommandMeta(
        name="export",
        aliases=("save",),
        description="Export current session to markdown and open $EDITOR.",
        keybind=None,
    )

    async def execute(self, ctx: TuiContext, args: str) -> None:
        if not ctx.session_id:
            ctx.chat_panel.handle_ws_event(
                {"type": "error", "message": "No active session to export"}
            )
            return
        try:
            session = api.get_session(ctx.session_id)
        except Exception as exc:
            ctx.chat_panel.handle_ws_event(
                {"type": "error", "message": f"Failed to get session: {exc}"}
            )
            return

        short_id = ctx.session_id[:8]
        timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
        filename = f"{short_id}_{timestamp}.md"
        exports_dir = ctx.state.state_dir / "exports"
        exports_dir.mkdir(parents=True, exist_ok=True)
        file_path = exports_dir / filename

        try:
            file_path.write_text(_render_export_markdown(session), encoding="utf-8")
        except OSError as exc:
            ctx.chat_panel.handle_ws_event(
                {"type": "error", "message": f"Failed to write export: {exc}"}
            )
            return

        _open_in_editor(str(file_path))
        ctx.chat_panel.handle_ws_event({"type": "status", "content": f"Exported to {file_path}"})


def _render_export_markdown(session: dict) -> str:
    lines: list[str] = []
    session_id = session.get("session_id", "unknown")
    profile_id = session.get("profile_id", "unknown")
    created = session.get("created_at", "")
    lines.append(f"# Navi Code Export — {session_id[:8]}")
    lines.append("")
    lines.append(f"- **Profile:** {profile_id}")
    lines.append(f"- **Session:** {session_id}")
    if created:
        lines.append(f"- **Created:** {created}")
    lines.append("")

    messages = session.get("messages", [])
    for msg in messages:
        role = msg.get("role", "unknown")
        content = msg.get("content", "")
        if not content:
            continue
        heading = role.capitalize()
        lines.append(f"## {heading}")
        lines.append("")
        lines.append(content)
        lines.append("")

    return "\n".join(lines)


def _open_in_editor(path: str) -> None:
    editor = os.environ.get("EDITOR")
    if not editor:
        editor = "notepad" if sys.platform == "win32" else "vi"
    # Detach so the TUI is not blocked while the editor runs.
    subprocess.Popen([editor, path], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)


async def _broadcast_session_list(ctx: TuiContext) -> None:
    """Refresh the sessions panel with the latest server state."""
    if not ctx.app:
        return
    try:
        sessions = api.list_sessions()
    except Exception:
        return
    info_list = [
        SessionInfo(
            id=s.get("session_id", ""),
            profile_id=s.get("profile_id", "unknown"),
            title=s.get("name", "") or s.get("preview", ""),
            created_at=s.get("created_at", ""),
        )
        for s in sessions
    ]
    ctx.app().post_message(SessionListUpdated(info_list, ctx.session_id))


async def _reconnect_ws(ctx: TuiContext) -> None:
    """Close old WebSocket and open a new one for the current session."""
    if ctx.ws_client:
        await ctx.ws_client.close()
    if not ctx.session_id:
        return
    from clients.terminal.tui.events import ConnectionStatusChanged
    from clients.terminal.ws_client import NaviWebSocketClient

    new_client = NaviWebSocketClient(ctx.session_id)
    ctx.ws_client = new_client
    try:
        await new_client.connect()
        app = ctx.app()
        app.run_worker(new_client.receive_loop)
        ctx.status_panel.set_connection(True, "")
        app.post_message(ConnectionStatusChanged(True, ""))
    except Exception as exc:
        ctx.status_panel.set_connection(False, str(exc))
        ctx.app().post_message(ConnectionStatusChanged(False, str(exc)))
