"""``albus run`` — execute an agent interactively or single-shot."""

from __future__ import annotations

from typing import Annotated

import typer

from albusos.cli import app
from albusos.cli._helpers import err, load_workspace_or_exit, out, run_async


@app.command("run")
def run_cmd(
    agent_id: Annotated[str, typer.Argument(help="Agent ID to run (as discovered in workspace).")],
    message: Annotated[
        str | None,
        typer.Argument(help="User message. Omit to enter interactive REPL."),
    ] = None,
    root: Annotated[str, typer.Option("--root", "-r", help="Workspace root.")] = ".",
    thread_id: Annotated[
        str | None,
        typer.Option("--thread-id", "-t", help="Thread ID for multi-turn persistence."),
    ] = None,
    model: Annotated[
        str | None,
        typer.Option("--model", "-m", help="Override the agent's default model."),
    ] = None,
    max_steps: Annotated[
        int | None,
        typer.Option("--max-steps", help="Max tool-calling rounds per turn."),
    ] = None,
    trace: Annotated[
        bool,
        typer.Option("--trace", help="Print live execution trace to stderr."),
    ] = False,
    save_trace: Annotated[
        str | None,
        typer.Option(
            "--save-trace",
            help="Save an HTML trace report to this path after execution.",
        ),
    ] = None,
    json_output: Annotated[
        bool,
        typer.Option("--json", "-j", help="Emit structured JSON result (single-shot only)."),
    ] = False,
) -> None:
    """Run an agent — interactive REPL or single-shot execution.

    Examples:

        albus run researcher "What is quantum computing?"

        albus run researcher "Hello" --trace

        albus run researcher "Hello" --save-trace trace.html

        albus run researcher   # interactive REPL
    """
    ws = load_workspace_or_exit(root)

    if agent_id not in ws.agents:
        available = ", ".join(sorted(ws.agents)) or "(none)"
        err.print(f"[red]Error:[/red] Agent [bold]{agent_id}[/bold] not found.")
        err.print(f"  Available: {available}")
        raise typer.Exit(1)

    # Build session kwargs, applying overrides.
    agent_def = ws.agent(agent_id)
    if model is not None:
        agent_def = agent_def.model_copy(update={"model": model})
    if max_steps is not None:
        agent_def = agent_def.model_copy(update={"max_steps": max_steps})

    if message is None:
        # Interactive REPL
        run_async(ws.interactive(agent_id))
    elif json_output:
        # Single-shot with JSON output
        from core.runner import Session

        session = Session(agent_def)
        _attach_trace_handler(session, trace)
        result = run_async(session.run(message, thread_id=thread_id))
        out.print_json(result.model_dump_json(indent=2))
        _save_trace_if_requested(session, save_trace)
    else:
        # Single-shot with human-readable output
        from core.runner import Session

        session = Session(agent_def)
        _attach_trace_handler(session, trace)
        result = run_async(session.run(message, thread_id=thread_id))

        if result.success:
            out.print(result.response)
            meta: list[str] = []
            if result.steps_taken:
                meta.append(f"{result.steps_taken} steps")
            if result.tokens_used:
                meta.append(f"{result.tokens_used:,} tokens")
            if meta:
                err.print(f"  [{' | '.join(meta)}]")
        else:
            err.print(f"[red]Error:[/red] {result.response}")
            raise typer.Exit(1)

        # Print trace summary after the run
        if trace:
            _print_trace_summary(session)
        _save_trace_if_requested(session, save_trace)


def _attach_trace_handler(session: object, trace: bool) -> None:
    """Attach a ``GraphTraceLogger`` to the session's VM when tracing is on."""
    if not trace:
        return
    import sys

    from core.observability.handlers import GraphTraceLogger

    is_tty = getattr(sys.stderr, "isatty", lambda: False)()
    logger = GraphTraceLogger(use_color=is_tty)
    session._vm.add_listener(logger)


def _print_trace_summary(session: object) -> None:
    """Print a compact trace summary to stderr after a run."""
    trace = session.last_trace
    if trace is None:
        return

    from core.observability.trace import _format_gantt_duration

    err.print("")
    err.print(f"  [bold]Trace:[/bold] {trace.execution_id}")
    err.print(
        f"  [bold]Duration:[/bold] {_format_gantt_duration(trace.total_duration_ms)}  "
        f"[bold]Tokens:[/bold] {trace.total_tokens:,}  "
        f"[bold]Nodes:[/bold] {len(trace.spans)}"
    )
    for span in trace.spans:
        status_icon = {"success": "✓", "failure": "✗", "skipped": "○"}.get(span.status, "?")
        color = {"success": "green", "failure": "red", "skipped": "dim"}.get(span.status, "")
        dur = _format_gantt_duration(span.duration_ms)
        err.print(f"    [{color}]{status_icon}[/{color}] {span.node_id} ({span.node_type}) {dur}")


def _save_trace_if_requested(session: object, save_trace: str | None) -> None:
    """Save an HTML trace report to the given path if --save-trace was passed."""
    if save_trace is None:
        return
    trace = session.last_trace
    if trace is None:
        err.print("[yellow]Warning:[/yellow] No trace available to save.")
        return
    try:
        path = session.save_trace(save_trace)
        err.print(f"  Trace saved to [bold]{path}[/bold]")
    except Exception as exc:
        err.print(f"[yellow]Warning:[/yellow] Could not save trace: {exc}")
