"""Loony CLI — turn raw data into production-ready services your agents can use."""

from typing import Any

import typer
from rich.console import Console
from rich.table import Table

from . import __version__, api, auth, config
from . import skills as skills_mod
from .api import ApiError
from .init import init_project

app = typer.Typer(
    name="loony",
    help="Turn raw data into production-ready services your agents can use.",
    no_args_is_help=True,
)
console = Console()


def _check_version() -> None:
    """Non-blocking check if a newer CLI version is available on PyPI."""
    try:
        import httpx

        resp = httpx.get("https://pypi.org/pypi/loony/json", timeout=3)
        if resp.status_code == 200:
            latest: str = resp.json()["info"]["version"]
            if latest != __version__:
                console.print(
                    f"[yellow]Update available:[/yellow] {__version__} → {latest}"
                    "  [dim](uv pip install --upgrade loony)[/dim]"
                )
    except Exception:
        pass


@app.callback(invoke_without_command=True)
def main(
    version: bool = typer.Option(False, "--version", "-v", help="Show version"),
) -> None:
    if version:
        console.print(f"loony {__version__}")
        raise typer.Exit()
    import threading

    threading.Thread(target=_check_version, daemon=True).start()


@app.command()
def init(
    name: str = typer.Argument(..., help="Service name"),
    agent: str | None = typer.Option(None, "--agent", "-a", help="Agent type: claude, cursor, codex"),
    update: bool = typer.Option(False, "--update", "-u", help="Update loony templates only, preserve user files"),
) -> None:
    """Scaffold a new pipeline project, or update loony templates."""
    from pathlib import Path

    result = init_project(name, agent=agent, project_dir=Path.cwd(), update=update)

    if update:
        console.print(f"\n[green]✓[/green] Updated loony templates for [bold]{result['name']}[/bold]\n")
        if result["updated"]:
            console.print("[bold]Updated:[/bold]")
            for f in result["updated"]:
                console.print(f"  [yellow]{f}[/yellow]")
        else:
            console.print("[dim]Everything up to date.[/dim]")
    else:
        console.print(f"\n[green]✓[/green] Created service [bold]{result['name']}[/bold]\n")

        if result["created"]:
            console.print("[bold]Core files:[/bold]")
            for f in result["created"]:
                console.print(f"  {f}")

        if result["skipped"]:
            console.print(f"\n[dim]Skipped (already exist): {', '.join(result['skipped'])}[/dim]")

        console.print("\n[bold]Next steps:[/bold]")
        console.print("  1. Edit schema.yaml — define your tables, columns, sync modes")
        console.print("  2. Write scripts in scripts/ — dlt scripts that export run(conn)")
        console.print("  3. Write transforms in transforms/ — SQL to clean and aggregate (optional)")
        console.print(f"\n  [dim]Next: cd {result['name']} && loony deploy[/dim]")


@app.command()
def login(
    env: str | None = typer.Option(None, "--env", "-e", hidden=True),
) -> None:
    """Authenticate with loony.dev via browser."""
    env = env or config.get_current_env()
    console.print("Opening browser...")

    try:
        user = auth.login(env)
        console.print(f"\n[green]✓[/green] Logged in as [bold]{user['email']}[/bold]")
        console.print(f"  User ID: {user['user_id']}")
        console.print("\n  [dim]Next: loony init <name> or cd into a service and loony deploy[/dim]")
    except Exception as e:
        console.print(f"\n[red]✗[/red] Login failed: {e}")
        raise typer.Exit(1)


@app.command()
def logout(
    env: str | None = typer.Option(None, "--env", "-e", hidden=True),
) -> None:
    """Clear stored credentials."""
    env = env or config.get_current_env()
    config.clear_credentials(env)
    console.print("[green]✓[/green] Logged out")
    console.print("\n  [dim]Next: loony login to re-authenticate[/dim]")


@app.command(hidden=True)
def env(
    name: str | None = typer.Argument(None, help="Environment to switch to"),
) -> None:
    """Show or switch the current environment (developer use only)."""
    if name:
        config.set_current_env(name)
        console.print(f"[green]✓[/green] Switched to [bold]{name}[/bold]")
    else:
        current = config.get_current_env()
        cfg = config.load()
        envs = cfg.get("environments", {})

        table = Table(title="Environments")
        table.add_column("Name")
        table.add_column("Endpoint")
        table.add_column("User")
        table.add_column("Active")

        for env_name in set(config.DEFAULTS.keys()) | set(envs.keys()):
            env_config = config.get_env_config(env_name)
            endpoint = env_config.get("endpoint", "")
            email = env_config.get("email", "")
            active = "→" if env_name == current else ""
            table.add_row(env_name, endpoint or "(not configured)", email or "(not logged in)", active)

        console.print(table)


@app.command()
def status() -> None:
    """Check connection and show user info."""
    # Health check
    try:
        h = api.health()
        console.print(f"[green]✓[/green] Control plane: {h.get('status', 'unknown')}")
    except Exception as e:
        console.print(f"[red]✗[/red] Control plane: {e}")
        raise typer.Exit(1)

    # Auth check
    try:
        user = api.me()
        console.print(f"[green]✓[/green] Authenticated as: {user.get('email')}")
        console.print(f"  User ID: {user.get('user_id')}")
    except ValueError as e:
        console.print(f"[yellow]⚠[/yellow] {e}")
    except Exception as e:
        console.print(f"[red]✗[/red] Auth failed: {e}")


# ─── Info ─────────────────────────────────────────────────────────────────────


@app.command()
def info(
    json_output: bool = typer.Option(False, "--json", help="Output as JSON for programmatic use"),
    env: str | None = typer.Option(None, "--env", "-e", hidden=True),
) -> None:
    """Show all integration details for the current service: API, MCP, database, CLI commands."""
    import json

    service_name = _get_service_name()
    target_env = env or config.get_current_env()
    env_config = config.get_env_config(target_env)
    endpoint = env_config.get("endpoint", "")

    if not endpoint:
        console.print(f"[red]✗[/red] No endpoint for '{target_env}'. Run 'loony login' first.")
        raise typer.Exit(1)

    # Gather all info
    base_api = f"{endpoint}/data/v1/{service_name}"
    mcp_url = f"{endpoint}/mcp/v1/{service_name}/mcp"
    docs_url = f"{endpoint}/docs"

    # Get keys
    keys: list[dict[str, Any]] = []
    try:
        keys = api.keys_list(service_name, env=env)
    except Exception:
        pass
    active_keys = [k for k in keys if k.get("type") == "publishable"]

    # Get DB details
    db_details: dict[str, Any] = {}
    try:
        db_details = api.get_connect_details(service_name, env=env)
    except Exception:
        pass

    # Get table list
    tables: list[str] = []
    try:
        desc = api.describe(service_name, env=env)
        tables = sorted(desc.get("tables", {}).keys())
    except Exception:
        pass

    if json_output:
        result: dict[str, Any] = {
            "service": service_name,
            "environment": target_env,
            "database": {
                "host": db_details.get("host", ""),
                "port": db_details.get("port", 0),
                "name": db_details.get("database", ""),
                "username": db_details.get("username", ""),
                "connection_string": db_details.get("connection_string", ""),
            } if db_details else None,
            "api": {
                "base_url": base_api,
                "docs_url": docs_url,
                "openapi_url": f"{endpoint}/openapi.json",
                "endpoints": [
                    f"GET {base_api}/tables",
                    f"GET {base_api}/tables/{{table}}",
                    f"GET {base_api}/tables/{{table}}/columns/{{column}}",
                    f"GET {base_api}/search?q={{query}}",
                    f"POST {base_api}/query",
                ],
                "auth_header": "Authorization: Bearer <api_key>",
            },
            "mcp": {
                "url": mcp_url,
                "tools": ["list_tables", "describe_table", "column_stats", "search", "query"],
            },
            "keys": {
                "count": len(active_keys),
                "prefixes": [k["prefix"] for k in active_keys],
            },
            "tables": tables,
            "commands": {
                "create_key": "loony keys create --name 'my-app'",
                "list_keys": "loony keys list",
                "connect_db": "loony connect --show-password",
                "query_data": "loony query 'SELECT * FROM table LIMIT 10'",
                "describe": "loony describe",
                "mcp_config": "loony mcp status --show-key",
                "deploy": "loony deploy",
                "run": "loony run",
                "logs": "loony logs --last",
            },
        }
        print(json.dumps(result, indent=2))  # noqa: T201
        return

    # Human-readable output
    console.print(f"\n  [bold]{service_name}[/bold] on [bold]{target_env}[/bold]\n")

    # API section
    console.print("  [bold]REST API[/bold]")
    console.print(f"    Base URL:  {base_api}")
    console.print(f"    Docs:      {docs_url}")
    console.print("    Auth:      Authorization: Bearer <api_key>")
    console.print("    Endpoints: GET /tables, GET /tables/{table}, POST /query, GET /search")
    console.print()

    # MCP section
    console.print("  [bold]MCP (for Claude Desktop / Cursor)[/bold]")
    console.print(f"    URL:   {mcp_url}")
    console.print("    Tools: list_tables, describe_table, column_stats, search, query")
    console.print()

    # Database section
    if db_details:
        console.print("  [bold]Database (for BI tools)[/bold]")
        console.print(f"    Host:  {db_details['host']}:{db_details['port']}")
        console.print(f"    DB:    {db_details['database']}")
        console.print("    Run:   loony connect --show-password")
        console.print()

    # Keys section
    if active_keys:
        console.print(f"  [bold]API Keys[/bold]: {len(active_keys)} active ({active_keys[0]['prefix']}...)")
    else:
        console.print("  [bold]API Keys[/bold]: [yellow]none[/yellow] — run 'loony keys create'")
    console.print()

    # Tables
    if tables:
        console.print(f"  [bold]Tables[/bold]: {', '.join(tables[:8])}")
        if len(tables) > 8:
            console.print(f"    ... and {len(tables) - 8} more (loony describe)")
        console.print()

    # Quick commands
    console.print("  [bold]Quick start[/bold]")
    console.print("    loony describe                    — view schema")
    console.print("    loony query 'SELECT ...'          — run SQL")
    console.print("    loony connect --show-password     — DB credentials")
    console.print("    loony mcp status --show-key       — MCP config")
    console.print("    loony keys create --name 'agent'  — new API key")
    console.print()


# ─── Validate ─────────────────────────────────────────────────────────────────


@app.command()
def validate() -> None:
    """Validate the current service without deploying."""
    from pathlib import Path

    from .validate import validate_service

    result = validate_service(Path.cwd())

    console.print(f"\n  Validating [bold]{result.name or '?'}[/bold]...")
    for check in result.checks:
        if check.ok:
            detail = f" — {check.detail}" if check.detail else ""
            console.print(f"  [green]✓[/green] {check.label}{detail}")
        else:
            console.print(f"  [red]✗[/red] {check.label}")
            for err in check.errors:
                console.print(f"    {err}")

    if result.passed:
        console.print("\n  [green]Ready to deploy.[/green]")
        console.print("\n  [dim]Next: loony deploy[/dim]")
    else:
        error_count = sum(1 for c in result.checks if not c.ok)
        console.print(f"\n  {error_count} error(s). Fix before deploying.")
        raise typer.Exit(1)


# ─── Deployment watcher (SSE streaming with polling fallback) ─────────────────


def _watch_deployment(name: str, deployment_id: int, env: str | None) -> None:
    """Watch a running deployment — stream logs via SSE, fall back to polling."""
    try:
        _stream_deployment(name, deployment_id, env)
    except (ApiError, ValueError):
        raise  # Don't swallow auth/config errors
    except Exception:
        _poll_deployment(name, deployment_id, env)

    # Fetch final status for summary
    try:
        status = api.get_deployment(name, deployment_id, env=env)
    except Exception:
        return

    _show_summary(status)


def _stream_deployment(name: str, deployment_id: int, env: str | None) -> None:
    """Stream deployment logs via SSE."""
    import httpx
    from httpx_sse import connect_sse

    env_config = config.get_env_config(env or config.get_current_env())
    endpoint = env_config.get("endpoint", "")
    token = env_config.get("access_token", "")
    url = f"{endpoint}/api/v1/services/{name}/deployments/{deployment_id}/stream"

    with httpx.Client(timeout=httpx.Timeout(660.0, connect=10.0)) as client:
        with connect_sse(client, "GET", url, headers={"Authorization": f"Bearer {token}"}) as es:
            for sse in es.iter_sse():
                if sse.event == "log":
                    console.print(f"  {sse.data}")
                elif sse.event == "done":
                    break


def _poll_deployment(name: str, deployment_id: int, env: str | None) -> None:
    """Poll deployment status (fallback when SSE not available)."""
    import time

    max_wait = 600
    start = time.monotonic()
    while True:
        if time.monotonic() - start > max_wait:
            console.print("\n  [yellow]Timed out. Check 'loony logs --last' later.[/yellow]")
            break
        time.sleep(3)
        try:
            status = api.get_deployment(name, deployment_id, env=env)
        except Exception:
            console.print("  [dim]· Lost connection.[/dim]")
            break

        if status.get("status") in ("success", "failed", "cancelled"):
            if status.get("log"):
                for line in status["log"].split("\n"):
                    console.print(f"  {line}")
            break


def _render_table_summary(tables_data: list[dict[str, Any]]) -> None:
    """Render the Tables/Rows/Extracted/Net Rich table."""
    if not tables_data:
        return
    console.print()
    tbl = Table(title="Tables")
    tbl.add_column("Table")
    tbl.add_column("Rows", justify="right")
    tbl.add_column("Extracted", justify="right")
    tbl.add_column("Net", justify="right")
    for t in tables_data:
        delta = t.get("delta", 0)
        if delta > 0:
            net = f"[green]+{delta:,}[/green]"
        elif delta < 0:
            net = f"[red]{delta:,}[/red]"
        else:
            net = "[dim]—[/dim]"
        extracted = t.get("extracted", 0)
        ext = f"{extracted:,}" if extracted else ""
        tbl.add_row(str(t["table"]), f"{t['rows']:,}", ext, net)
    console.print(tbl)


def _show_summary(status: dict[str, Any]) -> None:
    """Display the deployment summary table and result."""
    summary = status.get("summary")
    if summary and isinstance(summary, dict):
        _render_table_summary(summary.get("tables", []))

        succeeded = summary.get("succeeded", 0)
        failed_count = summary.get("failed", 0)
        console.print(f"\n  {succeeded} succeeded, {failed_count} failed.")

    duration_str = ""
    if status.get("started_at") and status.get("finished_at"):
        try:
            from datetime import datetime as dt

            t_start = dt.fromisoformat(str(status["started_at"]))
            t_end = dt.fromisoformat(str(status["finished_at"]))
            duration_str = f" ({(t_end - t_start).total_seconds():.0f}s)"
        except Exception:
            pass

    if status.get("status") == "success":
        console.print(f"  [green]✓[/green] Pipeline complete{duration_str}")
        console.print("\n  [dim]Next: loony logs --last for details[/dim]")
    elif status.get("status") == "cancelled":
        console.print("  [yellow]⚠[/yellow] Pipeline cancelled")
    else:
        console.print(f"  [yellow]⚠[/yellow] Pipeline finished with errors{duration_str}")
        if status.get("error"):
            console.print(f"  {status['error'][:300]}")
        console.print("\n  [dim]Next: loony logs --last to see full error details[/dim]")


# ─── Deploy ───────────────────────────────────────────────────────────────────


@app.command()
def deploy(
    no_run: bool = typer.Option(False, "--no-run", help="Upload files without running the pipeline"),
    env: str | None = typer.Option(None, "--env", "-e", hidden=True),
) -> None:
    """Validate and deploy the current service."""
    import hashlib
    from pathlib import Path

    from .validate import validate_service

    service_dir = Path.cwd()
    result = validate_service(service_dir)

    # Render validation
    console.print(f"\n  Validating [bold]{result.name or '?'}[/bold]...")
    for check in result.checks:
        if check.ok:
            detail = f" — {check.detail}" if check.detail else ""
            console.print(f"  [green]✓[/green] {check.label}{detail}")
        else:
            console.print(f"  [red]✗[/red] {check.label}")
            for err in check.errors:
                console.print(f"    {err}")

    if not result.passed:
        error_count = sum(1 for c in result.checks if not c.ok)
        console.print(f"\n  {error_count} error(s). Fix before deploying.")
        raise typer.Exit(1)

    # Compute files hash
    sorted_paths = sorted(result.files.keys())
    hasher = hashlib.sha256()
    for p in sorted_paths:
        hasher.update(result.files[p].encode())
    files_hash = hasher.hexdigest()

    # Upload
    target_env = env or config.get_current_env()
    console.print(f"\n  Deploying to [bold]{target_env}[/bold]...")

    try:
        resp = api.deploy(result.name, {"files": result.files, "files_hash": files_hash, "run": not no_run}, env=env)
    except ValueError as e:
        console.print(f"  [red]✗[/red] {e}")
        raise typer.Exit(1)
    except Exception as e:
        console.print(f"  [red]✗[/red] Deploy failed: {e}")
        console.print("  [dim]Try: loony validate to check for issues, or loony status to verify connection[/dim]")
        raise typer.Exit(1)

    # Handle response
    if resp.get("status") == "unchanged":
        console.print(f"  [dim]· Files unchanged since deployment #{resp.get('deployment_id')}[/dim]")
        raise typer.Exit()

    if resp.get("service_created"):
        console.print(f"  [green]✓[/green] Service created: [bold]{result.name}[/bold]")
    else:
        console.print(f"  [green]✓[/green] Service updated: [bold]{result.name}[/bold]")

    console.print(f"  [green]✓[/green] {resp.get('files_count', len(result.files))} files uploaded")
    console.print(f"  [green]✓[/green] Deployment #{resp.get('deployment_id')} — {resp.get('status', 'success')}")

    if resp.get("status") == "running":
        deployment_id = int(resp["deployment_id"])
        _watch_deployment(result.name, deployment_id, env)


# ─── Run ──────────────────────────────────────────────────────────────────────


@app.command()
def run(
    script: str | None = typer.Argument(None, help="Script name to run (without .py). Runs all if omitted."),
    env: str | None = typer.Option(None, "--env", "-e", hidden=True),
) -> None:
    """Trigger a pipeline run using the latest deployment."""
    name = _get_service_name()

    target_env = env or config.get_current_env()
    if script:
        console.print(f"\n  Running [bold]{script}[/bold] on [bold]{target_env}[/bold]...")
    else:
        console.print(f"\n  Running all scripts on [bold]{target_env}[/bold]...")

    try:
        resp = api.run_pipeline(name, script=script, env=env)
    except ValueError as e:
        console.print(f"  [red]✗[/red] {e}")
        raise typer.Exit(1)
    except Exception as e:
        console.print(f"  [red]✗[/red] Run failed: {e}")
        console.print("  [dim]Try: loony logs --last for details, or loony status to check connection[/dim]")
        raise typer.Exit(1)

    deployment_id = int(resp["deployment_id"])
    console.print(f"  Deployment #{deployment_id} — running\n")
    _watch_deployment(name, deployment_id, env)


# ─── Reset ────────────────────────────────────────────────────────────────────


@app.command()
def reset(
    env: str | None = typer.Option(None, "--env", "-e", hidden=True),
    yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation prompt"),
) -> None:
    """Clear corrupted pipeline state (orphaned staging schemas, pending dlt loads)."""
    name = _get_service_name()

    target_env = env or config.get_current_env()

    if not yes:
        console.print(f"\n  This will reset pipeline state for [bold]{name}[/bold] on [bold]{target_env}[/bold].")
        console.print("  Actions: drop staging schemas, clear pending dlt loads, remove local dlt cache.\n")
        confirm = typer.confirm("  Continue?")
        if not confirm:
            raise typer.Exit(0)

    console.print(f"\n  Resetting pipeline state for [bold]{name}[/bold]...")

    try:
        resp = api.reset_pipeline(name, env=env)
    except api.ApiError as e:
        console.print(f"  [red]✗[/red] {e.message}")
        raise typer.Exit(1)

    for action in resp.get("actions", []):
        console.print(f"  [green]✓[/green] {action}")
    console.print("\n  [dim]Next: loony run to re-run your pipeline[/dim]")


# ─── Cancel ───────────────────────────────────────────────────────────────────


@app.command()
def cancel(
    env: str | None = typer.Option(None, "--env", "-e", hidden=True),
) -> None:
    """Cancel the most recent running deployment."""
    service_name = _get_service_name()

    # Find the most recent running deployment
    try:
        logs = api.get_logs(service_name, limit=5, env=env)
    except api.ApiError as e:
        console.print(f"  [red]✗[/red] {e.message}")
        raise typer.Exit(1)

    running = [d for d in logs if d.get("status") in ("pending", "running")]
    if not running:
        console.print("  [dim]No running deployment to cancel.[/dim]")
        raise typer.Exit(0)

    deployment_id = running[0]["deployment_id"]
    console.print(f"  Cancelling deployment {deployment_id}...")

    try:
        resp = api.cancel_deployment(service_name, deployment_id, env=env)
    except api.ApiError as e:
        console.print(f"  [red]✗[/red] {e.message}")
        raise typer.Exit(1)

    status = resp.get("status", "unknown")
    message = resp.get("message", "")
    if status == "cancelled":
        console.print(f"  [green]✓[/green] {message}")
        console.print("\n  [dim]Next: loony run to re-run, or loony logs --last to see what happened[/dim]")
    else:
        console.print(f"  [yellow]⚠[/yellow] {message}")


# ─── Connect ──────────────────────────────────────────────────────────────────


@app.command()
def connect(
    env: str | None = typer.Option(None, "--env", "-e", hidden=True),
    show_password: bool = typer.Option(False, "--show-password", help="Show password in output"),
) -> None:
    """Show database connection details for BI tools (Lightdash, Metabase, etc.)."""
    service_name = _get_service_name()

    try:
        details = api.get_connect_details(service_name, env=env)
    except api.ApiError as e:
        console.print(f"  [red]✗[/red] {e.message}")
        raise typer.Exit(1)

    password = details["password"] if show_password else "•" * 12
    conn_str = (
        details["connection_string"]
        if show_password
        else details["connection_string"].replace(details["password"], "•" * 12)
    )

    console.print(f"\n  Connection details for [bold]{service_name}[/bold]:\n")
    console.print(f"  Host:     {details['host']}")
    console.print(f"  Port:     {details['port']}")
    console.print(f"  Database: {details['database']}")
    console.print(f"  Username: {details['username']}")
    console.print(f"  Password: {password}")
    console.print(f"  SSL:      {details.get('sslmode', 'disable')}")
    console.print(f"\n  Connection string:\n  {conn_str}")

    if not show_password:
        console.print("\n  [dim]Tip: add --show-password to reveal credentials[/dim]")
    console.print("\n  [dim]Next: loony describe to see available tables[/dim]")
    console.print()


# ─── Logs ─────────────────────────────────────────────────────────────────────


@app.command()
def logs(
    deployment_id: int | None = typer.Argument(None, help="Show detail for a specific deployment"),
    failed: bool = typer.Option(False, "--failed", help="Show only failed runs"),
    last: bool = typer.Option(False, "--last", help="Show full detail of most recent run"),
    limit: int = typer.Option(10, "--limit", "-n", help="Number of runs to show"),
    env: str | None = typer.Option(None, "--env", "-e", hidden=True),
) -> None:
    """View run history and deployment logs."""
    from rich.text import Text

    name = _get_service_name()

    # Detail view: specific deployment or --last
    if deployment_id is not None or last:
        try:
            if last:
                entries = api.get_logs(name, limit=1, env=env)
                if not entries:
                    console.print("  No runs found.")
                    raise typer.Exit()
                entry = entries[0]
            else:
                entry = api.get_deployment(name, deployment_id or 0, env=env)
        except ValueError as e:
            console.print(f"[red]✗[/red] {e}")
            raise typer.Exit(1)
        except Exception as e:
            console.print(f"[red]✗[/red] {e}")
            raise typer.Exit(1)

        dep_id = entry.get("deployment_id", "?")
        console.print(f"\n  [bold]Deployment #{dep_id}[/bold]")
        console.print(f"  Status:   {entry.get('status')}")
        console.print(f"  Started:  {entry.get('started_at', '—')}")
        console.print(f"  Finished: {entry.get('finished_at', '—')}")

        if entry.get("error"):
            console.print(f"\n  [red]Error:[/red] {entry['error'][:500]}")

        if entry.get("log"):
            console.print("\n  [bold]Log:[/bold]")
            for line in str(entry["log"]).split("\n"):
                console.print(f"  {line}")

        summary = entry.get("summary")
        if summary and isinstance(summary, dict):
            _render_table_summary(summary.get("tables", []))
        raise typer.Exit()

    # List view
    status_filter = "failed" if failed else None
    try:
        entries = api.get_logs(name, limit=limit, status_filter=status_filter, env=env)
    except Exception as e:
        console.print(f"  [red]✗[/red] {e}")
        raise typer.Exit(1)

    if not entries:
        console.print("  No runs found.")
        raise typer.Exit()

    console.print(f"\n  Recent runs for [bold]{name}[/bold]:\n")
    tbl = Table()
    tbl.add_column("#", justify="right")
    tbl.add_column("Status")
    tbl.add_column("Scripts")
    tbl.add_column("Started")
    tbl.add_column("Duration")

    for entry in entries:
        dep_id = str(entry.get("deployment_id", "?"))
        status_val = str(entry.get("status", "?"))

        if status_val == "success":
            status_text = Text("success", style="green")
        elif status_val == "failed":
            status_text = Text("failed", style="red")
        elif status_val == "running":
            status_text = Text("running", style="yellow")
        else:
            status_text = Text(status_val, style="dim")

        # Script summary from deployment summary
        summary = entry.get("summary")
        if summary and isinstance(summary, dict):
            succeeded = summary.get("succeeded", 0)
            failed_count = summary.get("failed", 0)
            scripts_str = f"{succeeded} ok"
            if failed_count:
                scripts_str += f", {failed_count} failed"
        else:
            scripts_str = "—"

        started = str(entry.get("started_at", "—"))[:19]
        started_dt = entry.get("started_at")
        finished_dt = entry.get("finished_at")
        if started_dt and finished_dt and started_dt != "None" and finished_dt != "None":
            try:
                from datetime import datetime as dt

                t_start = dt.fromisoformat(str(started_dt))
                t_end = dt.fromisoformat(str(finished_dt))
                dur = t_end - t_start
                duration = f"{dur.total_seconds():.0f}s"
            except Exception:
                duration = "—"
        else:
            duration = "—"

        tbl.add_row(dep_id, status_text, scripts_str, started, duration)

    console.print(tbl)


# ─── Describe ─────────────────────────────────────────────────────────────────


@app.command()
def describe(
    table: str | None = typer.Argument(None, help="Table name for detailed view. Omit to list all tables."),
    json_output: bool = typer.Option(False, "--json", help="Output as JSON"),
    env: str | None = typer.Option(None, "--env", "-e", hidden=True),
) -> None:
    """Show tables, measures, and dimensions for the current service.

    Without arguments: lists all tables with row counts.
    With a table name: shows columns, types, measures, dimensions, and sample data.
    """
    import json
    from pathlib import Path

    import yaml

    name = _get_service_name()
    schema_yaml = Path.cwd() / "schema.yaml"

    if not schema_yaml.exists():
        console.print("[red]✗[/red] No schema.yaml found.")
        raise typer.Exit(1)

    with open(schema_yaml) as f:
        schema = yaml.safe_load(f)
    tables_schema = schema.get("tables", {})

    # Try to get remote row counts
    remote_counts: dict[str, int] = {}
    deployed = False
    try:
        remote = api.describe(name, env=env)
        remote_counts = remote.get("tables", {})
        deployed = True
    except ApiError as e:
        if e.status_code != 404:
            console.print(f"  [dim]Warning: could not fetch live data: {e.message}[/dim]")
    except (ValueError, Exception):
        pass  # Not logged in or network error — show schema only

    # ─── Table detail mode ────────────────────────────────────────────────
    if table:
        table_def = tables_schema.get(table)
        if not table_def or not isinstance(table_def, dict):
            available = [t for t in tables_schema if isinstance(tables_schema[t], dict)]
            console.print(f"[red]✗[/red] Table '{table}' not in schema.yaml.")
            console.print(f"  Available: {', '.join(available)}")
            raise typer.Exit(1)

        detail: dict[str, Any] = {
            "name": table,
            "description": table_def.get("description", ""),
            "sync_mode": table_def.get("sync_mode", ""),
            "primary_key": table_def.get("primary_key", []),
            "rows": remote_counts.get(table),
        }

        # Columns
        cols_raw = table_def.get("columns", {})
        if isinstance(cols_raw, dict):
            detail["columns"] = [
                {"name": k, "type": v.get("type", ""), "description": v.get("description", "")}
                for k, v in cols_raw.items() if isinstance(v, dict)
            ]
        elif isinstance(cols_raw, list):
            detail["columns"] = cols_raw
        else:
            detail["columns"] = []

        # Measures
        measures_raw = table_def.get("measures", {})
        detail["measures"] = [
            {
                "name": k,
                "type": v.get("type", ""),
                "column": v.get("column", ""),
                "description": v.get("description", ""),
            }
            for k, v in measures_raw.items() if isinstance(v, dict)
        ] if isinstance(measures_raw, dict) else []

        # Dimensions
        dims_raw = table_def.get("dimensions", {})
        detail["dimensions"] = [
            {
                "name": k,
                "type": v.get("type", ""),
                "column": v.get("column", ""),
                "description": v.get("description", ""),
            }
            for k, v in dims_raw.items() if isinstance(v, dict)
        ] if isinstance(dims_raw, dict) else []

        # Joins
        joins_raw = table_def.get("joins", {})
        detail["joins"] = [
            {"name": k, "to": v.get("to", ""), "description": v.get("description", "")}
            for k, v in joins_raw.items() if isinstance(v, dict)
        ] if isinstance(joins_raw, dict) else []

        # Sample data via query (if deployed)
        if deployed:
            try:
                sample = api.query(name, sql=f'SELECT * FROM "{table}" LIMIT 5', limit=5, env=env)  # noqa: S608
                detail["sample"] = sample.get("rows", [])
            except Exception:
                detail["sample"] = []

        if json_output:
            print(json.dumps(detail, indent=2, default=str))  # noqa: T201
            return

        # Human-readable table detail
        console.print(f"\n  [bold]{table}[/bold]")
        if detail["description"]:
            console.print(f"  {detail['description']}")
        rows = detail.get("rows")
        console.print(f"  Rows: {rows:,}" if rows is not None else "  Rows: —")
        if detail["sync_mode"]:
            console.print(f"  Sync: {detail['sync_mode']}")
        if detail["primary_key"]:
            console.print(f"  PK:   {detail['primary_key']}")

        if detail["columns"]:
            console.print("\n  [dim]Columns:[/dim]")
            for c in detail["columns"]:
                console.print(f"    {c['name']:<25} {c['type']:<15} {c['description']}")

        if detail["measures"]:
            console.print("\n  [dim]Measures:[/dim]")
            for m in detail["measures"]:
                col = f"({m['column']})" if m["column"] else ""
                console.print(f"    {m['name']:<25} {m['type']}{col:<15} {m['description']}")

        if detail["dimensions"]:
            console.print("\n  [dim]Dimensions:[/dim]")
            for d in detail["dimensions"]:
                console.print(f"    {d['name']:<25} {d['type']:<15} {d['description']}")

        if detail.get("joins"):
            console.print("\n  [dim]Joins:[/dim]")
            for j in detail["joins"]:
                console.print(f"    → {j['to']:<25} {j['description']}")

        if detail.get("sample"):
            console.print("\n  [dim]Sample (5 rows):[/dim]")
            sample_tbl = Table()
            cols = list(detail["sample"][0].keys()) if detail["sample"] else []
            for col in cols:
                sample_tbl.add_column(col)
            for row in detail["sample"]:
                sample_tbl.add_row(*[str(row.get(c, "")) for c in cols])
            console.print(sample_tbl)

        console.print(f"\n  [dim]Next: loony query 'SELECT * FROM \"{table}\" LIMIT 10'[/dim]")  # noqa: S608
        return

    # ─── Table list mode (no table argument) ──────────────────────────────
    if json_output:
        result_list = []
        for tname, tdef in tables_schema.items():
            if not isinstance(tdef, dict):
                continue
            result_list.append({
                "name": tname,
                "description": tdef.get("description", ""),
                "sync_mode": tdef.get("sync_mode", ""),
                "primary_key": tdef.get("primary_key", []),
                "rows": remote_counts.get(tname),
                "has_measures": bool(tdef.get("measures")),
                "has_dimensions": bool(tdef.get("dimensions")),
            })
        print(json.dumps({"tables": result_list}, indent=2, default=str))  # noqa: T201
        return

    status_label = "" if deployed else " [dim](not deployed)[/dim]"
    console.print(f"\n  Service: [bold]{name}[/bold]{status_label}\n")

    tbl = Table(title="Tables")
    tbl.add_column("Table")
    tbl.add_column("Rows", justify="right")
    tbl.add_column("Sync")
    tbl.add_column("Primary Key")

    for table_name, table_def in tables_schema.items():
        if not isinstance(table_def, dict):
            continue
        rows = remote_counts.get(table_name)
        rows_str = f"{rows:,}" if rows is not None else "[dim]—[/dim]"
        sync = table_def.get("sync_mode", "")
        pk = table_def.get("primary_key", [])
        pk_str = str(pk) if pk else ""
        tbl.add_row(table_name, rows_str, sync, pk_str)

    for table_name, count in remote_counts.items():
        if table_name not in tables_schema:
            tbl.add_row(f"[dim]{table_name}[/dim]", f"{count:,}", "[dim]?[/dim]", "")

    console.print(tbl)

    # Show measures and dimensions
    for table_name, table_def in tables_schema.items():
        if not isinstance(table_def, dict):
            continue
        measures = table_def.get("measures", {})
        dimensions = table_def.get("dimensions", {})
        if not measures and not dimensions:
            continue

        console.print(f"\n  [bold]{table_name}[/bold]")

        if measures:
            console.print("  [dim]Measures:[/dim]")
            for m_name, m_def in measures.items():
                if isinstance(m_def, dict):
                    m_type = m_def.get("type", "")
                    m_col = m_def.get("column", "")
                    m_desc = m_def.get("description", "")
                    detail_str = f"{m_type}({m_col})" if m_col else m_type
                    console.print(f"    {m_name:<20} {detail_str:<20} {m_desc}")

        if dimensions:
            console.print("  [dim]Dimensions:[/dim]")
            for d_name, d_def in dimensions.items():
                if isinstance(d_def, dict):
                    d_type = d_def.get("type", "")
                    d_desc = d_def.get("description", "")
                    console.print(f"    {d_name:<20} {d_type:<20} {d_desc}")

    console.print("\n  [dim]Next: loony describe <table> for column details and sample data[/dim]")


# ─── Query ────────────────────────────────────────────────────────────────────


@app.command()
def query(
    sql: str = typer.Argument(..., help="SQL query (SELECT only)"),
    output_format: str = typer.Option("toon", "--format", "-f", help="Output format: toon, table, json"),
    limit: int = typer.Option(100, "--limit", "-n", help="Max rows to return"),
    env: str | None = typer.Option(None, "--env", "-e", hidden=True),
) -> None:
    """Run a read-only SQL query against the service database."""
    name = _get_service_name()

    try:
        result = api.query(name, sql=sql, limit=limit, env=env)
    except ValueError as e:
        console.print(f"  [red]✗[/red] {e}")
        raise typer.Exit(1)
    except Exception as e:
        console.print(f"  [red]✗[/red] Query failed: {e}")
        console.print("  [dim]Tip: loony describe to see available tables and columns[/dim]")
        raise typer.Exit(1)

    rows = result.get("rows", [])
    columns = result.get("columns", [])
    count = result.get("count", 0)

    if not rows:
        console.print("  No results.")
        raise typer.Exit()

    if output_format == "json":
        import json

        console.print(json.dumps(rows, indent=2, default=str))
    elif output_format == "table":
        tbl = Table()
        for col in columns:
            tbl.add_column(col)
        for row in rows:
            tbl.add_row(*[str(row.get(c, "")) for c in columns])
        console.print(tbl)
        console.print(f"\n  {count} rows")
    else:
        # TOON format (default)
        from toon import encode

        console.print(encode(rows))

    console.print()


# ─── Feedback ──────────────────────────────────────────────────────────────────


@app.command()
def feedback(
    message: str = typer.Argument(..., help="Describe the issue or suggestion"),
    include_traceback: bool = typer.Option(
        True, "--traceback/--no-traceback", help="Include recent Python traceback if available"
    ),
) -> None:
    """Submit feedback or report a bug. Creates a tracked issue with full context."""
    import platform
    import traceback as tb_mod

    console.print("[dim]Gathering context...[/dim]")

    payload = {
        "message": message,
        "cli_version": __version__,
        "python_version": platform.python_version(),
        "os": f"{platform.system()} {platform.release()}",
        "environment": config.get_current_env(),
    }

    # Capture last traceback if available
    if include_traceback:
        last_tb = tb_mod.format_exc()
        if last_tb and last_tb.strip() != "NoneType: None":
            payload["traceback"] = last_tb

    try:
        result = api.submit_feedback(payload)
        console.print("\n[green]✓[/green] Feedback submitted")
        if result.get("issue_url"):
            console.print(f"  Tracking: {result['issue_url']}")
        if result.get("issue_number"):
            console.print(f"  Issue #{result['issue_number']}")
    except ValueError as e:
        console.print(f"[red]✗[/red] {e}")
        raise typer.Exit(1)
    except Exception as e:
        console.print(f"[red]✗[/red] Failed to submit feedback: {e}")
        raise typer.Exit(1)


# ─── Schedule ─────────────────────────────────────────────────────────────────

schedule_app = typer.Typer(help="View and manage scheduled pipeline runs.")
app.add_typer(schedule_app, name="schedule")


@schedule_app.callback(invoke_without_command=True)
def schedule_default(ctx: typer.Context) -> None:
    """List scheduled runs for the current service."""
    if ctx.invoked_subcommand is not None:
        return
    _schedule_list()


@schedule_app.command("list")
def schedule_list_cmd() -> None:
    """List scheduled runs for the current service."""
    _schedule_list()


def _schedule_list(env: str | None = None) -> None:
    service_name = _get_service_name()

    try:
        schedules = api.schedule_list(service_name, env=env)
    except ApiError as e:
        console.print(f"[red]✗[/red] {e.message}")
        raise typer.Exit(1)
    except Exception as e:
        console.print(f"[red]✗[/red] {e}")
        raise typer.Exit(1)

    if not schedules:
        console.print("  No schedules configured.")
        console.print("\n  [dim]Add a schedule section to loony.yaml and run loony deploy.[/dim]")
        return

    console.print(f"\n  Schedules for [bold]{service_name}[/bold]:\n")
    tbl = Table()
    tbl.add_column("Script")
    tbl.add_column("Schedule")
    tbl.add_column("Status")
    for s in schedules:
        status_text = "[green]active[/green]" if s.get("active") else "[yellow]paused[/yellow]"
        tbl.add_row(s["script"], s["schedule"], status_text)
    console.print(tbl)


@schedule_app.command("pause")
def schedule_pause_cmd(
    script: str = typer.Argument(..., help="Script name to pause"),
    env: str | None = typer.Option(None, "--env", "-e", hidden=True),
) -> None:
    """Pause a scheduled script run."""
    service_name = _get_service_name()

    try:
        api.schedule_pause(service_name, script, env=env)
        console.print(f"[green]✓[/green] Paused schedule for [bold]{script}[/bold]")
        console.print(f"\n  [dim]Next: loony schedule resume {script} to re-enable[/dim]")
    except ApiError as e:
        console.print(f"[red]✗[/red] {e.message}")
        raise typer.Exit(1)
    except Exception as e:
        console.print(f"[red]✗[/red] {e}")
        raise typer.Exit(1)


@schedule_app.command("resume")
def schedule_resume_cmd(
    script: str = typer.Argument(..., help="Script name to resume"),
    env: str | None = typer.Option(None, "--env", "-e", hidden=True),
) -> None:
    """Resume a paused scheduled script run."""
    service_name = _get_service_name()

    try:
        api.schedule_resume(service_name, script, env=env)
        console.print(f"[green]✓[/green] Resumed schedule for [bold]{script}[/bold]")
        console.print("\n  [dim]Next: loony schedule to verify active schedules[/dim]")
    except ApiError as e:
        console.print(f"[red]✗[/red] {e.message}")
        raise typer.Exit(1)
    except Exception as e:
        console.print(f"[red]✗[/red] {e}")
        raise typer.Exit(1)


# ─── Keys ─────────────────────────────────────────────────────────────────────

keys_app = typer.Typer(help="Manage API keys for agent access.")
app.add_typer(keys_app, name="keys")


@keys_app.command("create")
def keys_create(
    key_name: str | None = typer.Option(None, "--name", "-n", help="Key name for identification"),
    env: str | None = typer.Option(None, "--env", "-e", hidden=True),
) -> None:
    """Create a publishable API key for agent access to this service."""
    service_name = _get_service_name()

    try:
        result = api.keys_create(service_name, key_name=key_name, env=env)
    except ApiError as e:
        console.print(f"[red]✗[/red] {e.message}")
        raise typer.Exit(1)
    except Exception as e:
        console.print(f"[red]✗[/red] {e}")
        raise typer.Exit(1)

    console.print("\n  [green]✓[/green] API key created\n")
    console.print(f"  Key: [bold]{result['key']}[/bold]")
    console.print("\n  [yellow]Save this now — it cannot be retrieved again.[/yellow]")

    endpoint = api.config.get_env_config(env).get("endpoint", "")
    console.print("\n  [dim]Use this key to query data:[/dim]")
    console.print(f"  [dim]  curl -H 'Authorization: Bearer {result['key'][:20]}...' \\[/dim]")
    console.print(f"  [dim]    {endpoint}/data/v1/{service_name}/tables[/dim]")


@keys_app.command("list")
def keys_list_cmd(
    env: str | None = typer.Option(None, "--env", "-e", hidden=True),
) -> None:
    """List active API keys (prefixes only)."""
    service_name = _get_service_name()

    try:
        keys = api.keys_list(service_name, env=env)
    except ApiError as e:
        console.print(f"[red]✗[/red] {e.message}")
        raise typer.Exit(1)
    except Exception as e:
        console.print(f"[red]✗[/red] {e}")
        raise typer.Exit(1)

    if not keys:
        console.print("  No API keys configured.")
        console.print("\n  [dim]Next: loony keys create[/dim]")
        return

    console.print(f"\n  API keys for [bold]{service_name}[/bold]:\n")
    tbl = Table()
    tbl.add_column("Prefix")
    tbl.add_column("Type")
    tbl.add_column("Name")
    tbl.add_column("Created")
    for k in keys:
        tbl.add_row(k["prefix"], k["type"], k.get("name", ""), k.get("created_at", "")[:19])
    console.print(tbl)


@keys_app.command("revoke")
def keys_revoke_cmd(
    prefix: str = typer.Argument(..., help="Key prefix to revoke (e.g., pk_live_abc123)"),
    env: str | None = typer.Option(None, "--env", "-e", hidden=True),
) -> None:
    """Revoke an API key."""
    service_name = _get_service_name()

    try:
        api.keys_revoke(service_name, prefix, env=env)
        console.print(f"[green]✓[/green] Revoked key [bold]{prefix}[/bold]")
        console.print("\n  [dim]Next: loony keys list to see remaining keys[/dim]")
    except ApiError as e:
        console.print(f"[red]✗[/red] {e.message}")
        raise typer.Exit(1)
    except Exception as e:
        console.print(f"[red]✗[/red] {e}")
        raise typer.Exit(1)


# ─── MCP ──────────────────────────────────────────────────────────────────────

mcp_app = typer.Typer(help="Manage MCP (Model Context Protocol) endpoints for agent access.")
app.add_typer(mcp_app, name="mcp")


@mcp_app.command("status")
def mcp_status(
    env: str | None = typer.Option(None, "--env", "-e", hidden=True),
    show_key: bool = typer.Option(False, "--show-key", help="Show full API key in config"),
) -> None:
    """Show MCP endpoint URL and connection config for the current service."""
    service_name = _get_service_name()
    target_env = env or config.get_current_env()
    env_config = config.get_env_config(target_env)
    endpoint = env_config.get("endpoint", "")

    if not endpoint:
        console.print(f"[red]✗[/red] No endpoint configured for '{target_env}'. Run 'loony login' first.")
        raise typer.Exit(1)

    mcp_url = f"{endpoint}/mcp/v1/{service_name}/mcp"

    # Get keys to show auth info
    key_display = "pk_live_xxx..."
    try:
        keys = api.keys_list(service_name, show_key=show_key, env=env)
        active_keys = [k for k in keys if k.get("type") == "publishable"]
        if active_keys:
            if show_key and active_keys[0].get("key"):
                key_display = active_keys[0]["key"]
            else:
                key_display = active_keys[0]["prefix"] + "..."
            key_count = len(active_keys)
        else:
            key_count = 0
    except Exception:
        key_count = 0

    console.print(f"\n  MCP endpoint for [bold]{service_name}[/bold]:\n")
    console.print(f"  URL:   {mcp_url}")
    if key_count:
        console.print(f"  Keys:  {key_count} active ({key_display})")
    else:
        console.print("  Keys:  [yellow]none[/yellow] — run 'loony keys create' first")

    console.print("\n  [dim]Claude Desktop / Cursor config:[/dim]")
    console.print(f"""  {{
    "mcpServers": {{
      "{service_name}": {{
        "url": "{mcp_url}",
        "headers": {{"Authorization": "Bearer {key_display}"}}
      }}
    }}
  }}""")

    if not key_count:
        console.print("\n  [dim]Next: loony keys create --name 'my agent'[/dim]")
    elif not show_key:
        console.print("\n  [dim]Tip: add --show-key to include the full API key in the config[/dim]")
    console.print()


# ─── Secrets ──────────────────────────────────────────────────────────────────

secrets_app = typer.Typer(help="Manage service secrets.")
app.add_typer(secrets_app, name="secrets")


def _get_service_name() -> str:
    """Read service name from loony.yaml in cwd."""
    from pathlib import Path

    import yaml

    loony_yaml = Path.cwd() / "loony.yaml"
    if not loony_yaml.exists():
        console.print("[red]✗[/red] No loony.yaml found. Run 'loony init <name>' or cd into a service directory.")
        raise typer.Exit(1)
    with open(loony_yaml) as f:
        conf = yaml.safe_load(f)
    name = conf.get("name", "")
    if not name:
        console.print("[red]✗[/red] loony.yaml missing 'name' field")
        raise typer.Exit(1)
    return str(name)


@secrets_app.command("set")
def secrets_set(
    key_value: str = typer.Argument(..., help="SECRET_NAME=value or SECRET_NAME (will prompt)"),
    env: str | None = typer.Option(None, "--env", "-e", hidden=True),
) -> None:
    """Set a secret for the current service."""
    name = _get_service_name()

    if "=" in key_value:
        secret_name, value = key_value.split("=", 1)
    else:
        secret_name = key_value
        value = typer.prompt(f"Value for {secret_name}", hide_input=True)

    try:
        result = api.secrets_set(name, secret_name=secret_name, value=value, env=env)
        console.print(f"[green]✓[/green] Secret [bold]{secret_name}[/bold] {result.get('status', 'saved')}")
        console.print("\n  [dim]Next: loony deploy to use the secret, or loony secrets list to verify[/dim]")
    except ApiError as e:
        console.print(f"[red]✗[/red] {e.message}")
        raise typer.Exit(1)
    except Exception as e:
        console.print(f"[red]✗[/red] {e}")
        raise typer.Exit(1)


@secrets_app.command("list")
def secrets_list_cmd(
    env: str | None = typer.Option(None, "--env", "-e", hidden=True),
) -> None:
    """List secret names (values are never shown)."""
    name = _get_service_name()

    try:
        secrets = api.secrets_list(name, env=env)
    except ApiError as e:
        console.print(f"[red]✗[/red] {e.message}")
        raise typer.Exit(1)
    except Exception as e:
        console.print(f"[red]✗[/red] {e}")
        raise typer.Exit(1)

    if not secrets:
        console.print("  No secrets configured.")
        console.print("\n  [dim]Next: loony secrets set API_KEY=xxx[/dim]")
        return

    console.print(f"\n  Secrets for [bold]{name}[/bold]:\n")
    for s in secrets:
        console.print(f"  {s}")
    console.print(f"\n  {len(secrets)} secret(s)")


@secrets_app.command("remove")
def secrets_remove_cmd(
    secret_name: str = typer.Argument(..., help="Secret name to remove"),
    env: str | None = typer.Option(None, "--env", "-e", hidden=True),
) -> None:
    """Remove a secret from the current service."""
    name = _get_service_name()

    try:
        api.secrets_remove(name, secret_name=secret_name, env=env)
        console.print(f"[green]✓[/green] Removed secret [bold]{secret_name}[/bold]")
        console.print("\n  [dim]Next: loony secrets list to verify[/dim]")
    except ApiError as e:
        console.print(f"[red]✗[/red] {e.message}")
        raise typer.Exit(1)
    except Exception as e:
        console.print(f"[red]✗[/red] {e}")
        raise typer.Exit(1)


# ─── Skills ────────────────────────────────────────────────────────────────────

skills_app = typer.Typer(help="Manage agent skills.")
app.add_typer(skills_app, name="skills")


@skills_app.callback(invoke_without_command=True)
def skills_default(ctx: typer.Context) -> None:
    """List available and installed skills."""
    if ctx.invoked_subcommand is not None:
        return
    # Default: show list
    _skills_list()


@skills_app.command("list")
def skills_list() -> None:
    """List available and installed skills."""
    _skills_list()


def _skills_list() -> None:
    from pathlib import Path

    result = skills_mod.list_skills(Path.cwd())

    table = Table(title="Skills")
    table.add_column("Name")
    table.add_column("Description")
    table.add_column("Status")

    for name, desc in result["available"].items():
        if name in result["installed"]:
            status = "[green]installed[/green]"
        else:
            status = "[dim]available[/dim]"
        table.add_row(name, desc, status)

    # Show user skills too
    for name, kind in result["installed"].items():
        if kind == "user":
            table.add_row(name, "(user-created)", "[cyan]custom[/cyan]")

    console.print(table)

    if not result["agent"]:
        console.print("\n[yellow]⚠[/yellow] No agent detected. Run 'loony init' first.")


@skills_app.command("add")
def skills_add(
    name: str = typer.Argument(..., help="Skill name to add"),
    agent: str | None = typer.Option(None, "--agent", "-a", help="Agent type: claude, cursor, codex"),
) -> None:
    """Add a bundled skill to the project."""
    from pathlib import Path

    try:
        result = skills_mod.add_skill(name, agent=agent, project_dir=Path.cwd())
        console.print(f"[green]✓[/green] {result['action'].title()} skill [bold]{result['name']}[/bold]")
        console.print(f"  {result['path']}")
    except ValueError as e:
        console.print(f"[red]✗[/red] {e}")
        raise typer.Exit(1)


@skills_app.command("remove")
def skills_remove(
    name: str = typer.Argument(..., help="Skill name to remove"),
) -> None:
    """Remove a skill from the project."""
    from pathlib import Path

    try:
        result = skills_mod.remove_skill(name, project_dir=Path.cwd())
        console.print(f"[green]✓[/green] Removed skill [bold]{result['name']}[/bold]")
    except ValueError as e:
        console.print(f"[red]✗[/red] {e}")
        raise typer.Exit(1)


if __name__ == "__main__":
    app()
