"""Admin management CLI commands.

This Typer sub-app exposes all admin REST endpoints under
``/api/v1/admin/*`` as CLI commands. It is registered on the main
``agentkit`` app as ``agentkit admin <group> <command>``.

Authentication is handled by :class:`AdminHttpClient`, which reads
credentials from args, env vars, or ``~/.agentkit/admin_config.yaml``.
Run ``agentkit admin login`` once to populate the config file.
"""

from __future__ import annotations

import json as _json
from pathlib import Path
from typing import Optional

import httpx
import typer
import yaml
from rich import print as rprint
from rich.console import Console
from rich.table import Table

from agentkit.cli.admin_client import DEFAULT_CONFIG_PATH, AdminHttpClient

admin_app = typer.Typer(
    name="admin",
    help="Admin management commands (departments, users, LLM, skills, KB, usage).",
    no_args_is_help=True,
)
console = Console()

# ---------------------------------------------------------------------------
# Shared option definitions (used as defaults via `= typer.Option(...)`)
# ---------------------------------------------------------------------------

ServerUrlOption = typer.Option(
    None, "--server-url", "-s", help="Server URL (default: http://localhost:18001)"
)
TokenOption = typer.Option(None, "--token", "-t", help="JWT access token")
ApiKeyOption = typer.Option(None, "--api-key", "-k", help="API key")
JsonFlag = typer.Option(False, "--json", help="Output raw JSON instead of a Rich table")


# ---------------------------------------------------------------------------
# Error handling helper
# ---------------------------------------------------------------------------


def _handle_http_error(e: httpx.HTTPStatusError, server_url: str) -> None:
    """Translate an HTTP error into a friendly message and exit."""
    status = e.response.status_code
    if status == 401:
        rprint("[red]Error: Authentication failed. Run 'agentkit admin login' first.[/red]")
    elif status == 403:
        rprint("[red]Error: Admin permission required.[/red]")
    elif status == 404:
        rprint("[red]Error: Resource not found.[/red]")
    elif status == 409:
        try:
            detail = e.response.json().get("detail", "")
        except ValueError:
            detail = e.response.text
        rprint(f"[red]Error: Conflict — {detail}[/red]")
    else:
        rprint(f"[red]Error: {status} — {e.response.text}[/red]")
    raise typer.Exit(1)


def _handle_connect_error(server_url: str) -> None:
    rprint(f"[red]Error: Cannot connect to server at {server_url}[/red]")
    rprint("[dim]Is the server running? Start it with: agentkit serve[/dim]")
    raise typer.Exit(1)


def _build_client(
    server_url: Optional[str],
    token: Optional[str],
    api_key: Optional[str],
) -> AdminHttpClient:
    """Construct an :class:`AdminHttpClient` from CLI options."""
    return AdminHttpClient.from_config(
        server_url=server_url,
        token=token,
        api_key=api_key,
    )


def _emit_json(data: object) -> None:
    rprint(_json.dumps(data, indent=2, ensure_ascii=False, default=str))


def _safe_get(
    client: AdminHttpClient, path: str, server_url: str, params: dict | None = None
) -> object:
    try:
        return client.get(path, params=params)
    except httpx.ConnectError:
        _handle_connect_error(server_url)
    except httpx.HTTPStatusError as e:
        _handle_http_error(e, server_url)


def _safe_post(
    client: AdminHttpClient, path: str, server_url: str, body: dict | None = None
) -> object:
    try:
        return client.post(path, json=body)
    except httpx.ConnectError:
        _handle_connect_error(server_url)
    except httpx.HTTPStatusError as e:
        _handle_http_error(e, server_url)


def _safe_patch(
    client: AdminHttpClient, path: str, server_url: str, body: dict | None = None
) -> object:
    try:
        return client.patch(path, json=body)
    except httpx.ConnectError:
        _handle_connect_error(server_url)
    except httpx.HTTPStatusError as e:
        _handle_http_error(e, server_url)


def _safe_put(client: AdminHttpClient, path: str, server_url: str, body: dict | None = None) -> object:
    try:
        return client.put(path, json=body)
    except httpx.ConnectError:
        _handle_connect_error(server_url)
    except httpx.HTTPStatusError as e:
        _handle_http_error(e, server_url)


def _safe_delete(
    client: AdminHttpClient, path: str, server_url: str, params: dict | None = None
) -> object:
    try:
        return client.delete(path, params=params)
    except httpx.ConnectError:
        _handle_connect_error(server_url)
    except httpx.HTTPStatusError as e:
        _handle_http_error(e, server_url)


# ---------------------------------------------------------------------------
# Login command (top-level)
# ---------------------------------------------------------------------------


@admin_app.command("login")
def admin_login(
    username: str = typer.Option(..., "--username", "-u", help="Admin username"),
    password: str = typer.Option(
        ..., "--password", "-p", help="Admin password", prompt=True, hide_input=True
    ),
    server_url: Optional[str] = ServerUrlOption,
    config_path: Optional[str] = typer.Option(
        None, "--config-path", help="Path to admin config file"
    ),
) -> None:
    """Login and save the access token to ``~/.agentkit/admin_config.yaml``."""
    resolved_url = server_url or "http://localhost:18001"
    client = AdminHttpClient(resolved_url)
    try:
        token = client.login(username, password)
    except httpx.ConnectError:
        _handle_connect_error(resolved_url)
    except httpx.HTTPStatusError as e:
        _handle_http_error(e, resolved_url)

    path = Path(config_path) if config_path else DEFAULT_CONFIG_PATH
    path.parent.mkdir(parents=True, exist_ok=True)
    config = {"server_url": resolved_url, "token": token}
    path.write_text(yaml.dump(config, default_flow_style=False, allow_unicode=True))
    rprint(f"[green]Login successful. Token saved to {path}[/green]")


# ---------------------------------------------------------------------------
# Department sub-group
# ---------------------------------------------------------------------------

dept_app = typer.Typer(name="department", help="Department management", no_args_is_help=True)
admin_app.add_typer(dept_app, name="department")


@dept_app.command("list")
def dept_list(
    server_url: Optional[str] = ServerUrlOption,
    token: Optional[str] = TokenOption,
    api_key: Optional[str] = ApiKeyOption,
    json_output: bool = JsonFlag,
) -> None:
    """List all departments."""
    client = _build_client(server_url, token, api_key)
    depts = _safe_get(client, "/api/v1/admin/departments", client.base_url)
    if json_output:
        _emit_json(depts)
        return
    if not depts:
        rprint("[dim]No departments found[/dim]")
        return
    table = Table(title="Departments")
    table.add_column("ID", style="cyan")
    table.add_column("Name")
    table.add_column("Description")
    table.add_column("Active")
    table.add_column("Created")
    for d in depts:
        table.add_row(
            str(d.get("id", "")),
            str(d.get("name", "")),
            str(d.get("description", "")),
            "OK" if d.get("is_active") else "--",
            str(d.get("created_at", "")),
        )
    console.print(table)


@dept_app.command("create")
def dept_create(
    name: str = typer.Option(..., "--name", "-n", help="Department name"),
    description: str = typer.Option("", "--description", "-d", help="Department description"),
    server_url: Optional[str] = ServerUrlOption,
    token: Optional[str] = TokenOption,
    api_key: Optional[str] = ApiKeyOption,
    json_output: bool = JsonFlag,
) -> None:
    """Create a department."""
    client = _build_client(server_url, token, api_key)
    result = _safe_post(
        client,
        "/api/v1/admin/departments",
        client.base_url,
        {"name": name, "description": description},
    )
    if json_output:
        _emit_json(result)
        return
    rprint(f"[green]Department created:[/green] {result.get('name')} (id={result.get('id')})")


@dept_app.command("update")
def dept_update(
    dept_id: str = typer.Argument(..., help="Department ID"),
    name: Optional[str] = typer.Option(None, "--name", "-n", help="New name"),
    description: Optional[str] = typer.Option(None, "--description", "-d", help="New description"),
    server_url: Optional[str] = ServerUrlOption,
    token: Optional[str] = TokenOption,
    api_key: Optional[str] = ApiKeyOption,
    json_output: bool = JsonFlag,
) -> None:
    """Update a department's name or description."""
    body: dict[str, object] = {}
    if name is not None:
        body["name"] = name
    if description is not None:
        body["description"] = description
    if not body:
        rprint("[red]Error: Provide --name or --description to update[/red]")
        raise typer.Exit(1)
    client = _build_client(server_url, token, api_key)
    result = _safe_patch(client, f"/api/v1/admin/departments/{dept_id}", client.base_url, body)
    if json_output:
        _emit_json(result)
        return
    rprint(f"[green]Department updated:[/green] {result.get('name')} (id={result.get('id')})")


@dept_app.command("delete")
def dept_delete(
    dept_id: str = typer.Argument(..., help="Department ID"),
    server_url: Optional[str] = ServerUrlOption,
    token: Optional[str] = TokenOption,
    api_key: Optional[str] = ApiKeyOption,
    json_output: bool = JsonFlag,
) -> None:
    """Delete a department."""
    client = _build_client(server_url, token, api_key)
    result = _safe_delete(client, f"/api/v1/admin/departments/{dept_id}", client.base_url)
    if json_output:
        _emit_json(result)
        return
    rprint(f"[green]Department deleted:[/green] {dept_id}")


@dept_app.command("enable")
def dept_enable(
    dept_id: str = typer.Argument(..., help="Department ID"),
    server_url: Optional[str] = ServerUrlOption,
    token: Optional[str] = TokenOption,
    api_key: Optional[str] = ApiKeyOption,
    json_output: bool = JsonFlag,
) -> None:
    """Enable a disabled department."""
    client = _build_client(server_url, token, api_key)
    result = _safe_post(client, f"/api/v1/admin/departments/{dept_id}/enable", client.base_url)
    if json_output:
        _emit_json(result)
        return
    rprint(f"[green]Department enabled:[/green] {dept_id}")


@dept_app.command("disable")
def dept_disable(
    dept_id: str = typer.Argument(..., help="Department ID"),
    server_url: Optional[str] = ServerUrlOption,
    token: Optional[str] = TokenOption,
    api_key: Optional[str] = ApiKeyOption,
    json_output: bool = JsonFlag,
) -> None:
    """Disable a department."""
    client = _build_client(server_url, token, api_key)
    result = _safe_post(client, f"/api/v1/admin/departments/{dept_id}/disable", client.base_url)
    if json_output:
        _emit_json(result)
        return
    rprint(f"[green]Department disabled:[/green] {dept_id}")


@dept_app.command("bind-skill")
def dept_bind_skill(
    dept_id: str = typer.Argument(..., help="Department ID"),
    skill_name: str = typer.Argument(..., help="Skill name"),
    server_url: Optional[str] = ServerUrlOption,
    token: Optional[str] = TokenOption,
    api_key: Optional[str] = ApiKeyOption,
    json_output: bool = JsonFlag,
) -> None:
    """Bind a skill to a department."""
    client = _build_client(server_url, token, api_key)
    result = _safe_post(
        client,
        f"/api/v1/admin/departments/{dept_id}/skills/{skill_name}",
        client.base_url,
    )
    if json_output:
        _emit_json(result)
        return
    rprint(f"[green]Skill '{skill_name}' bound to department {dept_id}[/green]")


@dept_app.command("unbind-skill")
def dept_unbind_skill(
    dept_id: str = typer.Argument(..., help="Department ID"),
    skill_name: str = typer.Argument(..., help="Skill name"),
    server_url: Optional[str] = ServerUrlOption,
    token: Optional[str] = TokenOption,
    api_key: Optional[str] = ApiKeyOption,
    json_output: bool = JsonFlag,
) -> None:
    """Unbind a skill from a department."""
    client = _build_client(server_url, token, api_key)
    result = _safe_delete(
        client,
        f"/api/v1/admin/departments/{dept_id}/skills/{skill_name}",
        client.base_url,
    )
    if json_output:
        _emit_json(result)
        return
    rprint(f"[green]Skill '{skill_name}' unbound from department {dept_id}[/green]")


@dept_app.command("bind-kb")
def dept_bind_kb(
    dept_id: str = typer.Argument(..., help="Department ID"),
    source_id: str = typer.Argument(..., help="KB source ID"),
    server_url: Optional[str] = ServerUrlOption,
    token: Optional[str] = TokenOption,
    api_key: Optional[str] = ApiKeyOption,
    json_output: bool = JsonFlag,
) -> None:
    """Bind a KB source to a department."""
    client = _build_client(server_url, token, api_key)
    result = _safe_post(
        client,
        f"/api/v1/admin/departments/{dept_id}/kb/{source_id}",
        client.base_url,
    )
    if json_output:
        _emit_json(result)
        return
    rprint(f"[green]KB source '{source_id}' bound to department {dept_id}[/green]")


@dept_app.command("unbind-kb")
def dept_unbind_kb(
    dept_id: str = typer.Argument(..., help="Department ID"),
    source_id: str = typer.Argument(..., help="KB source ID"),
    server_url: Optional[str] = ServerUrlOption,
    token: Optional[str] = TokenOption,
    api_key: Optional[str] = ApiKeyOption,
    json_output: bool = JsonFlag,
) -> None:
    """Unbind a KB source from a department."""
    client = _build_client(server_url, token, api_key)
    result = _safe_delete(
        client,
        f"/api/v1/admin/departments/{dept_id}/kb/{source_id}",
        client.base_url,
    )
    if json_output:
        _emit_json(result)
        return
    rprint(f"[green]KB source '{source_id}' unbound from department {dept_id}[/green]")


@dept_app.command("list-skills")
def dept_list_skills(
    dept_id: str = typer.Argument(..., help="Department ID"),
    server_url: Optional[str] = ServerUrlOption,
    token: Optional[str] = TokenOption,
    api_key: Optional[str] = ApiKeyOption,
    json_output: bool = JsonFlag,
) -> None:
    """List skills bound to a department."""
    client = _build_client(server_url, token, api_key)
    skills = _safe_get(
        client,
        f"/api/v1/admin/departments/{dept_id}/skills",
        client.base_url,
    )
    if json_output:
        _emit_json(skills)
        return
    if not skills:
        rprint(f"[dim]No skills bound to department {dept_id}[/dim]")
        return
    table = Table(title=f"Skills for department {dept_id}")
    table.add_column("Skill Name", style="cyan")
    for s in skills:
        table.add_row(str(s))
    console.print(table)


@dept_app.command("list-kb")
def dept_list_kb(
    dept_id: str = typer.Argument(..., help="Department ID"),
    server_url: Optional[str] = ServerUrlOption,
    token: Optional[str] = TokenOption,
    api_key: Optional[str] = ApiKeyOption,
    json_output: bool = JsonFlag,
) -> None:
    """List KB sources bound to a department."""
    client = _build_client(server_url, token, api_key)
    sources = _safe_get(
        client,
        f"/api/v1/admin/departments/{dept_id}/kb",
        client.base_url,
    )
    if json_output:
        _emit_json(sources)
        return
    if not sources:
        rprint(f"[dim]No KB sources bound to department {dept_id}[/dim]")
        return
    table = Table(title=f"KB sources for department {dept_id}")
    table.add_column("Source ID", style="cyan")
    for s in sources:
        table.add_row(str(s))
    console.print(table)


@dept_app.command("list-quotas")
def dept_list_quotas(
    dept_id: str = typer.Argument(..., help="Department ID"),
    server_url: Optional[str] = ServerUrlOption,
    token: Optional[str] = TokenOption,
    api_key: Optional[str] = ApiKeyOption,
    json_output: bool = JsonFlag,
) -> None:
    """List quotas for a department."""
    client = _build_client(server_url, token, api_key)
    quotas = _safe_get(
        client,
        f"/api/v1/admin/departments/{dept_id}/quotas",
        client.base_url,
    )
    if json_output:
        _emit_json(quotas)
        return
    if not quotas:
        rprint(f"[dim]No quotas set for department {dept_id}[/dim]")
        return
    table = Table(title=f"Quotas for department {dept_id}")
    table.add_column("Type", style="cyan")
    table.add_column("Limit")
    table.add_column("Period")
    for q in quotas:
        table.add_row(
            str(q.get("quota_type", "")),
            str(q.get("limit_value", "")),
            str(q.get("period", "")),
        )
    console.print(table)


@dept_app.command("set-quota")
def dept_set_quota(
    dept_id: str = typer.Argument(..., help="Department ID"),
    quota_type: str = typer.Option(..., "--type", help="Quota type (token/cost/model_whitelist)"),
    limit_value: str = typer.Option(
        ..., "--limit", help="Limit value (int for token/cost, comma-separated for model_whitelist)"
    ),
    period: str = typer.Option("daily", "--period", help="Quota period (daily/monthly)"),
    server_url: Optional[str] = ServerUrlOption,
    token: Optional[str] = TokenOption,
    api_key: Optional[str] = ApiKeyOption,
    json_output: bool = JsonFlag,
) -> None:
    """Set (upsert) a quota for a department."""
    if quota_type == "model_whitelist":
        limit: int | list[str] = [s.strip() for s in limit_value.split(",") if s.strip()]
    else:
        try:
            limit = int(limit_value)
        except ValueError:
            rprint(f"[red]Error: --limit must be an integer for quota_type={quota_type}[/red]")
            raise typer.Exit(1)
    client = _build_client(server_url, token, api_key)
    result = _safe_put(
        client,
        f"/api/v1/admin/departments/{dept_id}/quotas",
        client.base_url,
        {"quota_type": quota_type, "limit_value": limit, "period": period},
    )
    if json_output:
        _emit_json(result)
        return
    rprint(f"[green]Quota set:[/green] {quota_type}={limit} ({period}) for {dept_id}")


@dept_app.command("delete-quota")
def dept_delete_quota(
    dept_id: str = typer.Argument(..., help="Department ID"),
    quota_type: str = typer.Option(..., "--type", help="Quota type to delete"),
    period: str = typer.Option("daily", "--period", help="Quota period"),
    server_url: Optional[str] = ServerUrlOption,
    token: Optional[str] = TokenOption,
    api_key: Optional[str] = ApiKeyOption,
    json_output: bool = JsonFlag,
) -> None:
    """Delete a quota for a department."""
    client = _build_client(server_url, token, api_key)
    result = _safe_delete(
        client,
        f"/api/v1/admin/departments/{dept_id}/quotas",
        client.base_url,
        {"quota_type": quota_type, "period": period},
    )
    if json_output:
        _emit_json(result)
        return
    rprint(f"[green]Quota deleted:[/green] {quota_type} ({period}) for {dept_id}")


# ---------------------------------------------------------------------------
# User sub-group
# ---------------------------------------------------------------------------

user_app = typer.Typer(name="user", help="User management", no_args_is_help=True)
admin_app.add_typer(user_app, name="user")


@user_app.command("list")
def user_list(
    department_id: Optional[str] = typer.Option(
        None, "--department-id", "-d", help="Filter by department ID"
    ),
    server_url: Optional[str] = ServerUrlOption,
    token: Optional[str] = TokenOption,
    api_key: Optional[str] = ApiKeyOption,
    json_output: bool = JsonFlag,
) -> None:
    """List users, optionally filtered by department."""
    client = _build_client(server_url, token, api_key)
    params: dict[str, object] = {}
    if department_id:
        params["department_id"] = department_id
    users = _safe_get(client, "/api/v1/admin/users", client.base_url, params=params or None)
    if json_output:
        _emit_json(users)
        return
    if not users:
        rprint("[dim]No users found[/dim]")
        return
    table = Table(title="Users")
    table.add_column("ID", style="cyan")
    table.add_column("Username")
    table.add_column("Email")
    table.add_column("Role")
    table.add_column("Active")
    for u in users:
        table.add_row(
            str(u.get("id", "")),
            str(u.get("username", "")),
            str(u.get("email", "")),
            str(u.get("role", "")),
            "OK" if u.get("is_active") else "--",
        )
    console.print(table)


@user_app.command("create")
def user_create(
    username: str = typer.Option(..., "--username", "-u", help="Username"),
    email: str = typer.Option(..., "--email", "-e", help="Email"),
    password: str = typer.Option(
        ..., "--password", "-p", help="Password", prompt=True, hide_input=True
    ),
    role: str = typer.Option("member", "--role", "-r", help="Role (member/operator/admin)"),
    department_ids: Optional[str] = typer.Option(
        None, "--department-ids", help="Comma-separated department IDs"
    ),
    server_url: Optional[str] = ServerUrlOption,
    token: Optional[str] = TokenOption,
    api_key: Optional[str] = ApiKeyOption,
    json_output: bool = JsonFlag,
) -> None:
    """Create a new user."""
    body: dict[str, object] = {
        "username": username,
        "email": email,
        "password": password,
        "role": role,
    }
    if department_ids:
        body["department_ids"] = [s.strip() for s in department_ids.split(",") if s.strip()]
    client = _build_client(server_url, token, api_key)
    result = _safe_post(client, "/api/v1/admin/users", client.base_url, body)
    if json_output:
        _emit_json(result)
        return
    rprint(f"[green]User created:[/green] {result.get('username')} (id={result.get('id')})")


@user_app.command("update")
def user_update(
    user_id: str = typer.Argument(..., help="User ID"),
    role: Optional[str] = typer.Option(None, "--role", "-r", help="New role"),
    is_active: Optional[bool] = typer.Option(None, "--active/--inactive", help="Active flag"),
    is_terminal_authorized: Optional[bool] = typer.Option(
        None, "--terminal-authorized/--no-terminal-authorized"
    ),
    is_server_terminal_authorized: Optional[bool] = typer.Option(
        None, "--server-terminal-authorized/--no-server-terminal-authorized"
    ),
    server_url: Optional[str] = ServerUrlOption,
    token: Optional[str] = TokenOption,
    api_key: Optional[str] = ApiKeyOption,
    json_output: bool = JsonFlag,
) -> None:
    """Update a user's role or active flag."""
    body: dict[str, object] = {}
    if role is not None:
        body["role"] = role
    if is_active is not None:
        body["is_active"] = is_active
    if is_terminal_authorized is not None:
        body["is_terminal_authorized"] = is_terminal_authorized
    if is_server_terminal_authorized is not None:
        body["is_server_terminal_authorized"] = is_server_terminal_authorized
    if not body:
        rprint("[red]Error: Provide at least one field to update[/red]")
        raise typer.Exit(1)
    client = _build_client(server_url, token, api_key)
    result = _safe_patch(client, f"/api/v1/admin/users/{user_id}", client.base_url, body)
    if json_output:
        _emit_json(result)
        return
    rprint(f"[green]User updated:[/green] {result.get('username')} (id={result.get('id')})")


@user_app.command("delete")
def user_delete(
    user_id: str = typer.Argument(..., help="User ID"),
    server_url: Optional[str] = ServerUrlOption,
    token: Optional[str] = TokenOption,
    api_key: Optional[str] = ApiKeyOption,
    json_output: bool = JsonFlag,
) -> None:
    """Soft-delete a user (sets is_active=False)."""
    client = _build_client(server_url, token, api_key)
    result = _safe_delete(client, f"/api/v1/admin/users/{user_id}", client.base_url)
    if json_output:
        _emit_json(result)
        return
    rprint(f"[green]User deleted:[/green] {user_id}")


@user_app.command("reset-password")
def user_reset_password(
    user_id: str = typer.Argument(..., help="User ID"),
    new_password: str = typer.Option(
        ..., "--password", "-p", help="New password", prompt=True, hide_input=True
    ),
    server_url: Optional[str] = ServerUrlOption,
    token: Optional[str] = TokenOption,
    api_key: Optional[str] = ApiKeyOption,
    json_output: bool = JsonFlag,
) -> None:
    """Reset a user's password (revokes all their sessions)."""
    client = _build_client(server_url, token, api_key)
    result = _safe_post(
        client,
        f"/api/v1/admin/users/{user_id}/reset-password",
        client.base_url,
        {"new_password": new_password},
    )
    if json_output:
        _emit_json(result)
        return
    rprint(f"[green]Password reset for user:[/green] {user_id}")


@user_app.command("assign-department")
def user_assign_department(
    user_id: str = typer.Argument(..., help="User ID"),
    dept_id: str = typer.Argument(..., help="Department ID"),
    server_url: Optional[str] = ServerUrlOption,
    token: Optional[str] = TokenOption,
    api_key: Optional[str] = ApiKeyOption,
    json_output: bool = JsonFlag,
) -> None:
    """Assign a user to a department."""
    client = _build_client(server_url, token, api_key)
    result = _safe_post(
        client,
        f"/api/v1/admin/users/{user_id}/departments/{dept_id}",
        client.base_url,
    )
    if json_output:
        _emit_json(result)
        return
    rprint(f"[green]User {user_id} assigned to department {dept_id}[/green]")


@user_app.command("remove-department")
def user_remove_department(
    user_id: str = typer.Argument(..., help="User ID"),
    dept_id: str = typer.Argument(..., help="Department ID"),
    server_url: Optional[str] = ServerUrlOption,
    token: Optional[str] = TokenOption,
    api_key: Optional[str] = ApiKeyOption,
    json_output: bool = JsonFlag,
) -> None:
    """Remove a user from a department."""
    client = _build_client(server_url, token, api_key)
    result = _safe_delete(
        client,
        f"/api/v1/admin/users/{user_id}/departments/{dept_id}",
        client.base_url,
    )
    if json_output:
        _emit_json(result)
        return
    rprint(f"[green]User {user_id} removed from department {dept_id}[/green]")


# ---------------------------------------------------------------------------
# LLM sub-group
# ---------------------------------------------------------------------------

llm_app = typer.Typer(name="llm", help="LLM configuration management", no_args_is_help=True)
admin_app.add_typer(llm_app, name="llm")


@llm_app.command("list-providers")
def llm_list_providers(
    server_url: Optional[str] = ServerUrlOption,
    token: Optional[str] = TokenOption,
    api_key: Optional[str] = ApiKeyOption,
    json_output: bool = JsonFlag,
) -> None:
    """List all LLM providers (API keys masked)."""
    client = _build_client(server_url, token, api_key)
    providers = _safe_get(client, "/api/v1/admin/llm/providers", client.base_url)
    if json_output:
        _emit_json(providers)
        return
    if not providers:
        rprint("[dim]No LLM providers configured[/dim]")
        return
    table = Table(title="LLM Providers")
    table.add_column("Name", style="cyan")
    table.add_column("Type")
    table.add_column("Base URL")
    table.add_column("API Key")
    table.add_column("Max Tokens")
    table.add_column("Timeout")
    for p in providers:
        table.add_row(
            str(p.get("name", "")),
            str(p.get("type", "")),
            str(p.get("base_url", "")),
            str(p.get("api_key", "")),
            str(p.get("max_tokens", "")),
            str(p.get("timeout", "")),
        )
    console.print(table)


@llm_app.command("add-provider")
def llm_add_provider(
    name: str = typer.Option(..., "--name", "-n", help="Provider name"),
    type: str = typer.Option("openai", "--type", help="Provider type"),
    api_key: str = typer.Option(..., "--api-key", help="API key"),
    base_url: str = typer.Option("", "--base-url", help="Base URL override"),
    max_tokens: int = typer.Option(4096, "--max-tokens", help="Max tokens"),
    timeout: float = typer.Option(60.0, "--timeout", help="Timeout in seconds"),
    server_url: Optional[str] = ServerUrlOption,
    token: Optional[str] = TokenOption,
    api_key_opt: Optional[str] = typer.Option(
        None, "--api-key-auth", "-k", help="Admin API key (auth)"
    ),
    json_output: bool = JsonFlag,
) -> None:
    """Create a new LLM provider."""
    body: dict[str, object] = {
        "name": name,
        "type": type,
        "api_key": api_key,
        "base_url": base_url,
        "max_tokens": max_tokens,
        "timeout": timeout,
    }
    client = _build_client(server_url, token, api_key_opt)
    result = _safe_post(client, "/api/v1/admin/llm/providers", client.base_url, body)
    if json_output:
        _emit_json(result)
        return
    rprint(f"[green]Provider created:[/green] {result.get('name')}")


@llm_app.command("update-provider")
def llm_update_provider(
    name: str = typer.Argument(..., help="Provider name"),
    type: Optional[str] = typer.Option(None, "--type", help="Provider type"),
    api_key: Optional[str] = typer.Option(None, "--api-key", help="API key"),
    base_url: Optional[str] = typer.Option(None, "--base-url", help="Base URL"),
    max_tokens: Optional[int] = typer.Option(None, "--max-tokens", help="Max tokens"),
    timeout: Optional[float] = typer.Option(None, "--timeout", help="Timeout"),
    server_url: Optional[str] = ServerUrlOption,
    token: Optional[str] = TokenOption,
    api_key_opt: Optional[str] = typer.Option(
        None, "--api-key-auth", "-k", help="Admin API key (auth)"
    ),
    json_output: bool = JsonFlag,
) -> None:
    """Update an LLM provider's configuration."""
    body: dict[str, object] = {}
    if type is not None:
        body["type"] = type
    if api_key is not None:
        body["api_key"] = api_key
    if base_url is not None:
        body["base_url"] = base_url
    if max_tokens is not None:
        body["max_tokens"] = max_tokens
    if timeout is not None:
        body["timeout"] = timeout
    if not body:
        rprint("[red]Error: Provide at least one field to update[/red]")
        raise typer.Exit(1)
    client = _build_client(server_url, token, api_key_opt)
    result = _safe_patch(client, f"/api/v1/admin/llm/providers/{name}", client.base_url, body)
    if json_output:
        _emit_json(result)
        return
    rprint(f"[green]Provider updated:[/green] {result.get('name')}")


@llm_app.command("delete-provider")
def llm_delete_provider(
    name: str = typer.Argument(..., help="Provider name"),
    server_url: Optional[str] = ServerUrlOption,
    token: Optional[str] = TokenOption,
    api_key: Optional[str] = ApiKeyOption,
    json_output: bool = JsonFlag,
) -> None:
    """Delete an LLM provider."""
    client = _build_client(server_url, token, api_key)
    result = _safe_delete(client, f"/api/v1/admin/llm/providers/{name}", client.base_url)
    if json_output:
        _emit_json(result)
        return
    rprint(f"[green]Provider deleted:[/green] {name}")


@llm_app.command("set-api-key")
def llm_set_api_key(
    name: str = typer.Argument(..., help="Provider name"),
    api_key: str = typer.Option(..., "--api-key", help="New API key"),
    server_url: Optional[str] = ServerUrlOption,
    token: Optional[str] = TokenOption,
    api_key_opt: Optional[str] = typer.Option(
        None, "--api-key-auth", "-k", help="Admin API key (auth)"
    ),
    json_output: bool = JsonFlag,
) -> None:
    """Set the API key for a provider (stored in .env, not YAML)."""
    client = _build_client(server_url, token, api_key_opt)
    result = _safe_post(
        client,
        f"/api/v1/admin/llm/providers/{name}/api-key",
        client.base_url,
        {"api_key": api_key},
    )
    if json_output:
        _emit_json(result)
        return
    rprint(f"[green]API key set for provider:[/green] {name}")


@llm_app.command("list-fallbacks")
def llm_list_fallbacks(
    server_url: Optional[str] = ServerUrlOption,
    token: Optional[str] = TokenOption,
    api_key: Optional[str] = ApiKeyOption,
    json_output: bool = JsonFlag,
) -> None:
    """List all fallback chains."""
    client = _build_client(server_url, token, api_key)
    fallbacks = _safe_get(client, "/api/v1/admin/llm/fallbacks", client.base_url)
    if json_output:
        _emit_json(fallbacks)
        return
    if not fallbacks:
        rprint("[dim]No fallback chains configured[/dim]")
        return
    table = Table(title="LLM Fallback Chains")
    table.add_column("Model", style="cyan")
    table.add_column("Chain")
    for model, chain in fallbacks.items():
        table.add_row(str(model), " → ".join(str(c) for c in chain))
    console.print(table)


@llm_app.command("set-fallback")
def llm_set_fallback(
    model: str = typer.Argument(..., help="Model name"),
    chain: str = typer.Option(
        ...,
        "--chain",
        "-c",
        help="Comma-separated fallback chain (e.g. provider1/model1,provider2/model2)",
    ),
    server_url: Optional[str] = ServerUrlOption,
    token: Optional[str] = TokenOption,
    api_key: Optional[str] = ApiKeyOption,
    json_output: bool = JsonFlag,
) -> None:
    """Set the fallback chain for a model."""
    chain_list = [s.strip() for s in chain.split(",") if s.strip()]
    if not chain_list:
        rprint("[red]Error: --chain must contain at least one entry[/red]")
        raise typer.Exit(1)
    client = _build_client(server_url, token, api_key)
    result = _safe_put(
        client,
        f"/api/v1/admin/llm/fallbacks/{model}",
        client.base_url,
        {"chain": chain_list},
    )
    if json_output:
        _emit_json(result)
        return
    rprint(f"[green]Fallback set for {model}:[/green] {' → '.join(chain_list)}")


@llm_app.command("delete-fallback")
def llm_delete_fallback(
    model: str = typer.Argument(..., help="Model name"),
    server_url: Optional[str] = ServerUrlOption,
    token: Optional[str] = TokenOption,
    api_key: Optional[str] = ApiKeyOption,
    json_output: bool = JsonFlag,
) -> None:
    """Delete the fallback chain for a model."""
    client = _build_client(server_url, token, api_key)
    result = _safe_delete(client, f"/api/v1/admin/llm/fallbacks/{model}", client.base_url)
    if json_output:
        _emit_json(result)
        return
    rprint(f"[green]Fallback deleted for model:[/green] {model}")


# ---------------------------------------------------------------------------
# Skill sub-group
# ---------------------------------------------------------------------------

skill_app = typer.Typer(name="skill", help="Skill management (admin)", no_args_is_help=True)
admin_app.add_typer(skill_app, name="skill")


@skill_app.command("list")
def skill_list(
    server_url: Optional[str] = ServerUrlOption,
    token: Optional[str] = TokenOption,
    api_key: Optional[str] = ApiKeyOption,
    json_output: bool = JsonFlag,
) -> None:
    """List all skills (calls GET /skills, not /admin/skills)."""
    client = _build_client(server_url, token, api_key)
    skills = _safe_get(client, "/api/v1/skills", client.base_url)
    if json_output:
        _emit_json(skills)
        return
    if not skills:
        rprint("[dim]No skills registered[/dim]")
        return
    table = Table(title="Skills")
    table.add_column("Name", style="cyan")
    table.add_column("Type")
    table.add_column("Description")
    for s in skills:
        table.add_row(
            str(s.get("name", "")),
            str(s.get("agent_type", "")),
            str(s.get("description", "")),
        )
    console.print(table)


@skill_app.command("enable")
def skill_enable(
    name: str = typer.Argument(..., help="Skill name"),
    server_url: Optional[str] = ServerUrlOption,
    token: Optional[str] = TokenOption,
    api_key: Optional[str] = ApiKeyOption,
    json_output: bool = JsonFlag,
) -> None:
    """Enable a previously-disabled skill."""
    client = _build_client(server_url, token, api_key)
    result = _safe_post(client, f"/api/v1/admin/skills/{name}/enable", client.base_url)
    if json_output:
        _emit_json(result)
        return
    rprint(f"[green]Skill enabled:[/green] {name}")


@skill_app.command("disable")
def skill_disable(
    name: str = typer.Argument(..., help="Skill name"),
    server_url: Optional[str] = ServerUrlOption,
    token: Optional[str] = TokenOption,
    api_key: Optional[str] = ApiKeyOption,
    json_output: bool = JsonFlag,
) -> None:
    """Disable a skill (hides it from GET /skills)."""
    client = _build_client(server_url, token, api_key)
    result = _safe_post(client, f"/api/v1/admin/skills/{name}/disable", client.base_url)
    if json_output:
        _emit_json(result)
        return
    rprint(f"[green]Skill disabled:[/green] {name}")


@skill_app.command("import")
def skill_import(
    yaml_file: str = typer.Argument(..., help="Path to skill YAML file"),
    server_url: Optional[str] = ServerUrlOption,
    token: Optional[str] = TokenOption,
    api_key: Optional[str] = ApiKeyOption,
    json_output: bool = JsonFlag,
) -> None:
    """Import a skill from a YAML file."""
    path = Path(yaml_file)
    if not path.exists():
        rprint(f"[red]Error: File not found: {yaml_file}[/red]")
        raise typer.Exit(1)
    yaml_content = path.read_text(encoding="utf-8")
    client = _build_client(server_url, token, api_key)
    result = _safe_post(
        client,
        "/api/v1/admin/skills/import",
        client.base_url,
        {"yaml_content": yaml_content},
    )
    if json_output:
        _emit_json(result)
        return
    rprint(f"[green]Skill imported:[/green] {result.get('name', yaml_file)}")


@skill_app.command("reload")
def skill_reload(
    name: str = typer.Argument(..., help="Skill name"),
    server_url: Optional[str] = ServerUrlOption,
    token: Optional[str] = TokenOption,
    api_key: Optional[str] = ApiKeyOption,
    json_output: bool = JsonFlag,
) -> None:
    """Reload a skill from its YAML file."""
    client = _build_client(server_url, token, api_key)
    result = _safe_post(client, f"/api/v1/admin/skills/{name}/reload", client.base_url)
    if json_output:
        _emit_json(result)
        return
    rprint(f"[green]Skill reloaded:[/green] {name}")


# ---------------------------------------------------------------------------
# KB sub-group
# ---------------------------------------------------------------------------

kb_app = typer.Typer(name="kb", help="Knowledge base management", no_args_is_help=True)
admin_app.add_typer(kb_app, name="kb")


@kb_app.command("list-documents")
def kb_list_documents(
    source_id: Optional[str] = typer.Option(None, "--source-id", help="Filter by source ID"),
    department_id: Optional[str] = typer.Option(
        None, "--department-id", help="Filter by department ID"
    ),
    server_url: Optional[str] = ServerUrlOption,
    token: Optional[str] = TokenOption,
    api_key: Optional[str] = ApiKeyOption,
    json_output: bool = JsonFlag,
) -> None:
    """List KB documents."""
    client = _build_client(server_url, token, api_key)
    params: dict[str, object] = {}
    if source_id:
        params["source_id"] = source_id
    if department_id:
        params["department_id"] = department_id
    data = _safe_get(client, "/api/v1/admin/kb/documents", client.base_url, params=params or None)
    documents = data.get("documents", []) if isinstance(data, dict) else data
    if json_output:
        _emit_json(documents)
        return
    if not documents:
        rprint("[dim]No KB documents found[/dim]")
        return
    table = Table(title="KB Documents")
    table.add_column("ID", style="cyan")
    table.add_column("Filename")
    table.add_column("Source ID")
    table.add_column("Department ID")
    table.add_column("Chunks")
    table.add_column("Created")
    for d in documents:
        table.add_row(
            str(d.get("document_id", "")),
            str(d.get("filename", "")),
            str(d.get("source_id", "")),
            str(d.get("department_id", "")),
            str(d.get("chunks", "")),
            str(d.get("created_at", "")),
        )
    console.print(table)


@kb_app.command("upload")
def kb_upload(
    filename: str = typer.Option(..., "--filename", "-f", help="Document filename"),
    content_file: str = typer.Option(
        ..., "--content-file", "-c", help="Path to file with document content"
    ),
    source_id: str = typer.Option("", "--source-id", help="KB source ID"),
    department_id: Optional[str] = typer.Option(
        None, "--department-id", help="Department ID to bind to"
    ),
    server_url: Optional[str] = ServerUrlOption,
    token: Optional[str] = TokenOption,
    api_key: Optional[str] = ApiKeyOption,
    json_output: bool = JsonFlag,
) -> None:
    """Upload a KB document."""
    path = Path(content_file)
    if not path.exists():
        rprint(f"[red]Error: File not found: {content_file}[/red]")
        raise typer.Exit(1)
    content = path.read_text(encoding="utf-8")
    body: dict[str, object] = {
        "filename": filename,
        "content": content,
        "source_id": source_id,
    }
    if department_id is not None:
        body["department_id"] = department_id
    client = _build_client(server_url, token, api_key)
    result = _safe_post(client, "/api/v1/admin/kb/documents", client.base_url, body)
    if json_output:
        _emit_json(result)
        return
    rprint(f"[green]Document uploaded:[/green] {filename} (id={result.get('id')})")


@kb_app.command("delete")
def kb_delete(
    document_id: str = typer.Argument(..., help="Document ID"),
    server_url: Optional[str] = ServerUrlOption,
    token: Optional[str] = TokenOption,
    api_key: Optional[str] = ApiKeyOption,
    json_output: bool = JsonFlag,
) -> None:
    """Delete a KB document."""
    client = _build_client(server_url, token, api_key)
    result = _safe_delete(client, f"/api/v1/admin/kb/documents/{document_id}", client.base_url)
    if json_output:
        _emit_json(result)
        return
    rprint(f"[green]Document deleted:[/green] {document_id}")


@kb_app.command("sync")
def kb_sync(
    source_id: str = typer.Argument(..., help="KB source ID"),
    server_url: Optional[str] = ServerUrlOption,
    token: Optional[str] = TokenOption,
    api_key: Optional[str] = ApiKeyOption,
    json_output: bool = JsonFlag,
) -> None:
    """Trigger a sync for a KB source."""
    client = _build_client(server_url, token, api_key)
    result = _safe_post(client, f"/api/v1/admin/kb/sources/{source_id}/sync", client.base_url)
    if json_output:
        _emit_json(result)
        return
    rprint(f"[green]Sync triggered for source:[/green] {source_id}")


@kb_app.command("rebuild")
def kb_rebuild(
    source_id: str = typer.Argument(..., help="KB source ID"),
    server_url: Optional[str] = ServerUrlOption,
    token: Optional[str] = TokenOption,
    api_key: Optional[str] = ApiKeyOption,
    json_output: bool = JsonFlag,
) -> None:
    """Rebuild the index for a KB source."""
    client = _build_client(server_url, token, api_key)
    result = _safe_post(client, f"/api/v1/admin/kb/sources/{source_id}/rebuild", client.base_url)
    if json_output:
        _emit_json(result)
        return
    rprint(f"[green]Rebuild triggered for source:[/green] {source_id}")


# ---------------------------------------------------------------------------
# Usage sub-group
# ---------------------------------------------------------------------------

usage_app = typer.Typer(name="usage", help="Usage dashboard", no_args_is_help=True)
admin_app.add_typer(usage_app, name="usage")


def _usage_params(
    department_id: Optional[str],
    user_id: Optional[str],
    start: Optional[str],
    end: Optional[str],
) -> dict[str, object]:
    params: dict[str, object] = {}
    if department_id:
        params["department_id"] = department_id
    if user_id:
        params["user_id"] = user_id
    if start:
        params["start"] = start
    if end:
        params["end"] = end
    return params


@usage_app.command("summary")
def usage_summary(
    department_id: Optional[str] = typer.Option(None, "--department-id", "-d"),
    user_id: Optional[str] = typer.Option(None, "--user-id", "-u"),
    start: Optional[str] = typer.Option(None, "--start", help="ISO 8601 start timestamp"),
    end: Optional[str] = typer.Option(None, "--end", help="ISO 8601 end timestamp"),
    server_url: Optional[str] = ServerUrlOption,
    token: Optional[str] = TokenOption,
    api_key: Optional[str] = ApiKeyOption,
    json_output: bool = JsonFlag,
) -> None:
    """Show aggregated usage summary."""
    client = _build_client(server_url, token, api_key)
    params = _usage_params(department_id, user_id, start, end)
    data = _safe_get(client, "/api/v1/admin/usage/summary", client.base_url, params=params or None)
    if json_output:
        _emit_json(data)
        return
    table = Table(title="Usage Summary")
    table.add_column("Metric", style="cyan")
    table.add_column("Value")
    for key, value in (data or {}).items():
        if isinstance(value, float):
            table.add_row(str(key), f"{value:.4f}")
        elif isinstance(value, list | dict):
            table.add_row(str(key), _json.dumps(value, default=str))
        else:
            table.add_row(str(key), str(value))
    console.print(table)


@usage_app.command("timeseries")
def usage_timeseries(
    department_id: Optional[str] = typer.Option(None, "--department-id", "-d"),
    user_id: Optional[str] = typer.Option(None, "--user-id", "-u"),
    start: Optional[str] = typer.Option(None, "--start"),
    end: Optional[str] = typer.Option(None, "--end"),
    interval: str = typer.Option("day", "--interval", help="day or hour"),
    server_url: Optional[str] = ServerUrlOption,
    token: Optional[str] = TokenOption,
    api_key: Optional[str] = ApiKeyOption,
    json_output: bool = JsonFlag,
) -> None:
    """Show time-bucketed usage series."""
    client = _build_client(server_url, token, api_key)
    params = _usage_params(department_id, user_id, start, end)
    params["interval"] = interval
    data = _safe_get(client, "/api/v1/admin/usage/timeseries", client.base_url, params=params)
    if json_output:
        _emit_json(data)
        return
    if not data:
        rprint("[dim]No usage data[/dim]")
        return
    table = Table(title=f"Usage Timeseries (interval={interval})")
    table.add_column("Bucket", style="cyan")
    table.add_column("Requests")
    table.add_column("Tokens")
    table.add_column("Cost")
    for row in data:
        table.add_row(
            str(row.get("bucket", "")),
            str(row.get("requests", "")),
            str(row.get("tokens", "")),
            str(row.get("cost", "")),
        )
    console.print(table)


@usage_app.command("by-model")
def usage_by_model(
    department_id: Optional[str] = typer.Option(None, "--department-id", "-d"),
    user_id: Optional[str] = typer.Option(None, "--user-id", "-u"),
    start: Optional[str] = typer.Option(None, "--start"),
    end: Optional[str] = typer.Option(None, "--end"),
    server_url: Optional[str] = ServerUrlOption,
    token: Optional[str] = TokenOption,
    api_key: Optional[str] = ApiKeyOption,
    json_output: bool = JsonFlag,
) -> None:
    """Show per-model usage breakdown."""
    client = _build_client(server_url, token, api_key)
    params = _usage_params(department_id, user_id, start, end)
    data = _safe_get(client, "/api/v1/admin/usage/by-model", client.base_url, params=params or None)
    if json_output:
        _emit_json(data)
        return
    if not data:
        rprint("[dim]No usage data[/dim]")
        return
    table = Table(title="Usage by Model")
    table.add_column("Model", style="cyan")
    table.add_column("Requests")
    table.add_column("Tokens")
    table.add_column("Cost")
    for row in data:
        table.add_row(
            str(row.get("model", "")),
            str(row.get("requests", "")),
            str(row.get("tokens", "")),
            str(row.get("cost", "")),
        )
    console.print(table)


@usage_app.command("top-users")
def usage_top_users(
    department_id: Optional[str] = typer.Option(None, "--department-id", "-d"),
    start: Optional[str] = typer.Option(None, "--start"),
    end: Optional[str] = typer.Option(None, "--end"),
    limit: int = typer.Option(10, "--limit", "-n", help="Top N users"),
    server_url: Optional[str] = ServerUrlOption,
    token: Optional[str] = TokenOption,
    api_key: Optional[str] = ApiKeyOption,
    json_output: bool = JsonFlag,
) -> None:
    """Show top-N users by token usage."""
    client = _build_client(server_url, token, api_key)
    params: dict[str, object] = {"limit": limit}
    if department_id:
        params["department_id"] = department_id
    if start:
        params["start"] = start
    if end:
        params["end"] = end
    data = _safe_get(client, "/api/v1/admin/usage/top-users", client.base_url, params=params)
    if json_output:
        _emit_json(data)
        return
    if not data:
        rprint("[dim]No usage data[/dim]")
        return
    table = Table(title=f"Top {limit} Users by Tokens")
    table.add_column("Rank", style="cyan")
    table.add_column("User ID")
    table.add_column("Requests")
    table.add_column("Tokens")
    table.add_column("Cost")
    for i, row in enumerate(data, 1):
        table.add_row(
            str(i),
            str(row.get("user_id", "")),
            str(row.get("requests", "")),
            str(row.get("tokens", "")),
            str(row.get("cost", "")),
        )
    console.print(table)


@usage_app.command("export")
def usage_export(
    department_id: Optional[str] = typer.Option(None, "--department-id", "-d"),
    user_id: Optional[str] = typer.Option(None, "--user-id", "-u"),
    start: Optional[str] = typer.Option(None, "--start"),
    end: Optional[str] = typer.Option(None, "--end"),
    format: str = typer.Option("csv", "--format", help="csv or json"),
    output: Optional[str] = typer.Option(
        None, "--output", "-o", help="Output file (default: stdout)"
    ),
    server_url: Optional[str] = ServerUrlOption,
    token: Optional[str] = TokenOption,
    api_key: Optional[str] = ApiKeyOption,
) -> None:
    """Export raw usage records as CSV or JSON."""
    client = _build_client(server_url, token, api_key)
    params = _usage_params(department_id, user_id, start, end)
    params["format"] = format
    try:
        body = client.get_text("/api/v1/admin/usage/export", params=params)
    except httpx.ConnectError:
        _handle_connect_error(client.base_url)
    except httpx.HTTPStatusError as e:
        _handle_http_error(e, client.base_url)
    if output:
        Path(output).write_text(body, encoding="utf-8")
        rprint(f"[green]Export written to:[/green] {output}")
    else:
        # Print raw — no Rich formatting so the output is scriptable.
        print(body)
