"""Dobby CLI — thin wrapper over the REST API daemon."""
from __future__ import annotations

import asyncio
import json
import os

import click
import yaml

from dobby.cli.client import DobbyClient


def run_async(coro):
    """Run an async coroutine from a synchronous click command."""
    return asyncio.run(coro)


def _client() -> DobbyClient:
    return DobbyClient()


# ---------------------------------------------------------------------------
# CLI group
# ---------------------------------------------------------------------------

@click.group()
def cli():
    """Dobby — meta-agent orchestrator."""


# ---------------------------------------------------------------------------
# init
# ---------------------------------------------------------------------------

@cli.command()
@click.option("--non-interactive", is_flag=True, default=False, help="Skip prompts, use defaults")
def init(non_interactive):
    """Set up Dobby for the first time.

    Creates a .env file from template, optionally configures Slack tokens and
    hub URL, registers the current directory as a project, and generates MCP
    config for Claude Code.
    """
    import shutil
    from pathlib import Path

    repo_root = Path(__file__).resolve().parent.parent.parent
    env_file = repo_root / ".env"
    env_example = repo_root / ".env.example"

    click.echo(click.style("Dobby — first-time setup", bold=True))
    click.echo()

    # ---- 1. Create .env from template -----------------------------------
    if env_file.exists():
        click.echo(f"  .env already exists at {env_file}")
    else:
        if env_example.exists():
            shutil.copy(env_example, env_file)
            click.echo(click.style("  Created .env from .env.example", fg="green"))
        else:
            env_file.touch()
            click.echo(click.style("  Created empty .env", fg="yellow"))

    # ---- 2. Prompt for hub URL ------------------------------------------
    env_lines = env_file.read_text().splitlines()
    env_dict: dict[str, str] = {}
    for line in env_lines:
        line = line.strip()
        if line and not line.startswith("#") and "=" in line:
            k, _, v = line.partition("=")
            env_dict[k.strip()] = v.strip().strip('"').strip("'")

    if not non_interactive:
        click.echo()
        hub_url = click.prompt(
            "  Hub URL (leave blank for local-only)",
            default=env_dict.get("DOBBY_URL", "http://127.0.0.1:8420"),
            show_default=True,
        )
        if hub_url:
            env_dict["DOBBY_URL"] = hub_url

        # ---- 3. Prompt for Slack tokens (optional) ----------------------
        click.echo()
        if click.confirm("  Configure Slack integration?", default=False):
            bot_token = click.prompt(
                "    SLACK_BOT_TOKEN", default=env_dict.get("SLACK_BOT_TOKEN", "")
            )
            app_token = click.prompt(
                "    SLACK_APP_TOKEN", default=env_dict.get("SLACK_APP_TOKEN", "")
            )
            if bot_token:
                env_dict["SLACK_BOT_TOKEN"] = bot_token
            if app_token:
                env_dict["SLACK_APP_TOKEN"] = app_token

        # ---- Write updated .env -----------------------------------------
        new_lines = []
        for line in env_lines:
            stripped = line.strip()
            if stripped and not stripped.startswith("#") and "=" in stripped:
                k, _, _ = stripped.partition("=")
                k = k.strip()
                if k in env_dict:
                    new_lines.append(f"{k}={env_dict[k]}")
                    del env_dict[k]
                    continue
            new_lines.append(line)
        # Append any new keys not already in the file
        for k, v in env_dict.items():
            new_lines.append(f"{k}={v}")
        env_file.write_text("\n".join(new_lines) + "\n")

    # ---- 4. Register current directory as a project ---------------------
    click.echo()
    cwd = os.getcwd()
    cwd_name = os.path.basename(cwd)

    if not non_interactive and click.confirm(
        f"  Register '{cwd_name}' ({cwd}) as a project?", default=True
    ):
        proj_name = click.prompt("    Project name", default=cwd_name)
        proj_context = click.prompt(
            "    Short description (optional)", default="", show_default=False
        )
        try:
            payload: dict = {"name": proj_name, "repo": cwd}
            if proj_context:
                payload["context"] = proj_context
            run_async(_client().post("/projects", json=payload))
            click.echo(click.style(f"    Registered project '{proj_name}'", fg="green"))
        except Exception:
            pass  # Daemon not running — skip silently, user can register later

    # ---- 5. Generate MCP config snippet ---------------------------------
    click.echo()
    dobby_url = env_dict.get("DOBBY_URL", "http://127.0.0.1:8420")
    mcp_config = {
        "mcpServers": {
            "dobby": {
                "command": "uv",
                "args": ["run", "--project", str(repo_root), "dobby-mcp"],
                "env": {
                    "DOBBY_URL": dobby_url,
                },
            }
        }
    }

    click.echo("  MCP config for Claude Code (~/.claude/.mcp.json or .mcp.json):")
    click.echo()
    click.echo(click.style(json.dumps(mcp_config, indent=2), fg="cyan"))

    click.echo()
    click.echo(click.style("Setup complete!", fg="green", bold=True))
    click.echo()
    click.echo("  Next steps:")
    click.echo("    1. Start the daemon:       dobby start")
    click.echo("    2. Register a project:     dobby register /path/to/repo --name myproject")
    click.echo("    3. Add a task:             dobby add 'Fix the login bug' --project myproject")
    click.echo("    4. Check status:           dobby status")


# ---------------------------------------------------------------------------
# add
# ---------------------------------------------------------------------------

@cli.command()
@click.argument("description")
@click.option("--project", required=True, help="Project name")
@click.option("--exit-criteria", default=None, help="Exit criteria for the task")
@click.option("--priority", default="normal", help="Task priority (low/normal/high/urgent)")
@click.option("--backend", type=click.Choice(["claude", "codex"]), default=None,
              help="Agent backend to use (claude/codex)")
@click.option("--cron", default=None, help="Cron schedule expression")
@click.option("--every", default=None, help="Repeat interval (e.g. 1h, 30m)")
@click.option("--after", default=None, help="Task ID that must complete first")
@click.option("--infra", default=None, help="Infrastructure requirements")
@click.option("--retries", default=None, type=int, help="Max retry count")
@click.option(
    "--checkpoint", is_flag=True, default=False, help="Require approval before big decisions"
)
@click.option("--timeout", default=None, type=int, help="Timeout in seconds")
def add(description, project, exit_criteria, priority, backend, cron, every, after,
        infra, retries, checkpoint, timeout):
    """Add a new task to the queue."""
    payload: dict = {
        "description": description,
        "project": project,
        "priority": priority,
    }
    if exit_criteria:
        payload["exit_criteria"] = exit_criteria
    if backend:
        payload["backend"] = backend
    if cron:
        payload["schedule"] = {"type": "cron", "expr": cron}
    elif every:
        payload["schedule"] = {"type": "interval", "every": every}
    elif after:
        payload["schedule"] = {"type": "after", "depends_on": after}
    if infra:
        payload["infra"] = infra
    if retries is not None:
        payload["retries"] = retries
    if checkpoint:
        payload["checkpoint"] = checkpoint
    if timeout is not None:
        payload["timeout"] = timeout

    task = run_async(_client().post("/tasks", json=payload))
    click.echo(click.style(f"Task created: {task['id']}", fg="green"))
    click.echo(f"  status : {task.get('status', 'unknown')}")
    click.echo(f"  project: {task.get('project', project)}")


# ---------------------------------------------------------------------------
# batch
# ---------------------------------------------------------------------------

@cli.command()
@click.argument("yaml_file", type=click.Path(exists=True))
def batch(yaml_file):
    """Dispatch a batch of tasks from a YAML file.

    The YAML file should contain a list of task dicts under a 'tasks' key:

    \b
      tasks:
        - project: spare
          description: "Fix warmup scheduler"
          priority: high
        - project: spare
          description: "Add eval harness"
    """
    with open(yaml_file) as f:
        data = yaml.safe_load(f)

    tasks = data.get("tasks", [])
    if not tasks:
        click.echo(click.style("No tasks found in YAML file.", fg="red"))
        raise SystemExit(1)

    result = run_async(_client().post("/batches", json={"tasks": tasks}))
    batch_id = result.get("batch_id", "unknown")
    created = result.get("tasks", [])

    click.echo(click.style(f"Batch created: {batch_id}", fg="green"))
    click.echo(f"  tasks: {len(created)}")
    for t in created:
        click.echo(f"    {t['id']}  {t['project']:<14} {t['description'][:40]}")


@cli.command("batch-status")
@click.argument("batch_id")
def batch_status(batch_id):
    """Show status of a batch."""
    data = run_async(_client().get(f"/batches/{batch_id}"))
    total = data.get("total", 0)
    completed = data.get("completed", 0)
    failed = data.get("failed", 0)
    running = data.get("running", 0)
    done = data.get("done", 0)

    click.echo(click.style(f"Batch: {batch_id}", bold=True))
    click.echo(
        f"  progress: {done}/{total} done ({completed} ok, {failed} failed, {running} running)"
    )

    if data.get("all_done"):
        click.echo(click.style("  status: ALL DONE", fg="green"))

    for t in data.get("tasks", []):
        status_colors = {
            "running": "green", "pending": "yellow", "failed": "red", "completed": "blue"
        }
        st = t.get("status", "?")
        color = status_colors.get(st, "white")
        click.echo(
            f"    {t['id']}  " + click.style(f"{st:<12}", fg=color) + f" {t['description'][:40]}"
        )


# ---------------------------------------------------------------------------
# plan
# ---------------------------------------------------------------------------

@cli.command()
@click.argument("description")
@click.option("--project", required=True, help="Project name")
def plan(description, project):
    """Create a planned (draft) task that requires review before execution."""
    payload = {
        "description": description,
        "project": project,
        "mode": "planned",
    }
    task = run_async(_client().post("/tasks", json=payload))
    click.echo(click.style(f"Plan created: {task['id']}", fg="cyan"))
    click.echo(f"  status: {task.get('status', 'drafting')}")


# ---------------------------------------------------------------------------
# status
# ---------------------------------------------------------------------------

@cli.command()
@click.argument("project", required=False)
def status(project):
    """Show task status, optionally filtered by project."""
    params = {}
    if project:
        params["project"] = project

    tasks = run_async(_client().get("/tasks", params=params or None))

    if not tasks:
        click.echo("No tasks found.")
        return

    # Table header
    header = f"{'ID':<16} {'PROJECT':<14} {'STATUS':<12} {'PRIORITY':<10} DESCRIPTION"
    click.echo(click.style(header, bold=True))
    click.echo("-" * 80)

    status_colors = {
        "running": "green",
        "pending": "yellow",
        "failed": "red",
        "completed": "blue",
        "cancelled": "white",
    }

    for t in tasks:
        tid = t.get("id", "?")
        proj = t.get("project", "?")
        st = t.get("status", "?")
        pri = t.get("priority", "normal")
        desc = t.get("description", "")[:40]
        color = status_colors.get(st, "white")
        click.echo(
            f"{tid:<16} {proj:<14} "
            + click.style(f"{st:<12}", fg=color)
            + f" {pri:<10} {desc}"
        )


# ---------------------------------------------------------------------------
# results
# ---------------------------------------------------------------------------

@cli.command()
@click.argument("task_id_or_project")
def results(task_id_or_project):
    """Show results for a task ID or all results for a project."""
    arg = task_id_or_project
    # Heuristic: if it looks like a task ID (starts with "t-"), use /results/{id}
    if arg.startswith("t-"):
        data = run_async(_client().get(f"/results/{arg}"))
    else:
        data = run_async(_client().get("/results", params={"project": arg}))

    if isinstance(data, list):
        for r in data:
            _print_result(r)
    else:
        _print_result(data)


def _print_result(r: dict):
    click.echo(click.style(f"Task: {r.get('task_id', '?')}", bold=True))
    click.echo(f"  status : {r.get('status', '?')}")
    summary = r.get("summary")
    if summary:
        click.echo(f"  summary: {summary}")
    cost = r.get("cost", {})
    if cost:
        click.echo(f"  cost   : ${cost.get('cost_usd', 0):.4f}")
    artifacts = r.get("artifacts", [])
    if artifacts:
        click.echo(f"  artifacts ({len(artifacts)}):")
        for a in artifacts:
            click.echo(f"    - {a}")
    metrics = r.get("metrics", {})
    if metrics:
        click.echo(f"  metrics: {metrics}")


# ---------------------------------------------------------------------------
# projects
# ---------------------------------------------------------------------------

@cli.command()
def projects():
    """List registered projects."""
    proj_list = run_async(_client().get("/projects"))

    if not proj_list:
        click.echo("No projects registered.")
        return

    header = f"{'NAME':<20} {'REPO':<40} CONTEXT"
    click.echo(click.style(header, bold=True))
    click.echo("-" * 80)
    for p in proj_list:
        name = p.get("name", "?")
        repo = p.get("repo", "?")
        ctx = p.get("context", "")
        click.echo(f"{name:<20} {repo:<40} {ctx}")


# ---------------------------------------------------------------------------
# register
# ---------------------------------------------------------------------------

@cli.command()
@click.argument("path")
@click.option("--name", required=True, help="Project name")
@click.option("--context", default=None, help="Project context description")
def register(path, name, context):
    """Register a project repository with Dobby."""
    payload: dict = {"name": name, "repo": path}
    if context:
        payload["context"] = context

    proj = run_async(_client().post("/projects", json=payload))
    click.echo(click.style(f"Registered project: {proj.get('name', name)}", fg="green"))
    click.echo(f"  repo: {proj.get('repo', path)}")


# ---------------------------------------------------------------------------
# cancel
# ---------------------------------------------------------------------------

@cli.command()
@click.argument("task_id")
def cancel(task_id):
    """Cancel a running or pending task."""
    task = run_async(_client().patch(f"/tasks/{task_id}", json={"status": "cancelled"}))
    click.echo(click.style(f"Task {task_id} cancelled.", fg="yellow"))
    click.echo(f"  status: {task.get('status', 'cancelled')}")


# ---------------------------------------------------------------------------
# retry
# ---------------------------------------------------------------------------

@cli.command()
@click.argument("task_id")
@click.option("--cascade", is_flag=True, default=False,
              help="Also retry all downstream dependent tasks")
def retry(task_id, cascade):
    """Retry a failed or cancelled task."""
    params = {"cascade": "true"} if cascade else None
    data = run_async(_client().post(f"/tasks/{task_id}/retry", params=params))
    if cascade and "retried" in data:
        retried = data["retried"]
        click.echo(click.style(f"Cascade retry: {len(retried)} task(s)", fg="green"))
        for t in retried:
            click.echo(f"  {t.get('id', '?'):<16} {t.get('status', '?')}")
    else:
        click.echo(click.style(f"Retry queued: {data.get('id', task_id)}", fg="green"))
        click.echo(f"  status: {data.get('status', 'pending')}")


# ---------------------------------------------------------------------------
# stats
# ---------------------------------------------------------------------------

@cli.command()
def stats():
    """Show aggregate statistics for the Dobby daemon."""
    data = run_async(_client().get("/stats"))
    click.echo(click.style("Dobby Stats", bold=True))
    click.echo("-" * 40)
    click.echo(f"  Pending   : {data.get('pending', 0)}")
    click.echo(f"  Running   : {data.get('running', 0)}")
    click.echo(f"  Completed : {data.get('completed', 0)}")
    click.echo(f"  Failed    : {data.get('failed', 0)}")
    click.echo(f"  Total cost: ${data.get('total_cost_usd', 0):.4f}")
    click.echo(f"  Tokens    : {data.get('total_tokens', 0):,}")


# ---------------------------------------------------------------------------
# detail-status
# ---------------------------------------------------------------------------

@cli.command("detail-status")
@click.argument("task_id")
def detail_status(task_id):
    """Show detailed live status of a running task — current activity, files touched, tools used."""
    data = run_async(_client().get(f"/tasks/{task_id}/detail-status"))

    click.echo(click.style(f"Detail Status: {data.get('task_id', task_id)}", bold=True))
    click.echo(f"  status  : {data.get('status', '?')}")
    click.echo(f"  activity: {data.get('current_activity', 'none') or 'none'}")

    files = data.get("files_touched", [])
    if files:
        click.echo(f"  files touched ({len(files)}):")
        for fp in files[:20]:
            click.echo(f"    - {fp}")
        if len(files) > 20:
            click.echo(f"    ... and {len(files) - 20} more")

    tools = data.get("tools_used", {})
    if tools:
        click.echo("  tools used:")
        for tool_name, count in sorted(tools.items(), key=lambda x: -x[1]):
            bar = "#" * min(count, 40)
            click.echo(f"    {tool_name:<12} {count:>3}  {bar}")

    tokens = data.get("tokens", {})
    if tokens:
        click.echo("  tokens:")
        if "total_tokens" in tokens:
            click.echo(f"    total : {tokens['total_tokens']:,}")
        if "cost_usd" in tokens:
            click.echo(f"    cost  : ${tokens['cost_usd']:.4f}")
        if "num_turns" in tokens:
            click.echo(f"    turns : {tokens['num_turns']}")


# ---------------------------------------------------------------------------
# logs
# ---------------------------------------------------------------------------

@cli.command()
@click.argument("task_id")
def logs(task_id):
    """Fetch and display logs for a task."""
    data = run_async(_client().get(f"/tasks/{task_id}/logs"))
    logs_text = data.get("logs", "")
    if not logs_text:
        click.echo(f"No log artifacts for {task_id}.")
        return
    click.echo(logs_text)


# ---------------------------------------------------------------------------
# export
# ---------------------------------------------------------------------------

@cli.command()
@click.option("--project", default=None, help="Filter by project name")
@click.option(
    "--format", "fmt", type=click.Choice(["json", "csv"]), default="json", help="Output format"
)
@click.option("--output", "-o", default=None, help="Output file path (default: stdout)")
def export(project, fmt, output):
    """Export task + result data as CSV or JSON."""
    params = {"format": fmt}
    if project:
        params["project"] = project

    if fmt == "csv":
        # For CSV, we need to get raw text response
        import aiohttp

        async def _fetch_csv():
            url = f"{_client().base_url}/export"
            async with aiohttp.ClientSession() as session, session.get(url, params=params) as resp:
                resp.raise_for_status()
                return await resp.text()

        text = run_async(_fetch_csv())
        if output:
            with open(output, "w") as f:
                f.write(text)
            click.echo(click.style(f"Exported to {output}", fg="green"))
        else:
            click.echo(text)
    else:
        data = run_async(_client().get("/export", params=params))
        formatted = json.dumps(data, indent=2)
        if output:
            with open(output, "w") as f:
                f.write(formatted)
            click.echo(click.style(f"Exported to {output}", fg="green"))
        else:
            click.echo(formatted)


# ---------------------------------------------------------------------------
# start
# ---------------------------------------------------------------------------

@cli.command()
@click.option("--config", default=None, help="Path to dobby config file")
@click.option("--daemon", is_flag=True, default=False, help="Run in daemon mode (background)")
@click.option(
    "--remote", is_flag=True, default=False,
    help="Also start a Claude Code remote control session"
)
@click.option("--remote-name", default="Dobby Hub", help="Name for the remote session")
@click.option("--remote-capacity", default=4, help="Max concurrent remote sessions")
def start(config, daemon, remote, remote_name, remote_capacity):
    """Start the Dobby daemon."""
    import os as _os

    from dobby.config import DobbyConfig
    from dobby.core.daemon import DobbyDaemon

    # Auto-enable remote if DOBBY_REMOTE=1 env var is set
    if _os.environ.get("DOBBY_REMOTE", "").strip() in ("1", "true", "yes"):
        remote = True

    cfg = DobbyConfig.load(config) if config else DobbyConfig()
    d = DobbyDaemon(cfg)

    if remote:
        d._remote_session_config = {
            "name": remote_name,
            "capacity": remote_capacity,
        }

    click.echo(click.style(f"Dobby listening on {cfg.host}:{cfg.port}", fg="green"))
    if remote:
        click.echo(click.style(
            f"Remote session '{remote_name}' will start (connect at claude.ai/code)", fg="cyan"
        ))
    asyncio.run(d.run())


# ---------------------------------------------------------------------------
# slack
# ---------------------------------------------------------------------------

@cli.command()
@click.option("--dobby-url", default="http://127.0.0.1:8420", help="Dobby daemon URL")
@click.option("--hub-url", default="", envvar="DOBBY_HUB_URL", help="Public-facing dashboard URL")
def slack(dobby_url, hub_url):
    """Start the Dobby Slack bot (requires SLACK_BOT_TOKEN and SLACK_APP_TOKEN env vars)."""
    import os
    bot_token = os.environ.get("SLACK_BOT_TOKEN")
    app_token = os.environ.get("SLACK_APP_TOKEN")
    if not bot_token or not app_token:
        click.echo(click.style(
            "Error: SLACK_BOT_TOKEN and SLACK_APP_TOKEN env vars are required.\n"
            "Get them from https://api.slack.com/apps",
            fg="red",
        ))
        raise SystemExit(1)

    from dobby.slack.bot import DobbySlackBot
    bot = DobbySlackBot(
        slack_bot_token=bot_token,
        slack_app_token=app_token,
        dobby_url=dobby_url,
        hub_url=hub_url,
    )
    click.echo(click.style("Dobby Slack bot starting...", fg="green"))
    asyncio.run(bot.run())


# ---------------------------------------------------------------------------
# remote (Claude Code remote control session)
# ---------------------------------------------------------------------------

@cli.command()
@click.option("--name", default="Dobby Hub", help="Session name shown in Claude Code")
@click.option("--capacity", default=4, help="Max concurrent remote sessions")
@click.option("--cwd", default=None, help="Working directory for remote sessions")
def remote(name, capacity, cwd):
    """Start a Claude Code remote control session.

    Allows connecting to this machine's Claude Code from anywhere
    via claude.ai/code. The session stays running until stopped.
    """
    from dobby.core.remote_session import RemoteSession

    session = RemoteSession(name=name, capacity=capacity, cwd=cwd)

    async def _run():
        started = await session.start()
        if not started:
            click.echo(click.style("Failed to start remote session", fg="red"))
            return
        click.echo(click.style(
            f"Remote session '{name}' started (capacity={capacity})\n"
            f"Connect at: https://claude.ai/code",
            fg="green",
        ))
        # Keep running until interrupted
        try:
            while session.is_running:
                await asyncio.sleep(1)
        except asyncio.CancelledError:
            pass
        finally:
            await session.stop()

    asyncio.run(_run())


# ---------------------------------------------------------------------------
# agent (spoke daemon)
# ---------------------------------------------------------------------------

@cli.command()
@click.option("--hub", required=True, help="Dobby hub URL (e.g. https://dobby.example.com)")
@click.option(
    "--id", "spoke_id", required=True, help="Unique spoke identifier (e.g. mac-local, gpu-a100)"
)
@click.option("--token", default=None, help="API token for hub authentication")
@click.option("--poll-interval", default=5.0, help="Seconds between task polls")
def agent(hub, spoke_id, token, poll_interval):
    """Run as a spoke agent that connects to a remote Dobby hub."""
    from dobby.core.spoke_agent import SpokeAgent

    spoke = SpokeAgent(
        hub_url=hub,
        spoke_id=spoke_id,
        token=token,
        poll_interval=poll_interval,
    )
    click.echo(click.style(f"Dobby spoke '{spoke_id}' connecting to {hub}", fg="green"))
    asyncio.run(spoke.run())


# ---------------------------------------------------------------------------
# deploy (subcommand group)
# ---------------------------------------------------------------------------

@cli.group()
def deploy():
    """Manage Dobby VPS deployment on Vultr."""


@deploy.command("provision")
@click.option("--label", default="dobby-vps", help="Instance label/hostname")
@click.option("--region", default="ewr", help="Vultr region code (default: ewr)")
@click.option("--plan", default="vc2-1c-1gb", help="Vultr plan ID")
@click.option("--api-key", default=None, help="Vultr API key (or set VULTR_API_KEY)")
@click.option("--install-source", default="", help="Pip install source for dobby (e.g. git+https://...)")
def deploy_provision(label, region, plan, api_key, install_source):
    """Provision a new VPS and install Dobby."""
    from dobby.deploy.manager import DeployManager

    mgr = DeployManager()
    click.echo(click.style("Provisioning VPS on Vultr...", fg="cyan"))
    click.echo(f"  region: {region}  plan: {plan}  label: {label}")

    try:
        vps = run_async(mgr.provision(
            label=label, region=region, plan=plan,
            api_key=api_key, install_source=install_source,
        ))
    except RuntimeError as e:
        click.echo(click.style(f"Error: {e}", fg="red"))
        raise SystemExit(1) from e
    except Exception as e:
        click.echo(click.style(f"Provisioning failed: {e}", fg="red"))
        raise SystemExit(1) from e

    click.echo(click.style("\nVPS provisioned!", fg="green"))
    click.echo(f"  instance: {vps.instance_id}")
    click.echo(f"  ip      : {vps.ip}")
    click.echo(f"  status  : {vps.status}")
    click.echo("\nVPS info saved to dobby.yaml")
    click.echo(f"SSH: ssh root@{vps.ip}")
    click.echo(f"API: http://{vps.ip}:8420/health")


@deploy.command("destroy")
@click.option("--api-key", default=None, help="Vultr API key (or set VULTR_API_KEY)")
@click.confirmation_option(prompt="Are you sure you want to destroy the VPS?")
def deploy_destroy(api_key):
    """Destroy the provisioned VPS."""
    from dobby.deploy.manager import DeployManager

    mgr = DeployManager()
    try:
        run_async(mgr.destroy(api_key=api_key))
    except RuntimeError as e:
        click.echo(click.style(f"Error: {e}", fg="red"))
        raise SystemExit(1) from e

    click.echo(click.style("VPS destroyed and removed from config.", fg="yellow"))


@deploy.command("status")
@click.option("--api-key", default=None, help="Vultr API key (or set VULTR_API_KEY)")
def deploy_status(api_key):
    """Show status of the deployed VPS."""
    from dobby.deploy.manager import DeployManager

    mgr = DeployManager()
    vps = mgr.get_vps_info()
    if not vps:
        click.echo("No VPS configured. Run 'dobby deploy provision' first.")
        return

    click.echo(click.style("Dobby VPS", bold=True))
    click.echo(f"  instance: {vps.instance_id}")
    click.echo(f"  ip      : {vps.ip}")
    click.echo(f"  region  : {vps.region}")
    click.echo(f"  plan    : {vps.plan}")

    if api_key or os.environ.get("VULTR_API_KEY"):
        try:
            live = run_async(mgr.status(api_key=api_key))
            if live:
                click.echo(f"  status  : {live.status}")
        except Exception as e:
            click.echo(f"  status  : error ({e})")
    else:
        click.echo(f"  status  : {vps.status} (cached)")


@deploy.command("verify")
def deploy_verify():
    """Verify Dobby is running on the remote VPS."""
    from dobby.deploy.manager import DeployManager

    mgr = DeployManager()
    try:
        results = run_async(mgr.verify_deployment())
    except RuntimeError as e:
        click.echo(click.style(f"Error: {e}", fg="red"))
        raise SystemExit(1) from e

    click.echo(click.style(f"Verification: {results['ip']}", bold=True))
    checks = results.get("checks", {})
    for check_name, passed in checks.items():
        icon = click.style("OK", fg="green") if passed else click.style("FAIL", fg="red")
        click.echo(f"  {check_name:<20} {icon}")

    if all(checks.values()):
        click.echo(click.style("\nAll checks passed!", fg="green"))
    else:
        click.echo(click.style("\nSome checks failed.", fg="red"))
        raise SystemExit(1)


@deploy.command("ssh")
@click.argument("command", nargs=-1, required=True)
def deploy_ssh(command):
    """Run a command on the remote VPS via SSH."""
    from dobby.deploy.manager import DeployManager

    mgr = DeployManager()
    cmd_str = " ".join(command)
    try:
        rc, output = run_async(mgr.ssh_command(cmd_str))
    except RuntimeError as e:
        click.echo(click.style(f"Error: {e}", fg="red"))
        raise SystemExit(1) from e

    click.echo(output)
    if rc != 0:
        raise SystemExit(rc)
