"""Click CLI for the Navi Code terminal client."""

from __future__ import annotations

import asyncio
from pathlib import Path
from typing import TYPE_CHECKING

import click

from clients.terminal import api
from clients.terminal.config import settings
from clients.terminal.render import Renderer
from clients.terminal.state import StateManager
from clients.terminal.ws_client import NaviWebSocketClient

if TYPE_CHECKING:
    from clients.terminal.tui.tui_app import NaviCodeTui


@click.command(
    context_settings={
        "ignore_unknown_options": True,
        "allow_extra_args": True,
        "help_option_names": ["-h", "--help"],
    }
)
@click.argument("prompt", required=False)
@click.option("--base-url", default=None, help="Navi API base URL.")
@click.option("--ws-url", default=None, help="Navi WebSocket URL (defaults to base-url).")
@click.option("--profile-id", default=None, help="Profile to use for new sessions.")
@click.option("--new-session", is_flag=True, help="Create a new session even if state exists.")
@click.option(
    "--resume",
    "resume_session_id",
    default=None,
    help="Resume a specific session by id (prints the id on close so you can re-run with it).",
)
@click.option("--show-thinking", is_flag=True, help="Show model reasoning blocks.")
@click.option("--show-events/--no-events", default=True, help="Show tool call events.")
@click.option("--theme", default=None, help="TUI theme name to start with.")
@click.option("--mouse/--no-mouse", default=None, help="Override mouse support in the TUI.")
@click.option("--raw", is_flag=True, help="Use the plain CLI instead of the TUI.")
@click.version_option(version="0.1.0", prog_name="navi-code")
def main(
    prompt: str | None,
    base_url: str | None,
    ws_url: str | None,
    profile_id: str | None,
    new_session: bool,
    show_thinking: bool,
    show_events: bool,
    theme: str | None,
    mouse: bool | None,
    raw: bool,
    resume_session_id: str | None,
) -> None:
    """Navi Code — terminal client for Navi.

    Without PROMPT, runs the TUI. With PROMPT, sends one message and exits.
    Use --raw to run the plain CLI instead of the TUI.
    """
    if base_url:
        settings.base_url = base_url
    if ws_url:
        settings.ws_url = ws_url
    if show_thinking:
        settings.show_thinking = True
    settings.show_events = show_events

    if resume_session_id and new_session:
        raise click.ClickException("--resume and --new-session are mutually exclusive.")

    cwd = Path.cwd().resolve()

    if raw or prompt:
        _run_raw(prompt, new_session, profile_id, cwd, resume_session_id)
        return

    _run_tui(profile_id, new_session, theme, mouse, cwd, resume_session_id)


def _run_raw(
    prompt: str | None,
    new_session: bool,
    profile_id: str | None,
    cwd: Path,
    resume_session_id: str | None,
) -> None:
    state = StateManager()
    session_id = _resolve_session_id(state, new_session, profile_id, resume_session_id)
    if not session_id:
        raise click.ClickException("Failed to create or resume a session.")

    renderer = Renderer(show_thinking=settings.show_thinking, show_events=settings.show_events)
    client = NaviWebSocketClient(session_id, renderer=renderer, cwd=cwd)

    if prompt:
        asyncio.run(_run_one_shot(client, prompt))
        return

    asyncio.run(_run_interactive(client, state))


def _run_tui(
    profile_id: str | None,
    new_session: bool,
    theme: str | None,
    mouse: bool | None,
    cwd: Path,
    resume_session_id: str | None,
) -> None:
    from clients.terminal.tui.tui_app import NaviCodeTui

    app = NaviCodeTui(
        profile_id=profile_id,
        new_session=new_session,
        session_id=resume_session_id,
        theme_name=theme,
        cwd=cwd,
    )
    if mouse is not None:
        app._mouse_enabled = mouse
    app.run(mouse=app._mouse_enabled)
    _print_resume_hint(app)


def _resume_hint(session_id: str) -> str:
    """The exact command a user can re-run to continue this session."""
    return f"navi-code --resume {session_id}"


def _print_resume_hint(app: NaviCodeTui) -> None:
    """After the TUI exits, tell the user how to resume the session they had open.

    Printed here (not from on_unmount) because Textual has already restored the
    terminal by the time app.run() returns, so the hint lands on clean stdout.
    """
    session_id = getattr(app._ctx, "session_id", None)
    if not session_id:
        return
    click.secho(f"Session {session_id}", fg="bright_black")
    click.secho(f"Resume with: {_resume_hint(session_id)}", fg="cyan")


def _resolve_session_id(
    state: StateManager,
    force_new: bool,
    profile_id: str | None,
    resume_session_id: str | None = None,
) -> str | None:
    if resume_session_id:
        try:
            session = api.get_session(resume_session_id)
        except Exception as exc:
            raise click.ClickException(f"Failed to resume session {resume_session_id[:8]}: {exc}")
        state.set_session_id(session["session_id"])
        click.secho(
            f"Resumed session {session['session_id'][:8]} (profile {session['profile_id']})",
            fg="bright_black",
        )
        return session["session_id"]

    if not force_new:
        saved = state.get_session_id()
        if saved:
            try:
                session = api.get_session(saved)
                click.secho(
                    f"Resumed session {session['session_id'][:8]} (profile {session['profile_id']})",
                    fg="bright_black",
                )
                return session["session_id"]
            except Exception:
                state.clear_session_id()

    profile = profile_id or settings.default_profile_id
    try:
        session = api.create_session(profile)
    except Exception as exc:
        click.secho(f"Failed to create session: {exc}", fg="red", err=True)
        return None

    state.set_session_id(session["session_id"])
    click.secho(
        f"Created session {session['session_id'][:8]} (profile {session['profile_id']})",
        fg="green",
    )
    return session["session_id"]


async def _run_one_shot(client: NaviWebSocketClient, prompt: str) -> None:
    await client.run_one_shot(prompt)


async def _run_interactive(client: NaviWebSocketClient, state: StateManager) -> None:
    click.secho("Navi Code interactive mode. Type /quit to exit, /help for commands.", fg="cyan")

    await client.connect()
    receive_task = asyncio.create_task(client.receive_loop())

    try:
        while True:
            try:
                user_input = await asyncio.get_event_loop().run_in_executor(
                    None,
                    lambda: input(click.style("You: ", fg="blue", bold=True)),
                )
            except EOFError:
                break

            user_input = user_input.strip()
            if not user_input:
                continue

            if user_input.startswith("/"):
                handled = await _handle_command(user_input, state, client)
                if handled:
                    continue

            client.enqueue(user_input)
    finally:
        client.stop_input()
        receive_task.cancel()
        try:
            await receive_task
        except asyncio.CancelledError:
            pass
        await client.close()


async def _handle_command(cmd: str, state: StateManager, client: NaviWebSocketClient) -> bool:
    parts = cmd.split()
    head = parts[0].lower()

    if head == "/quit":
        raise click.exceptions.Exit(0)

    if head == "/help":
        click.echo("Commands:")
        click.echo("  /new              create a new session")
        click.echo("  /sessions         list server sessions")
        click.echo("  /switch <id>      switch to another session")
        click.echo("  /profile          show current session profile")
        click.echo("  /clear            clear local session state")
        click.echo("  /quit             exit")
        return True

    if head == "/new":
        try:
            session = api.create_session(settings.default_profile_id)
        except Exception as exc:
            click.secho(f"Failed to create session: {exc}", fg="red", err=True)
            return True
        state.set_session_id(session["session_id"])
        click.secho(
            f"Switched to new session {session['session_id'][:8]} (profile {session['profile_id']})",
            fg="green",
        )
        return True

    if head == "/sessions":
        try:
            sessions = api.list_sessions()
        except Exception as exc:
            click.secho(f"Failed to list sessions: {exc}", fg="red", err=True)
            return True
        for s in sessions:
            title = s.get("name", "") or s.get("preview", "")
            click.echo(f"  {s['session_id'][:8]}  {s.get('profile_id', 'unknown')}  {title}")
        return True

    if head == "/switch" and len(parts) == 2:
        target = parts[1]
        try:
            session = api.get_session(target)
        except Exception as exc:
            # Try to find by prefix
            try:
                sessions = api.list_sessions()
                matches = [s for s in sessions if s["session_id"].startswith(target)]
                if len(matches) == 1:
                    session = matches[0]
                else:
                    raise exc
            except Exception:
                click.secho(f"Session not found: {target}", fg="red", err=True)
                return True
        state.set_session_id(session["session_id"])
        click.secho(
            f"Switched to session {session['session_id'][:8]} (profile {session['profile_id']})",
            fg="green",
        )
        return True

    if head == "/profile":
        try:
            current = api.get_session(state.get_session_id() or "")
            click.echo(f"Profile: {current.get('profile_id', 'unknown')}")
            click.echo(f"Session: {current.get('session_id', 'unknown')}")
        except Exception as exc:
            click.secho(f"Failed to get session: {exc}", fg="red", err=True)
        return True

    if head == "/clear":
        state.clear_session_id()
        click.secho("Local session state cleared.", fg="green")
        return True

    return False


if __name__ == "__main__":
    main()
