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

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 .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()
    _check_version()


@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", help="Environment to login to"),
) -> None:
    """Authenticate with loony.dev via browser."""
    env = env or config.get_current_env()
    console.print(f"Logging into [bold]{env}[/bold]...")
    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(f"  Environment: {env}")
        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", help="Environment to logout from"),
) -> None:
    """Clear stored credentials."""
    env = env or config.get_current_env()
    config.clear_credentials(env)
    console.print(f"[green]✓[/green] Logged out from [bold]{env}[/bold]")


@app.command()
def env(
    name: str | None = typer.Argument(None, help="Environment to switch to"),
) -> None:
    """Show or switch the current environment."""
    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 to control plane and show user info."""
    current = config.get_current_env()
    env_config = config.get_env_config()

    console.print(f"Environment: [bold]{current}[/bold]")
    console.print(f"Endpoint: {env_config.get('endpoint', '(not configured)')}")
    console.print()

    # 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}")


# ─── 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)


# ─── 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", help="Environment to deploy to"),
) -> 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}")
        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":
        import time

        deployment_id = int(resp["deployment_id"])
        console.print("\n  Running pipeline...")

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

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

                # Render table summary if available
                summary = status.get("summary")
                if summary and isinstance(summary, dict):
                    tables = summary.get("tables", [])
                    if tables:
                        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:
                            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)

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

                # Timing
                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 run to re-run, loony logs to see history[/dim]")
                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]")
                break


# ─── 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", help="Environment"),
) -> None:
    """Trigger a pipeline run using the latest deployment."""
    from pathlib import Path

    # Get service name from loony.yaml in cwd
    loony_yaml = Path.cwd() / "loony.yaml"
    if not loony_yaml.exists():
        console.print("[red]✗[/red] No loony.yaml found. Are you in a service directory?")
        raise typer.Exit(1)

    import yaml

    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)

    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}")
        raise typer.Exit(1)

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

    # Poll for completion (same logic as deploy)
    import time

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

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

            summary = status.get("summary")
            if summary and isinstance(summary, dict):
                tables = summary.get("tables", [])
                if tables:
                    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:
                        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)

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

            # Timing
            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]")
            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]")
            break


# ─── 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", help="Environment"),
) -> None:
    """View run history and deployment logs."""
    from pathlib import Path

    import yaml
    from rich.text import Text

    loony_yaml = Path.cwd() / "loony.yaml"
    if not loony_yaml.exists():
        console.print("[red]✗[/red] No loony.yaml found. Are you in a service directory?")
        raise typer.Exit(1)

    with open(loony_yaml) as f:
        conf = yaml.safe_load(f)
    name = conf.get("name", "")

    # Detail view: specific deployment or --last
    if deployment_id is not None or last:
        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)

        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):
            tables_data = summary.get("tables", [])
            if tables_data:
                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_str = f"[green]+{delta:,}[/green]"
                    elif delta < 0:
                        net_str = f"[red]{delta:,}[/red]"
                    else:
                        net_str = "[dim]—[/dim]"
                    extracted = t.get("extracted", 0)
                    ext = f"{extracted:,}" if extracted else ""
                    tbl.add_row(str(t["table"]), f"{t['rows']:,}", ext, net_str)
                console.print(tbl)
        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(
    env: str | None = typer.Option(None, "--env", "-e", help="Environment"),
) -> None:
    """Show tables, measures, and dimensions for the current service."""
    from pathlib import Path

    import yaml

    loony_yaml = Path.cwd() / "loony.yaml"
    schema_yaml = Path.cwd() / "schema.yaml"

    if not loony_yaml.exists():
        console.print("[red]✗[/red] No loony.yaml found. Are you in a service directory?")
        raise typer.Exit(1)

    with open(loony_yaml) as f:
        conf = yaml.safe_load(f)
    name = conf.get("name", "")

    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 Exception:
        pass

    # Render
    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)

    # Also show tables in DB but not in schema
    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 = f"{m_type}({m_col})" if m_col else m_type
                    console.print(f"    {m_name:<20} {detail:<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}")


# ─── 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", help="Environment"),
) -> None:
    """Run a read-only SQL query against the service database."""
    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. Are you in a service directory?")
        raise typer.Exit(1)

    with open(loony_yaml) as f:
        conf = yaml.safe_load(f)
    name = conf.get("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}")
        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)


# ─── 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. Are you in 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", help="Environment"),
) -> 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')}")
    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", help="Environment"),
) -> None:
    """List secret names (values are never shown)."""
    name = _get_service_name()

    try:
        secrets = api.secrets_list(name, env=env)
    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", help="Environment"),
) -> 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]")
    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()
