"""`agnes admin activity` — terminal access to Activity Center.

Three subcommands that mirror the three /api/admin/activity/* JSON endpoints:

    agnes admin activity              # audit_log timeline (last 24h, table output)
    agnes admin activity health       # health pulse (scheduler / sync / users / memory)
    agnes admin activity sync         # cross-table sync history feed

All require admin auth (the server returns 403 otherwise).
"""

from __future__ import annotations

import json as json_lib
import sys
from typing import Optional

import typer

from cli.client import api_get

activity_app = typer.Typer(help="Activity Center — audit_log timeline, health pulse, sync history")


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def _parse_since(value: str) -> int:
    """Parse a human shorthand into minutes.

    Accepts:
        30m   → 30
        1h    → 60
        24h   → 1440
        7d    → 10080
        raw integer string → pass-through

    Raises typer.BadParameter on unrecognised format.
    """
    s = value.strip().lower()
    try:
        return int(s)  # bare integer → minutes
    except ValueError:
        pass
    if s.endswith("m"):
        try:
            return int(s[:-1])
        except ValueError:
            pass
    if s.endswith("h"):
        try:
            return int(s[:-1]) * 60
        except ValueError:
            pass
    if s.endswith("d"):
        try:
            return int(s[:-1]) * 60 * 24
        except ValueError:
            pass
    raise typer.BadParameter(
        f"Cannot parse duration {value!r}. Use formats like 30m, 1h, 24h, 7d, or a bare integer (minutes)."
    )


def _handle_error(resp, context: str) -> None:
    """Print a clean error and exit non-zero for non-2xx responses."""
    if resp.status_code in (401, 403):
        typer.echo(
            "[err] authentication required — run `agnes auth login` or import a PAT",
            err=True,
        )
        raise typer.Exit(1)
    if resp.status_code >= 500:
        try:
            body = resp.json().get("detail", resp.text)
        except Exception:
            body = resp.text
        typer.echo(f"[err] server error on {context}: {body}", err=True)
        raise typer.Exit(1)
    if resp.status_code >= 400:
        try:
            body = resp.json().get("detail", resp.text)
        except Exception:
            body = resp.text
        typer.echo(f"[err] {context}: {body}", err=True)
        raise typer.Exit(1)


# ---------------------------------------------------------------------------
# timeline (default callback — invoked as `agnes admin activity`)
# ---------------------------------------------------------------------------


@activity_app.callback(invoke_without_command=True)
def timeline(
    ctx: typer.Context,
    since: str = typer.Option("24h", "--since", help="How far back to look (e.g. 1h, 7d, 30m, or raw minutes)"),
    limit: int = typer.Option(50, "--limit", help="Maximum rows to return (1–200)"),
    action: Optional[str] = typer.Option(None, "--action", help="Filter by action prefix (e.g. sync.)"),
    user: Optional[str] = typer.Option(None, "--user", help="Filter by user_id (full or prefix)"),
    resource: Optional[str] = typer.Option(None, "--resource", help="Filter by resource (e.g. table:orders)"),
    result: Optional[str] = typer.Option(None, "--result", help="Filter by result prefix (e.g. error)"),
    search: Optional[str] = typer.Option(None, "--search", help="Full-text search on params JSON"),
    as_json: bool = typer.Option(False, "--json", help="Emit raw JSON to stdout"),
):
    """Tail the audit_log timeline (default last 24h, up to 50 rows).

    Equivalent to GET /api/admin/activity with filters.
    """
    if ctx.invoked_subcommand is not None:
        return

    try:
        since_minutes = _parse_since(since)
    except typer.BadParameter as e:
        typer.echo(str(e), err=True)
        raise typer.Exit(2)

    params: dict = {"since_minutes": since_minutes, "limit": limit}
    if action:
        params["action_prefix"] = action
    if user:
        params["user_id"] = user
    if resource:
        params["resource"] = resource
    if result:
        params["result_pattern"] = result
    if search:
        params["q"] = search

    try:
        resp = api_get("/api/admin/activity", params=params)
    except Exception as e:
        typer.echo(f"[err] cannot reach server: {e}", err=True)
        raise typer.Exit(1)

    _handle_error(resp, "activity timeline")

    data = resp.json()
    if as_json:
        typer.echo(json_lib.dumps(data, indent=2, default=str))
        return

    rows = data.get("rows", [])
    if not rows:
        typer.echo("No activity found for the given filters.")
        return

    # Table output
    col_time = 20
    col_action = 28
    col_user = 22
    col_result = 10
    header = f"  {'TIME':<{col_time}}  {'ACTION':<{col_action}}  {'USER':<{col_user}}  {'RESULT':<{col_result}}  RESOURCE"
    typer.echo(header)
    typer.echo("  " + "-" * (len(header) - 2))
    for row in rows:
        ts = str(row.get("timestamp") or row.get("ts") or "")[:col_time]
        act = str(row.get("action") or "")[:col_action]
        uid = str(row.get("user_id") or row.get("user_email") or "")[:col_user]
        res = str(row.get("result") or "")[:col_result]
        resource_val = str(row.get("resource") or "")
        typer.echo(
            f"  {ts:<{col_time}}  {act:<{col_action}}  {uid:<{col_user}}  {res:<{col_result}}  {resource_val}"
        )

    next_cur = data.get("next_cursor")
    if next_cur:
        typer.echo(f"\n  (more rows available — pass --limit higher or use --json to page with cursor)")


# ---------------------------------------------------------------------------
# health subcommand
# ---------------------------------------------------------------------------


@activity_app.command("health")
def health(
    as_json: bool = typer.Option(False, "--json", help="Emit raw JSON to stdout"),
):
    """Show the Activity Center health pulse (5 metrics + status sentence).

    Equivalent to GET /api/admin/activity/health.
    """
    try:
        resp = api_get("/api/admin/activity/health")
    except Exception as e:
        typer.echo(f"[err] cannot reach server: {e}", err=True)
        raise typer.Exit(1)

    _handle_error(resp, "activity health")

    data = resp.json()
    if as_json:
        typer.echo(json_lib.dumps(data, indent=2, default=str))
        return

    status = data.get("status", "unknown")
    sentence = data.get("sentence", "")
    fields = data.get("fields", [])

    # Status line with colour hint
    status_marker = {"green": "[ok]", "yellow": "[!] ", "red": "[!!]"}.get(status, "[?] ")
    typer.echo(f"\n  STATUS  {status_marker} {status.upper()}")
    typer.echo(f"  {sentence}\n")

    col_key = 22
    col_val = 20
    typer.echo(f"  {'METRIC':<{col_key}}  {'VALUE':<{col_val}}  STATUS")
    typer.echo("  " + "-" * (col_key + col_val + 14))
    for f in fields:
        key = str(f.get("key") or "")[:col_key]
        val = str(f.get("value") or "")[:col_val]
        color = f.get("color", "")
        marker = {"green": "ok", "yellow": "warn", "red": "ALERT"}.get(color, color)
        typer.echo(f"  {key:<{col_key}}  {val:<{col_val}}  {marker}")


# ---------------------------------------------------------------------------
# sync subcommand
# ---------------------------------------------------------------------------


@activity_app.command("sync")
def sync(
    since: str = typer.Option("24h", "--since", help="How far back to look (e.g. 1h, 7d, 30m, or raw minutes)"),
    limit: int = typer.Option(100, "--limit", help="Maximum rows to return (1–500)"),
    as_json: bool = typer.Option(False, "--json", help="Emit raw JSON to stdout"),
):
    """Show cross-table sync history feed.

    Equivalent to GET /api/admin/activity/sync.
    """
    try:
        since_minutes = _parse_since(since)
    except typer.BadParameter as e:
        typer.echo(str(e), err=True)
        raise typer.Exit(2)

    params: dict = {"since_minutes": since_minutes, "limit": limit}

    try:
        resp = api_get("/api/admin/activity/sync", params=params)
    except Exception as e:
        typer.echo(f"[err] cannot reach server: {e}", err=True)
        raise typer.Exit(1)

    _handle_error(resp, "activity sync")

    data = resp.json()
    if as_json:
        typer.echo(json_lib.dumps(data, indent=2, default=str))
        return

    rows = data.get("rows", [])
    if not rows:
        typer.echo("No sync history found for the given window.")
        return

    col_table = 28
    col_time = 20
    col_rows = 10
    col_dur = 10
    col_status = 8
    header = f"  {'TABLE':<{col_table}}  {'SYNCED AT':<{col_time}}  {'ROWS':>{col_rows}}  {'DURATION':>{col_dur}}  STATUS"
    typer.echo(header)
    typer.echo("  " + "-" * (len(header) - 2))
    for row in rows:
        table_id = str(row.get("table_id") or row.get("name") or "")[:col_table]
        synced_at = str(row.get("synced_at") or row.get("last_synced_at") or "")[:col_time]
        row_count = str(row.get("rows") or row.get("row_count") or "")
        dur_ms = row.get("duration_ms")
        dur_str = f"{dur_ms}ms" if dur_ms is not None else ""
        status_val = str(row.get("status") or "")
        typer.echo(
            f"  {table_id:<{col_table}}  {synced_at:<{col_time}}  {row_count:>{col_rows}}  {dur_str:>{col_dur}}  {status_val}"
        )
