# retoor <retoor@molodetz.nl>

from __future__ import annotations

from ..spec import Action
from ._shared import body, confirm, path, query


ADMIN_ACTIONS: tuple[Action, ...] = (
    Action(
        name="admin_overview",
        method="GET",
        path="/admin",
        summary="View the admin overview",
        requires_admin=True,
    ),
    Action(
        name="site_analytics",
        method="GET",
        path="/admin/analytics",
        summary="Site-wide aggregate analytics in one call (admin only)",
        description=(
            "Returns JSON: total members, active users in the last 24h/7d/30d, signed_in_now, "
            "new signups (24h/7d/30d), content totals (posts, comments, gists, projects, "
            "news), and top authors. Use this for any 'how many'/'how active'/count question "
            "instead of paging through admin_list_users. signed_in_now counts members holding an "
            "unexpired session (logged in within the session lifetime, default 7-30 days), NOT a "
            "real-time online count - see signed_in_definition in the response and never report it "
            "as 'currently online' or 'logged in right now'."
        ),
        params=(query("top_n", "How many top authors to include (1-50)."),),
        requires_admin=True,
    ),
    Action(
        name="admin_statistics",
        method="GET",
        path="/admin/statistics/data",
        summary="Platform statistics tab data with trends (admin only)",
        description=(
            "Returns JSON for one statistics tab: KPI cards with period-over-period deltas, "
            "time-series points for line charts, breakdown tables, and highlight metrics. "
            "Tabs: overview, visitors, members, content, engagement, social, ai, devii, "
            "services, containers, game, awards, moderation, tools, storage."
        ),
        params=(
            query("tab", "Tab key (default overview)."),
            query("hours", "Lookback window in hours (24, 168, 720, 2160, or 0 for all time)."),
            query("compare", "Include previous-period comparison (1 or 0, default 1)."),
            query("top_n", "Rows in breakdown tables (default 10)."),
        ),
        requires_admin=True,
    ),
    Action(
        name="ai_usage",
        method="GET",
        path="/admin/ai-usage/data",
        summary="AI gateway usage, cost, latency, and reliability metrics (admin only)",
        description=(
            "Returns JSON for a bounded window: request volume and throughput, token usage with "
            "averages and percentiles, latency (upstream, gateway overhead, queue wait, connect), "
            "error rates by category, cost in USD (per model, per caller, input vs output, projected "
            "monthly burn, caching savings), caller behavior, and an hourly breakdown. Use this for "
            "any question about AI spend, token consumption, gateway performance, or errors."
        ),
        params=(
            query("hours", "Lookback window in hours (1-168, default 48)."),
            query("top_n", "How many rows in each top-N breakdown (default 10)."),
        ),
        requires_admin=True,
    ),
    Action(
        name="audit_log",
        method="GET",
        path="/admin/audit-log",
        summary="Query the platform audit trail with filters (admin only)",
        description=(
            "Returns JSON: a paginated, newest-first list of audit events (every state-changing "
            "action on the platform, with actor, origin, target, old/new value, and result), plus "
            "pagination, the active filters, and an 'options' object listing every available value "
            "for each filter (category, event_key, actor_role, origin, result) so you can both "
            "filter and discover valid filter values from a single call. Use this for any audit, "
            "history, 'who did X', 'what changed', or 'show denied/failed actions' question. Filter "
            "by event_key (exact), category, actor_role, actor_uid, origin (web, api, devii, cli, "
            "service, scheduler), or result (success, failure, denied); narrow by date_from/date_to "
            "(ISO date bounds, inclusive) or free-text q over summary, event key, and target."
        ),
        params=(
            query("page", "Page number (1-based)."),
            query("event_key", "Filter by exact event key (e.g. admin.setting.update)."),
            query("category", "Filter by category (auth, content, admin, container, ...)."),
            query("actor_role", "Filter by actor role at action time."),
            query("actor_uid", "Filter by acting user uid."),
            query("origin", "Filter by origin (web, api, devii, cli, service, scheduler)."),
            query("result", "Filter by result (success, failure, denied)."),
            query("q", "Free-text search over summary, event key, and target."),
            query("date_from", "ISO date lower bound (inclusive)."),
            query("date_to", "ISO date upper bound (inclusive)."),
        ),
        requires_admin=True,
    ),
    Action(
        name="audit_event",
        method="GET",
        path="/admin/audit-log/{uid}",
        summary="Get one audit event with its related-object links (admin only)",
        description=(
            "Returns JSON: the full audit event row plus every related-object link (actor, target, "
            "parent, project, instance, setting, ...). Use after audit_log to inspect a single event "
            "in detail."
        ),
        params=(path("uid", "Audit event uid."),),
        requires_admin=True,
    ),
    Action(
        name="bot_monitor",
        method="GET",
        path="/admin/bots/data",
        summary="Live screenshot monitor of every running bot persona (admin only)",
        description=(
            "Returns JSON: one entry per running bot slot with its username, persona, current "
            "action and status text, last page url, frame age in seconds, whether it is active, "
            "and a frame_url to the latest low-quality screenshot. Use this to see what the bot "
            "fleet is doing right now. Frames live only in the worker running the Bots service."
        ),
        requires_admin=True,
    ),
    Action(
        name="admin_list_users",
        method="GET",
        path="/admin/users",
        summary="List users for administration",
        params=(query("page", "Page number."),),
        requires_admin=True,
    ),
    Action(
        name="admin_set_user_role",
        method="POST",
        path="/admin/users/{uid}/role",
        summary="Set a user's role",
        params=(
            path("uid", "User uid."),
            body("role", "New role.", required=True),
        ),
        requires_admin=True,
    ),
    Action(
        name="admin_set_user_password",
        method="POST",
        path="/admin/users/{uid}/password",
        summary="Set a user's password",
        params=(
            path("uid", "User uid."),
            body("password", "New password.", required=True),
        ),
        requires_admin=True,
    ),
    Action(
        name="admin_toggle_user",
        method="POST",
        path="/admin/users/{uid}/toggle",
        summary="Toggle a user's active state",
        params=(path("uid", "User uid."),),
        requires_admin=True,
    ),
    Action(
        name="admin_get_settings",
        method="GET",
        path="/admin/settings",
        summary="View site settings",
        requires_admin=True,
    ),
    Action(
        name="admin_save_settings",
        method="POST",
        path="/admin/settings",
        summary="Save site settings",
        params=(
            body("site_name", "Site name."),
            body("site_description", "Site description."),
            body("site_tagline", "Site tagline."),
            body("max_upload_size_mb", "Maximum upload size in megabytes."),
            body("allowed_file_types", "Allowed file types."),
            body("max_attachments_per_resource", "Maximum attachments per resource."),
            body("rate_limit_per_minute", "Rate limit per minute."),
            body("rate_limit_window_seconds", "Rate limit window in seconds."),
            body("session_max_age_days", "Session maximum age in days."),
            body("session_remember_days", "Remember-me duration in days."),
            body("registration_open", "Whether registration is open."),
            body("maintenance_mode", "Whether maintenance mode is on."),
            body("maintenance_message", "Maintenance message."),
        ),
        requires_admin=True,
    ),
    Action(
        name="admin_list_news",
        method="GET",
        path="/admin/news",
        summary="List news for administration",
        params=(query("page", "Page number."),),
        requires_admin=True,
    ),
    Action(
        name="admin_toggle_news",
        method="POST",
        path="/admin/news/{uid}/toggle",
        summary="Toggle a news article",
        params=(path("uid", "News uid."),),
        requires_admin=True,
    ),
    Action(
        name="admin_publish_news",
        method="POST",
        path="/admin/news/{uid}/publish",
        summary="Publish a news article",
        params=(path("uid", "News uid."),),
        requires_admin=True,
    ),
    Action(
        name="admin_landing_news",
        method="POST",
        path="/admin/news/{uid}/landing",
        summary="Set a news article as landing content",
        params=(path("uid", "News uid."),),
        requires_admin=True,
    ),
    Action(
        name="admin_delete_news",
        method="POST",
        path="/admin/news/{uid}/delete",
        summary="Delete a news article (admin only). Soft delete, confirmation required",
        params=(path("uid", "News uid."), confirm()),
        requires_admin=True,
    ),
    Action(
        name="admin_get_notification_defaults",
        method="GET",
        path="/admin/notifications",
        summary="View site-wide default notification settings for every type/channel",
        requires_admin=True,
    ),
    Action(
        name="admin_set_notification_default",
        method="POST",
        path="/admin/notifications",
        summary="Set the site-wide default for a notification type and channel",
        params=(
            body("notification_type", "Notification type key, e.g. 'follow', 'mention'.", required=True),
            body("channel", "Delivery channel: in_app, push, or telegram.", required=True),
            body(
                "value",
                "true to enable this default, false to disable it.",
                required=True,
                type="boolean",
            ),
        ),
        requires_admin=True,
    ),
    Action(
        name="admin_list_services",
        method="GET",
        path="/admin/services",
        summary="View managed services",
        requires_admin=True,
    ),
    Action(
        name="admin_services_data",
        method="GET",
        path="/admin/services/data",
        summary="Get live status, metrics, and log tail for every background service",
        requires_admin=True,
    ),
    Action(
        name="admin_service_status",
        method="GET",
        path="/admin/services/{name}/data",
        summary="Get live status, metrics, and log tail for one background service",
        params=(path("name", "Service name (e.g. news, bots, openai)."),),
        requires_admin=True,
    ),
    Action(
        name="admin_start_service",
        method="POST",
        path="/admin/services/{name}/start",
        summary="Start a managed service",
        params=(path("name", "Service name."),),
        requires_admin=True,
    ),
    Action(
        name="admin_stop_service",
        method="POST",
        path="/admin/services/{name}/stop",
        summary="Stop a managed service",
        params=(path("name", "Service name."),),
        requires_admin=True,
    ),
    Action(
        name="admin_run_service",
        method="POST",
        path="/admin/services/{name}/run",
        summary="Run a managed service once",
        params=(path("name", "Service name."),),
        requires_admin=True,
    ),
    Action(
        name="admin_clear_service_logs",
        method="POST",
        path="/admin/services/{name}/clear-logs",
        summary="Clear a managed service's logs",
        params=(path("name", "Service name."),),
        requires_admin=True,
    ),
    Action(
        name="admin_config_service",
        method="POST",
        path="/admin/services/{name}/config",
        summary="Update a managed service's configuration",
        params=(path("name", "Service name."),),
        freeform_body=True,
        requires_admin=True,
    ),
    Action(
        name="admin_user_ai_usage",
        method="GET",
        path="/admin/users/{uid}/ai-usage",
        summary="Get a user's AI gateway usage (admin only)",
        description="Returns per-user AI usage data including token counts, cost, and request volume for the given lookback window.",
        params=(
            path("uid", "User uid."),
            query("hours", "Lookback window in hours (default 24)."),
        ),
        requires_admin=True,
    ),
    Action(
        name="admin_reset_user_ai_quota",
        method="POST",
        path="/admin/users/{uid}/reset-ai-quota",
        summary="Reset a user's AI quota (admin only)",
        description="Clears the AI quota ledger for a specific user, allowing them to use AI features again.",
        params=(path("uid", "User uid."), confirm()),
        requires_admin=True,
    ),
    Action(
        name="admin_media_purge",
        method="POST",
        path="/admin/media/{uid}/purge",
        summary="Permanently remove soft-deleted media (admin only)",
        description="Permanently deletes a soft-deleted attachment from the database and filesystem.",
        params=(path("uid", "Attachment uid to purge."), confirm()),
        requires_admin=True,
    ),
    Action(
        name="admin_revoke_award",
        method="POST",
        path="/admin/awards/{uid}/revoke",
        summary="Revoke a published award (admin only)",
        description=(
            "Soft-deletes the award and linked attachments, then recomputes receiver stats. "
            "Confirmation is required in the UI; Devii should confirm before calling."
        ),
        params=(path("uid", "Award uid to revoke."), confirm()),
        requires_admin=True,
    ),
    Action(
        name="admin_reset_guest_ai_quota",
        method="POST",
        path="/admin/ai-quota/reset-guests",
        summary="Reset all guest AI quotas (admin only)",
        description="Clears the AI quota ledger for all guest sessions.",
        params=(confirm(),),
        requires_admin=True,
    ),
    Action(
        name="admin_reset_all_ai_quota",
        method="POST",
        path="/admin/ai-quota/reset-all",
        summary="Reset ALL AI quotas including member quotas (admin only)",
        description="Clears the AI quota ledger for every user and guest. Use with caution.",
        params=(confirm(),),
        requires_admin=True,
    ),
    Action(
        name="backups_overview",
        method="GET",
        path="/admin/backups/data",
        summary="Backup dashboard: storage usage, backups, and schedules (admin only)",
        description=(
            "Returns JSON: per-path storage usage (database, uploads, attachments, project files, "
            "keys, zips, deepsearch, container workspaces, backups), total data-directory size, total "
            "stored-backup size and count, disk usage (total/used/free/percent), every backup archive "
            "(target, status, size, file count, sha256 checksum, created/completed time, download_url), "
            "and every configured backup schedule. Use this for any question about backups, data "
            "footprint, disk space, or what is scheduled."
        ),
        requires_admin=True,
    ),
    Action(
        name="backup_run",
        method="POST",
        path="/admin/backups/run",
        summary="Start a backup of a data target (admin only)",
        description=(
            "Enqueues an async backup job and returns its job uid and status_url. Target is one of: "
            "database (consistent SQLite snapshot plus Devii databases), uploads (attachments and "
            "project files), keys (VAPID keys and config), or full (database, uploads, and keys in one "
            "archive). Poll backup_status with the returned uid until status is done, then read the "
            "download_url. The job runs off the request path and never blocks the server."
        ),
        params=(
            body("target", "One of database, uploads, keys, full.", required=True),
        ),
        requires_admin=True,
    ),
    Action(
        name="backup_status",
        method="GET",
        path="/admin/backups/jobs/{uid}",
        summary="Check the status of a backup job (admin only)",
        description=(
            "Returns JSON for one backup job: status (pending, running, done, failed), target, "
            "backup_uid, download_url (when done), sha256, archive size, file count, and timestamps. "
            "Poll this after backup_run."
        ),
        params=(path("uid", "Backup job uid returned by backup_run."),),
        requires_admin=True,
    ),
    Action(
        name="backup_delete",
        method="POST",
        path="/admin/backups/{uid}/delete",
        summary="Permanently delete a backup archive (admin only)",
        description=(
            "Removes a backup archive file and its record. This is irreversible and reclaims disk "
            "space. The uid is a backup uid (from backups_overview), not a job uid."
        ),
        params=(path("uid", "Backup uid to delete."), confirm()),
        requires_admin=True,
    ),
    Action(
        name="backup_schedule_create",
        method="POST",
        path="/admin/backups/schedules/create",
        summary="Create a recurring backup schedule (admin only)",
        description=(
            "Creates a schedule that fires backups automatically. kind is 'interval' (use every_seconds, "
            "minimum 60) or 'cron' (use a 5-field cron expression in 'minute hour dom month dow'). "
            "keep_last rotates older backups of this schedule, keeping only the newest N (0 keeps all). "
            "target is one of database, uploads, keys, full."
        ),
        params=(
            body("name", "Human-readable schedule name.", required=True),
            body("target", "One of database, uploads, keys, full.", required=True),
            body("kind", "interval or cron.", required=True),
            body("every_seconds", "Seconds between runs when kind=interval (min 60)."),
            body("cron", "Cron expression when kind=cron, e.g. '0 3 * * *'."),
            body("keep_last", "Keep only the newest N backups of this schedule (0 = all)."),
        ),
        requires_admin=True,
    ),
    Action(
        name="backup_schedule_edit",
        method="POST",
        path="/admin/backups/schedules/{uid}/edit",
        summary="Edit a recurring backup schedule (admin only)",
        description=(
            "Replaces every field of an existing schedule (name, target, kind, every_seconds/cron, "
            "keep_last) - this is a full update, not a partial patch."
        ),
        params=(
            path("uid", "Backup schedule uid."),
            body("name", "Human-readable schedule name.", required=True),
            body("target", "One of database, uploads, keys, full.", required=True),
            body("kind", "interval or cron.", required=True),
            body("every_seconds", "Seconds between runs when kind=interval (min 60)."),
            body("cron", "Cron expression when kind=cron, e.g. '0 3 * * *'."),
            body("keep_last", "Keep only the newest N backups of this schedule (0 = all)."),
        ),
        requires_admin=True,
    ),
    Action(
        name="backup_schedule_toggle",
        method="POST",
        path="/admin/backups/schedules/{uid}/toggle",
        summary="Enable or disable a backup schedule (admin only)",
        description="Flips the schedule's enabled state; a disabled schedule never fires.",
        params=(path("uid", "Backup schedule uid."),),
        requires_admin=True,
    ),
    Action(
        name="backup_schedule_run",
        method="POST",
        path="/admin/backups/schedules/{uid}/run",
        summary="Run a backup schedule immediately (admin only)",
        description=(
            "Enqueues an out-of-band backup job using the schedule's target and keep_last, without "
            "waiting for its next scheduled fire. Poll backup_status with the resulting job uid."
        ),
        params=(path("uid", "Backup schedule uid."),),
        requires_admin=True,
    ),
    Action(
        name="backup_schedule_delete",
        method="POST",
        path="/admin/backups/schedules/{uid}/delete",
        summary="Delete a backup schedule (admin only)",
        description=(
            "Removes a backup schedule so it stops firing. Existing backup archives are kept; only the "
            "schedule is deleted."
        ),
        params=(path("uid", "Backup schedule uid."), confirm()),
        requires_admin=True,
    ),
    Action(
        name="admin_list_devii_tasks",
        method="GET",
        path="/admin/devii-tasks",
        summary="List every scheduled Devii task across all owners (admin only)",
        description=(
            "Returns every scheduled Devii task regardless of owner, with its schedule, status, "
            "run/failure counts, quotas, and bounds. state is one of active, inactive, all."
        ),
        params=(query("state", "Filter: active, inactive, or all."),),
        requires_admin=True,
    ),
    Action(
        name="admin_devii_task_disable",
        method="POST",
        path="/admin/devii-tasks/{uid}/disable",
        summary="Disable another owner's scheduled Devii task (admin only)",
        description="Stops the task from firing again; it is not deleted and can be re-enabled by its owner.",
        params=(path("uid", "Devii task uid."),),
        requires_admin=True,
    ),
    Action(
        name="admin_devii_task_delete",
        method="POST",
        path="/admin/devii-tasks/{uid}/delete",
        summary="Delete another owner's scheduled Devii task (admin only). Soft delete, confirmation required",
        params=(path("uid", "Devii task uid."), confirm()),
        requires_admin=True,
    ),
    Action(
        name="restore_media",
        method="POST",
        path="/media/{uid}/restore",
        summary="Restore a soft-deleted media attachment (admin only)",
        description="Restores a previously soft-deleted attachment, making it visible again.",
        params=(path("uid", "Attachment uid to restore."),),
        requires_admin=True,
    ),
)
