"""Admin dashboard scaffold — infrastructure health monitoring API.

Supports Python (FastAPI + SQLAlchemy) and TypeScript (Express + Prisma).
Provides health checks, DB stats, query management, user admin, and logs.
"""

from __future__ import annotations

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from . import Language

DIR = "stablestack_admin_dashboard"


def get_files(language: Language) -> dict[str, str]:
    """Return {relative_path: content} for the admin-dashboard scaffold."""
    if language == "python":
        return _python_files()
    return _typescript_files()


def get_slash_command() -> str:
    """Return the .claude/commands/admin-dashboard.md content."""
    return _SLASH_COMMAND


# ---------------------------------------------------------------------------
# Python files
# ---------------------------------------------------------------------------

def _python_files() -> dict[str, str]:
    return {
        f"{DIR}/__init__.py": _PY_INIT,
        f"{DIR}/monitor.py": _PY_MONITOR,
        f"{DIR}/models.py": _PY_MODELS,
        f"{DIR}/jobs.py": _PY_JOBS,
        f"{DIR}/api.py": _PY_API,
        f"{DIR}/ui/AdminDashboard.tsx": _REACT_UI,
    }


_PY_INIT = '''\
"""StableStack Admin Dashboard — infrastructure health monitoring API.

Installed by: stablestack add-admin-dashboard
"""

from .api import router
from .jobs import JobRegistry

__all__ = ["router", "JobRegistry"]
'''

_PY_MONITOR = '''\
"""
Infrastructure health monitor with pluggable health checks.

Register checks for your databases, caches, and external services.
The monitor aggregates results for the /health endpoint.

Usage:
    monitor = InfrastructureMonitor()
    monitor.register("database", db_health_check)
    monitor.register("redis", redis_health_check)
    results = await monitor.run_all()

Installed by: stablestack add-admin-dashboard
"""

import asyncio
import logging
import time
from dataclasses import dataclass, field
from typing import Any, Awaitable, Callable, Optional

logger = logging.getLogger(__name__)


@dataclass
class HealthResult:
    """Result of a single health check."""
    name: str
    status: str  # "healthy", "degraded", "unhealthy"
    latency_ms: float = 0.0
    details: Optional[dict[str, Any]] = None
    error: Optional[str] = None


# Type for health check callables — async functions that return a HealthResult
HealthCheckFn = Callable[[], Awaitable[HealthResult]]


class InfrastructureMonitor:
    """Aggregates health checks for registered services.

    Usage:
        monitor = InfrastructureMonitor()
        monitor.register("database", check_db)
        results = await monitor.run_all()
    """

    def __init__(self):
        self._checks: dict[str, HealthCheckFn] = {}

    def register(self, name: str, check_fn: HealthCheckFn) -> None:
        """Register a named health check."""
        self._checks[name] = check_fn

    async def run_all(self, timeout: float = 10.0) -> list[HealthResult]:
        """Run all registered checks concurrently with a timeout."""
        if not self._checks:
            return []

        async def _run_one(name: str, fn: HealthCheckFn) -> HealthResult:
            start = time.monotonic()
            try:
                result = await asyncio.wait_for(fn(), timeout=timeout)
                result.latency_ms = (time.monotonic() - start) * 1000
                return result
            except asyncio.TimeoutError:
                return HealthResult(
                    name=name,
                    status="unhealthy",
                    latency_ms=(time.monotonic() - start) * 1000,
                    error=f"Health check timed out after {timeout}s",
                )
            except Exception as e:
                return HealthResult(
                    name=name,
                    status="unhealthy",
                    latency_ms=(time.monotonic() - start) * 1000,
                    error=str(e),
                )

        tasks = [_run_one(name, fn) for name, fn in sorted(self._checks.items())]
        return list(await asyncio.gather(*tasks))

    def overall_status(self, results: list[HealthResult]) -> str:
        """Derive overall status from individual check results."""
        if not results:
            return "unknown"
        statuses = {r.status for r in results}
        if "unhealthy" in statuses:
            return "unhealthy"
        if "degraded" in statuses:
            return "degraded"
        return "healthy"


# -- Built-in health check factories ----------------------------------------

def make_db_check(engine) -> HealthCheckFn:
    """Create a database health check using a SQLAlchemy async engine."""
    async def check() -> HealthResult:
        from sqlalchemy import text
        try:
            async with engine.connect() as conn:
                await conn.execute(text("SELECT 1"))
            return HealthResult(name="database", status="healthy")
        except Exception as e:
            return HealthResult(name="database", status="unhealthy", error=str(e))
    return check


def make_redis_check(redis_url: str) -> HealthCheckFn:
    """Create a Redis health check."""
    async def check() -> HealthResult:
        try:
            import redis.asyncio as aioredis
            r = aioredis.from_url(redis_url)
            await r.ping()
            await r.aclose()
            return HealthResult(name="redis", status="healthy")
        except Exception as e:
            return HealthResult(name="redis", status="unhealthy", error=str(e))
    return check


def make_http_check(name: str, url: str, timeout: float = 5.0) -> HealthCheckFn:
    """Create an HTTP endpoint health check."""
    async def check() -> HealthResult:
        try:
            import httpx
            async with httpx.AsyncClient() as client:
                resp = await client.get(url, timeout=timeout)
                if resp.status_code < 400:
                    return HealthResult(name=name, status="healthy")
                return HealthResult(
                    name=name, status="degraded",
                    details={"status_code": resp.status_code},
                )
        except Exception as e:
            return HealthResult(name=name, status="unhealthy", error=str(e))
    return check
'''

_PY_MODELS = '''\
"""
Pydantic response models for the admin dashboard API.

Installed by: stablestack add-admin-dashboard
"""

from datetime import datetime
from typing import Any, List, Optional

from pydantic import BaseModel


class HealthCheck(BaseModel):
    name: str
    status: str
    latency_ms: float = 0.0
    details: Optional[dict[str, Any]] = None
    error: Optional[str] = None


class HealthResponse(BaseModel):
    status: str
    version: str
    uptime_seconds: float
    checks: List[HealthCheck]


class ActiveQuery(BaseModel):
    pid: int
    state: str
    query_preview: str
    duration_seconds: float
    username: str
    application_name: Optional[str] = None


class PoolStats(BaseModel):
    pool_size: int
    pool_checked_in: int
    pool_checked_out: int
    pool_overflow: int
    pool_max: int


class DatabaseStats(BaseModel):
    xact_commit: int
    xact_rollback: int
    tup_inserted: int
    tup_updated: int
    tup_deleted: int
    tup_fetched: int
    cache_hit_ratio: float
    database_size_mb: float


class TerminateQueryResponse(BaseModel):
    success: bool
    pid: int
    terminated_query: Optional[str] = None
    error: Optional[str] = None


class BatchTerminateResponse(BaseModel):
    terminated_count: int
    failed_count: int
    terminated: List[dict]


class UserSummary(BaseModel):
    id: int
    email: str
    name: Optional[str] = None
    is_admin: bool
    created_at: datetime
    last_active: Optional[datetime] = None


class UsersResponse(BaseModel):
    users: List[UserSummary]
    total: int
    page: int
    page_size: int


class CronJobInfo(BaseModel):
    name: str
    schedule: str
    description: str = ""
    enabled: bool = True
    last_run: Optional[datetime] = None
    last_status: Optional[str] = None
    last_error: Optional[str] = None


class JobRunSummary(BaseModel):
    id: str
    job_name: str
    started_at: datetime
    completed_at: Optional[datetime] = None
    duration_seconds: Optional[float] = None
    status: str
    error: Optional[str] = None
    triggered_by: str = "scheduler"
'''

_PY_JOBS = '''\
"""
Cron job registry and run tracking.

Register your background jobs so the admin dashboard can display their
schedule, last/next run times, run history, and support manual triggers.

Usage:
    from stablestack_admin_dashboard.jobs import job_registry

    # Register a job at startup
    job_registry.register(
        name="rotate-tokens",
        schedule="0 3 * * *",          # cron expression
        handler=rotate_expired_tokens,  # async callable
        description="Refresh OAuth tokens expiring within 30 days",
    )

    # Record a run (call this from your scheduler/cron wrapper)
    run = job_registry.start_run("rotate-tokens")
    try:
        await rotate_expired_tokens()
        job_registry.complete_run(run)
    except Exception as e:
        job_registry.fail_run(run, error=str(e))

Installed by: stablestack add-admin-dashboard
"""

import logging
import uuid
from collections import deque
from dataclasses import dataclass, field
from datetime import datetime, timezone
from typing import Any, Awaitable, Callable, Deque, Dict, List, Optional

logger = logging.getLogger(__name__)


@dataclass
class JobRun:
    """Record of a single job execution."""
    id: str
    job_name: str
    started_at: datetime
    completed_at: Optional[datetime] = None
    duration_seconds: Optional[float] = None
    status: str = "running"  # running, success, failed
    error: Optional[str] = None
    triggered_by: str = "scheduler"  # scheduler, manual, system
    details: Optional[Dict[str, Any]] = None


@dataclass
class RegisteredJob:
    """A registered background job with its schedule and recent history."""
    name: str
    schedule: str  # cron expression, e.g. "0 3 * * *"
    description: str = ""
    handler: Optional[Callable[[], Awaitable[Any]]] = None
    enabled: bool = True
    last_run: Optional[datetime] = None
    last_status: Optional[str] = None
    last_error: Optional[str] = None
    recent_runs: Deque[JobRun] = field(default_factory=lambda: deque(maxlen=50))


class JobRegistry:
    """Central registry for all background/cron jobs.

    Keeps an in-memory record of registered jobs and their recent runs.
    For persistent storage, connect to your database in the API layer.
    """

    def __init__(self):
        self._jobs: Dict[str, RegisteredJob] = {}

    def register(
        self,
        name: str,
        schedule: str,
        handler: Optional[Callable[[], Awaitable[Any]]] = None,
        description: str = "",
        enabled: bool = True,
    ) -> RegisteredJob:
        """Register a job. Call this at app startup."""
        job = RegisteredJob(
            name=name,
            schedule=schedule,
            description=description,
            handler=handler,
            enabled=enabled,
        )
        self._jobs[name] = job
        logger.info("Registered job: %s (%s)", name, schedule)
        return job

    def get(self, name: str) -> Optional[RegisteredJob]:
        return self._jobs.get(name)

    def list_jobs(self) -> List[RegisteredJob]:
        return list(self._jobs.values())

    def start_run(
        self, job_name: str, triggered_by: str = "scheduler"
    ) -> JobRun:
        """Record the start of a job run. Returns the JobRun to pass to
        complete_run() or fail_run() when finished."""
        run = JobRun(
            id=uuid.uuid4().hex[:12],
            job_name=job_name,
            started_at=datetime.now(timezone.utc),
            triggered_by=triggered_by,
        )
        job = self._jobs.get(job_name)
        if job:
            job.recent_runs.appendleft(run)
            job.last_run = run.started_at
            job.last_status = "running"
        return run

    def complete_run(
        self, run: JobRun, details: Optional[Dict[str, Any]] = None
    ) -> None:
        """Mark a job run as successfully completed."""
        now = datetime.now(timezone.utc)
        run.completed_at = now
        run.duration_seconds = (now - run.started_at).total_seconds()
        run.status = "success"
        run.details = details
        job = self._jobs.get(run.job_name)
        if job:
            job.last_status = "success"
            job.last_error = None

    def fail_run(self, run: JobRun, error: str) -> None:
        """Mark a job run as failed."""
        now = datetime.now(timezone.utc)
        run.completed_at = now
        run.duration_seconds = (now - run.started_at).total_seconds()
        run.status = "failed"
        run.error = error
        job = self._jobs.get(run.job_name)
        if job:
            job.last_status = "failed"
            job.last_error = error

    async def trigger(self, job_name: str) -> JobRun:
        """Manually trigger a registered job."""
        job = self._jobs.get(job_name)
        if not job:
            raise ValueError(f"Unknown job: {job_name}")
        if not job.handler:
            raise ValueError(
                f"Job {job_name!r} has no handler — register with a callable"
            )
        if not job.enabled:
            raise ValueError(f"Job {job_name!r} is disabled")

        run = self.start_run(job_name, triggered_by="manual")
        try:
            result = await job.handler()
            self.complete_run(run, details={"result": str(result)} if result else None)
        except Exception as e:
            self.fail_run(run, error=str(e))
            logger.exception("Manual trigger of %s failed", job_name)
        return run


# Module-level singleton — import and use this in your app
job_registry = JobRegistry()
'''

_PY_API = '''\
"""
FastAPI admin dashboard router — health, DB stats, queries, users, logs.

Mount in your FastAPI app:
    from stablestack_admin_dashboard import router
    app.include_router(router, prefix="/api")

Installed by: stablestack add-admin-dashboard
"""

import time
from datetime import datetime
from pathlib import Path
from typing import Any, List, Optional

from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy import text
from sqlalchemy.ext.asyncio import AsyncSession

from .jobs import job_registry
from .models import (
    ActiveQuery,
    BatchTerminateResponse,
    CronJobInfo,
    DatabaseStats,
    HealthResponse,
    JobRunSummary,
    PoolStats,
    TerminateQueryResponse,
    UsersResponse,
)
from .monitor import InfrastructureMonitor

router = APIRouter(prefix="/admin", tags=["admin"])

# Global monitor — register health checks at app startup
monitor = InfrastructureMonitor()

# Track startup time for uptime calculation
_start_time = time.time()


# -- Dependency (replace with your own) --------------------------------------

def get_db():
    """Yields an async SQLAlchemy session. Replace with your dependency."""
    raise NotImplementedError(
        "Replace get_db() in stablestack_admin_dashboard/api.py with your "
        "project\'s database session dependency."
    )


def require_admin():
    """Auth dependency — verify the caller is an admin. Replace with your auth."""
    raise NotImplementedError(
        "Replace require_admin() in stablestack_admin_dashboard/api.py "
        "with your project\'s admin authentication dependency."
    )


# =============================================================================
# Health
# =============================================================================

@router.get("/health")
async def admin_health(
    db: AsyncSession = Depends(get_db),
    admin=Depends(require_admin),
) -> dict:
    """System health with all registered checks."""
    results = await monitor.run_all()
    checks = [
        {
            "name": r.name,
            "status": r.status,
            "latency_ms": round(r.latency_ms, 2),
            "error": r.error,
        }
        for r in results
    ]
    return {
        "status": monitor.overall_status(results),
        "version": "1.0.0",  # TODO: read from your config
        "uptime_seconds": round(time.time() - _start_time, 1),
        "checks": checks,
    }


# =============================================================================
# Database Stats & Queries
# =============================================================================

@router.get("/db/stats")
async def db_stats(
    db: AsyncSession = Depends(get_db),
    admin=Depends(require_admin),
) -> dict:
    """Database statistics — commits, rollbacks, cache ratio, size."""
    stats_result = await db.execute(text("""
        SELECT
            xact_commit,
            xact_rollback,
            tup_inserted,
            tup_updated,
            tup_deleted,
            tup_fetched,
            COALESCE(blks_hit::float / NULLIF(blks_hit + blks_read, 0), 0) as cache_hit_ratio
        FROM pg_stat_database
        WHERE datname = current_database()
    """))
    row = stats_result.fetchone()

    size_result = await db.execute(
        text("SELECT pg_database_size(current_database()) / 1024 / 1024 as size_mb")
    )
    size_row = size_result.fetchone()

    return {
        "xact_commit": row.xact_commit if row else 0,
        "xact_rollback": row.xact_rollback if row else 0,
        "tup_inserted": row.tup_inserted if row else 0,
        "tup_updated": row.tup_updated if row else 0,
        "tup_deleted": row.tup_deleted if row else 0,
        "tup_fetched": row.tup_fetched if row else 0,
        "cache_hit_ratio": round(row.cache_hit_ratio if row else 0, 4),
        "database_size_mb": round(size_row.size_mb if size_row else 0, 2),
    }


@router.get("/db/queries")
async def list_active_queries(
    db: AsyncSession = Depends(get_db),
    admin=Depends(require_admin),
    limit: int = Query(default=20, le=100),
) -> list[dict]:
    """Active database queries with duration."""
    result = await db.execute(text("""
        SELECT
            pid,
            state,
            LEFT(query, 200) as query_preview,
            EXTRACT(EPOCH FROM (now() - query_start)) as duration_seconds,
            usename as username,
            application_name
        FROM pg_stat_activity
        WHERE state != 'idle'
          AND pid != pg_backend_pid()
        ORDER BY query_start ASC
        LIMIT :limit
    """), {"limit": limit})

    return [
        {
            "pid": row.pid,
            "state": row.state,
            "query_preview": row.query_preview or "",
            "duration_seconds": round(row.duration_seconds or 0, 2),
            "username": row.username or "",
            "application_name": row.application_name,
        }
        for row in result.fetchall()
    ]


@router.post("/db/queries/{pid}/terminate")
async def terminate_query(
    pid: int,
    db: AsyncSession = Depends(get_db),
    admin=Depends(require_admin),
) -> dict:
    """Terminate a specific database query by PID."""
    current_pid = (await db.execute(text("SELECT pg_backend_pid()"))).scalar()
    if pid == current_pid:
        raise HTTPException(400, "Cannot terminate own connection")

    query_row = (await db.execute(
        text("SELECT LEFT(query, 200) as q FROM pg_stat_activity WHERE pid = :pid"),
        {"pid": pid},
    )).fetchone()

    if not query_row:
        raise HTTPException(404, f"No active query with PID {pid}")

    success = (await db.execute(
        text("SELECT pg_terminate_backend(:pid)"), {"pid": pid}
    )).scalar()

    return {
        "success": success,
        "pid": pid,
        "terminated_query": query_row.q if success else None,
        "error": None if success else "Failed to terminate query",
    }


@router.post("/db/queries/terminate-long-running")
async def terminate_long_running(
    min_duration_seconds: int = Query(default=30, ge=5),
    db: AsyncSession = Depends(get_db),
    admin=Depends(require_admin),
) -> dict:
    """Kill queries running longer than the specified duration."""
    current_pid = (await db.execute(text("SELECT pg_backend_pid()"))).scalar()

    result = await db.execute(text("""
        SELECT pid, LEFT(query, 200) as query_preview,
               EXTRACT(EPOCH FROM (now() - query_start)) as duration_seconds,
               usename as username
        FROM pg_stat_activity
        WHERE state != 'idle'
          AND pid != :current_pid
          AND EXTRACT(EPOCH FROM (now() - query_start)) > :min_duration
    """), {"current_pid": current_pid, "min_duration": min_duration_seconds})

    terminated = []
    failed_count = 0

    for row in result.fetchall():
        success = (await db.execute(
            text("SELECT pg_terminate_backend(:pid)"), {"pid": row.pid}
        )).scalar()
        if success:
            terminated.append({
                "pid": row.pid,
                "duration_seconds": round(row.duration_seconds, 2),
                "query_preview": row.query_preview,
                "username": row.username,
            })
        else:
            failed_count += 1

    return {
        "terminated_count": len(terminated),
        "failed_count": failed_count,
        "terminated": terminated,
    }


@router.get("/db/pool")
async def connection_pool(
    db: AsyncSession = Depends(get_db),
    admin=Depends(require_admin),
) -> dict:
    """Connection pool statistics."""
    engine = db.get_bind()
    pool = engine.pool
    return {
        "pool_size": pool.size(),
        "pool_checked_in": pool.checkedin(),
        "pool_checked_out": pool.checkedout(),
        "pool_overflow": pool.overflow(),
        "pool_max": pool.size() + pool.overflow(),
    }


# =============================================================================
# User Management
# =============================================================================

@router.get("/users")
async def list_users(
    db: AsyncSession = Depends(get_db),
    admin=Depends(require_admin),
    page: int = Query(default=1, ge=1),
    page_size: int = Query(default=20, le=100),
    search: Optional[str] = None,
) -> dict:
    """List users with pagination and search.

    TODO: Replace with your User model queries.
    """
    # Example with raw SQL — replace with your ORM query:
    # query = select(User)
    # if search:
    #     query = query.where(User.email.ilike(f"%{search}%"))
    # total = await db.scalar(select(func.count()).select_from(query.subquery()))
    # users = await db.scalars(query.offset((page-1)*page_size).limit(page_size))
    return {
        "users": [],
        "total": 0,
        "page": page,
        "page_size": page_size,
    }


@router.post("/users/{user_id}/toggle-admin")
async def toggle_admin(
    user_id: int,
    db: AsyncSession = Depends(get_db),
    admin=Depends(require_admin),
) -> dict:
    """Toggle admin status for a user.

    TODO: Replace with your User model update.
    """
    return {"success": True, "user_id": user_id, "is_admin": False}


# =============================================================================
# Logs
# =============================================================================

@router.get("/logs")
async def event_logs(
    db: AsyncSession = Depends(get_db),
    admin=Depends(require_admin),
    page: int = Query(default=1, ge=1),
    page_size: int = Query(default=20, le=100),
    log_type: Optional[str] = None,
) -> dict:
    """Event/audit logs with filtering.

    TODO: Replace with your AuditLog model queries.
    """
    return {
        "logs": [],
        "total": 0,
        "page": page,
        "page_size": page_size,
    }


# =============================================================================
# Cron Jobs
# =============================================================================

@router.get("/jobs")
async def list_jobs(
    admin=Depends(require_admin),
) -> list[dict]:
    """List all registered cron jobs with schedule and last-run info."""
    return [
        {
            "name": j.name,
            "schedule": j.schedule,
            "description": j.description,
            "enabled": j.enabled,
            "last_run": j.last_run.isoformat() if j.last_run else None,
            "last_status": j.last_status,
            "last_error": j.last_error,
        }
        for j in job_registry.list_jobs()
    ]


@router.get("/jobs/{job_name}/runs")
async def job_run_history(
    job_name: str,
    admin=Depends(require_admin),
    limit: int = Query(default=20, le=100),
) -> list[dict]:
    """Recent run history for a specific job."""
    job = job_registry.get(job_name)
    if not job:
        raise HTTPException(404, f"Unknown job: {job_name}")

    runs = list(job.recent_runs)[:limit]
    return [
        {
            "id": r.id,
            "job_name": r.job_name,
            "started_at": r.started_at.isoformat(),
            "completed_at": r.completed_at.isoformat() if r.completed_at else None,
            "duration_seconds": round(r.duration_seconds, 2) if r.duration_seconds else None,
            "status": r.status,
            "error": r.error,
            "triggered_by": r.triggered_by,
        }
        for r in runs
    ]


@router.post("/jobs/{job_name}/trigger")
async def trigger_job(
    job_name: str,
    admin=Depends(require_admin),
) -> dict:
    """Manually trigger a registered job."""
    try:
        run = await job_registry.trigger(job_name)
        return {
            "success": run.status == "success",
            "run_id": run.id,
            "status": run.status,
            "duration_seconds": round(run.duration_seconds, 2) if run.duration_seconds else None,
            "error": run.error,
        }
    except ValueError as e:
        raise HTTPException(400, str(e))


# =============================================================================
# Token Manager Integration
# =============================================================================

@router.get("/tokens/status")
async def token_status(
    db: AsyncSession = Depends(get_db),
    admin=Depends(require_admin),
) -> dict:
    """Delegates to token-manager if installed."""
    try:
        from stablestack_token_manager.api import credential_status
        # Call the token manager\'s status function directly
        return credential_status(db=db)
    except ImportError:
        return {
            "available": False,
            "message": "Token manager not installed. Run: stablestack add-token-manager",
        }
'''

# ---------------------------------------------------------------------------
# TypeScript files
# ---------------------------------------------------------------------------

def _typescript_files() -> dict[str, str]:
    return {
        f"{DIR}/index.ts": _TS_INDEX,
        f"{DIR}/monitor.ts": _TS_MONITOR,
        f"{DIR}/types.ts": _TS_TYPES,
        f"{DIR}/jobs.ts": _TS_JOBS,
        f"{DIR}/api.ts": _TS_API,
        f"{DIR}/ui/AdminDashboard.tsx": _REACT_UI,
    }


_TS_INDEX = '''\
/**
 * StableStack Admin Dashboard — infrastructure health monitoring API.
 *
 * Installed by: stablestack add-admin-dashboard
 */

export { adminRouter } from "./api";
export { InfrastructureMonitor } from "./monitor";
export { JobRegistry, jobRegistry } from "./jobs";
export type { HealthResult, HealthCheckFn } from "./monitor";
export type { JobRun, RegisteredJob } from "./jobs";
export type {
  HealthResponse,
  ActiveQuery,
  PoolStats,
  DatabaseStats,
  TerminateQueryResponse,
  BatchTerminateResponse,
  UserSummary,
  UsersResponse,
  CronJobInfo,
  JobRunSummary,
} from "./types";
'''

_TS_MONITOR = '''\
/**
 * Infrastructure health monitor with pluggable checks.
 *
 * Installed by: stablestack add-admin-dashboard
 */

export interface HealthResult {
  name: string;
  status: "healthy" | "degraded" | "unhealthy";
  latency_ms: number;
  details?: Record<string, any>;
  error?: string;
}

export type HealthCheckFn = () => Promise<HealthResult>;

export class InfrastructureMonitor {
  private checks = new Map<string, HealthCheckFn>();

  register(name: string, checkFn: HealthCheckFn): void {
    this.checks.set(name, checkFn);
  }

  async runAll(timeout = 10_000): Promise<HealthResult[]> {
    const entries = [...this.checks.entries()].sort(([a], [b]) =>
      a.localeCompare(b)
    );

    return Promise.all(
      entries.map(async ([name, fn]) => {
        const start = performance.now();
        try {
          const result = await Promise.race([
            fn(),
            new Promise<HealthResult>((_, reject) =>
              setTimeout(() => reject(new Error(`Timed out after ${timeout}ms`)), timeout)
            ),
          ]);
          result.latency_ms = performance.now() - start;
          return result;
        } catch (e: any) {
          return {
            name,
            status: "unhealthy" as const,
            latency_ms: performance.now() - start,
            error: e.message,
          };
        }
      })
    );
  }

  overallStatus(results: HealthResult[]): string {
    if (!results.length) return "unknown";
    const statuses = new Set(results.map((r) => r.status));
    if (statuses.has("unhealthy")) return "unhealthy";
    if (statuses.has("degraded")) return "degraded";
    return "healthy";
  }
}

// -- Built-in check factories -----------------------------------------------

/**
 * Create a database health check (pg pool).
 */
export function makeDbCheck(pool: any): HealthCheckFn {
  return async () => {
    try {
      await pool.query("SELECT 1");
      return { name: "database", status: "healthy", latency_ms: 0 };
    } catch (e: any) {
      return { name: "database", status: "unhealthy", latency_ms: 0, error: e.message };
    }
  };
}

/**
 * Create a Redis health check.
 */
export function makeRedisCheck(redisClient: any): HealthCheckFn {
  return async () => {
    try {
      await redisClient.ping();
      return { name: "redis", status: "healthy", latency_ms: 0 };
    } catch (e: any) {
      return { name: "redis", status: "unhealthy", latency_ms: 0, error: e.message };
    }
  };
}

/**
 * Create an HTTP endpoint health check.
 */
export function makeHttpCheck(name: string, url: string, timeoutMs = 5000): HealthCheckFn {
  return async () => {
    try {
      const resp = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
      if (resp.ok) return { name, status: "healthy", latency_ms: 0 };
      return {
        name,
        status: "degraded",
        latency_ms: 0,
        details: { status_code: resp.status },
      };
    } catch (e: any) {
      return { name, status: "unhealthy", latency_ms: 0, error: e.message };
    }
  };
}
'''

_TS_TYPES = '''\
/**
 * TypeScript interfaces for admin dashboard API responses.
 *
 * Installed by: stablestack add-admin-dashboard
 */

export interface HealthCheck {
  name: string;
  status: "healthy" | "degraded" | "unhealthy";
  latency_ms: number;
  error?: string;
}

export interface HealthResponse {
  status: string;
  version: string;
  uptime_seconds: number;
  checks: HealthCheck[];
}

export interface ActiveQuery {
  pid: number;
  state: string;
  query_preview: string;
  duration_seconds: number;
  username: string;
  application_name?: string;
}

export interface PoolStats {
  pool_size: number;
  pool_checked_in: number;
  pool_checked_out: number;
  pool_overflow: number;
  pool_max: number;
}

export interface DatabaseStats {
  xact_commit: number;
  xact_rollback: number;
  tup_inserted: number;
  tup_updated: number;
  tup_deleted: number;
  tup_fetched: number;
  cache_hit_ratio: number;
  database_size_mb: number;
}

export interface TerminateQueryResponse {
  success: boolean;
  pid: number;
  terminated_query?: string;
  error?: string;
}

export interface BatchTerminateResponse {
  terminated_count: number;
  failed_count: number;
  terminated: Array<{
    pid: number;
    duration_seconds: number;
    query_preview: string;
    username: string;
  }>;
}

export interface UserSummary {
  id: number;
  email: string;
  name?: string;
  is_admin: boolean;
  created_at: string;
  last_active?: string;
}

export interface UsersResponse {
  users: UserSummary[];
  total: number;
  page: number;
  page_size: number;
}

export interface CronJobInfo {
  name: string;
  schedule: string;
  description: string;
  enabled: boolean;
  last_run: string | null;
  last_status: string | null;
  last_error: string | null;
}

export interface JobRunSummary {
  id: string;
  job_name: string;
  started_at: string;
  completed_at: string | null;
  duration_seconds: number | null;
  status: "running" | "success" | "failed";
  error: string | null;
  triggered_by: "scheduler" | "manual" | "system";
}
'''

_TS_JOBS = '''\
/**
 * Cron job registry and run tracking.
 *
 * Register your background jobs so the admin dashboard can display their
 * schedule, last/next run times, run history, and support manual triggers.
 *
 * Usage:
 *   import { jobRegistry } from "./stablestack_admin_dashboard/jobs";
 *
 *   // Register a job at startup
 *   jobRegistry.register({
 *     name: "rotate-tokens",
 *     schedule: "0 3 * * *",
 *     handler: rotateExpiredTokens,
 *     description: "Refresh OAuth tokens expiring within 30 days",
 *   });
 *
 *   // Record a run (call from your scheduler wrapper)
 *   const run = jobRegistry.startRun("rotate-tokens");
 *   try {
 *     await rotateExpiredTokens();
 *     jobRegistry.completeRun(run);
 *   } catch (e: any) {
 *     jobRegistry.failRun(run, e.message);
 *   }
 *
 * Installed by: stablestack add-admin-dashboard
 */

import { randomUUID } from "crypto";

export interface JobRun {
  id: string;
  jobName: string;
  startedAt: Date;
  completedAt: Date | null;
  durationSeconds: number | null;
  status: "running" | "success" | "failed";
  error: string | null;
  triggeredBy: "scheduler" | "manual" | "system";
  details?: Record<string, any>;
}

export interface RegisteredJob {
  name: string;
  schedule: string;
  description: string;
  handler?: () => Promise<any>;
  enabled: boolean;
  lastRun: Date | null;
  lastStatus: string | null;
  lastError: string | null;
  recentRuns: JobRun[];
}

const MAX_RECENT_RUNS = 50;

export class JobRegistry {
  private jobs = new Map<string, RegisteredJob>();

  register(opts: {
    name: string;
    schedule: string;
    handler?: () => Promise<any>;
    description?: string;
    enabled?: boolean;
  }): RegisteredJob {
    const job: RegisteredJob = {
      name: opts.name,
      schedule: opts.schedule,
      description: opts.description ?? "",
      handler: opts.handler,
      enabled: opts.enabled ?? true,
      lastRun: null,
      lastStatus: null,
      lastError: null,
      recentRuns: [],
    };
    this.jobs.set(opts.name, job);
    return job;
  }

  get(name: string): RegisteredJob | undefined {
    return this.jobs.get(name);
  }

  listJobs(): RegisteredJob[] {
    return [...this.jobs.values()];
  }

  startRun(jobName: string, triggeredBy: "scheduler" | "manual" | "system" = "scheduler"): JobRun {
    const run: JobRun = {
      id: randomUUID().replace(/-/g, "").slice(0, 12),
      jobName,
      startedAt: new Date(),
      completedAt: null,
      durationSeconds: null,
      status: "running",
      error: null,
      triggeredBy,
    };
    const job = this.jobs.get(jobName);
    if (job) {
      job.recentRuns.unshift(run);
      if (job.recentRuns.length > MAX_RECENT_RUNS) job.recentRuns.pop();
      job.lastRun = run.startedAt;
      job.lastStatus = "running";
    }
    return run;
  }

  completeRun(run: JobRun, details?: Record<string, any>): void {
    const now = new Date();
    run.completedAt = now;
    run.durationSeconds = (now.getTime() - run.startedAt.getTime()) / 1000;
    run.status = "success";
    run.details = details;
    const job = this.jobs.get(run.jobName);
    if (job) {
      job.lastStatus = "success";
      job.lastError = null;
    }
  }

  failRun(run: JobRun, error: string): void {
    const now = new Date();
    run.completedAt = now;
    run.durationSeconds = (now.getTime() - run.startedAt.getTime()) / 1000;
    run.status = "failed";
    run.error = error;
    const job = this.jobs.get(run.jobName);
    if (job) {
      job.lastStatus = "failed";
      job.lastError = error;
    }
  }

  async trigger(jobName: string): Promise<JobRun> {
    const job = this.jobs.get(jobName);
    if (!job) throw new Error(`Unknown job: ${jobName}`);
    if (!job.handler) throw new Error(`Job ${jobName} has no handler`);
    if (!job.enabled) throw new Error(`Job ${jobName} is disabled`);

    const run = this.startRun(jobName, "manual");
    try {
      const result = await job.handler();
      this.completeRun(run, result ? { result: String(result) } : undefined);
    } catch (e: any) {
      this.failRun(run, e.message);
    }
    return run;
  }
}

// Module-level singleton — import and use this in your app
export const jobRegistry = new JobRegistry();
'''

_TS_API = '''\
/**
 * Express admin dashboard router — health, DB stats, queries, users, logs.
 *
 * Mount in your Express app:
 *   import { adminRouter } from "./stablestack_admin_dashboard";
 *   app.use("/api", adminRouter);
 *
 * Installed by: stablestack add-admin-dashboard
 */

import { Router, Request, Response } from "express";
import { InfrastructureMonitor } from "./monitor";
import { jobRegistry } from "./jobs";

export const adminRouter = Router();

// Global monitor — register health checks at app startup
export const monitor = new InfrastructureMonitor();

const startTime = Date.now();

/**
 * Replace with your database client (e.g., pg Pool or Prisma).
 */
function getDb(): any {
  throw new Error(
    "Replace getDb() in stablestack_admin_dashboard/api.ts with your " +
      "project\'s database client."
  );
}

/**
 * Replace with your admin auth middleware.
 */
function requireAdmin(req: Request, res: Response, next: Function): void {
  // TODO: Verify the caller is an admin
  // Example: if (!req.user?.isAdmin) return res.status(403).json({ error: "Forbidden" });
  next();
}

// =============================================================================
// Health
// =============================================================================

adminRouter.get("/admin/health", requireAdmin, async (req: Request, res: Response) => {
  const results = await monitor.runAll();
  res.json({
    status: monitor.overallStatus(results),
    version: "1.0.0",
    uptime_seconds: Math.round((Date.now() - startTime) / 1000),
    checks: results.map((r) => ({
      name: r.name,
      status: r.status,
      latency_ms: Math.round(r.latency_ms * 100) / 100,
      error: r.error,
    })),
  });
});

// =============================================================================
// Database Stats & Queries
// =============================================================================

adminRouter.get("/admin/db/stats", requireAdmin, async (req: Request, res: Response) => {
  const db = getDb();
  const statsResult = await db.query(`
    SELECT
      xact_commit,
      xact_rollback,
      tup_inserted,
      tup_updated,
      tup_deleted,
      tup_fetched,
      COALESCE(blks_hit::float / NULLIF(blks_hit + blks_read, 0), 0) as cache_hit_ratio
    FROM pg_stat_database
    WHERE datname = current_database()
  `);
  const sizeResult = await db.query(
    "SELECT pg_database_size(current_database()) / 1024 / 1024 as size_mb"
  );

  const row = statsResult.rows[0] || {};
  const sizeRow = sizeResult.rows[0] || {};

  res.json({
    xact_commit: parseInt(row.xact_commit) || 0,
    xact_rollback: parseInt(row.xact_rollback) || 0,
    tup_inserted: parseInt(row.tup_inserted) || 0,
    tup_updated: parseInt(row.tup_updated) || 0,
    tup_deleted: parseInt(row.tup_deleted) || 0,
    tup_fetched: parseInt(row.tup_fetched) || 0,
    cache_hit_ratio: parseFloat(row.cache_hit_ratio) || 0,
    database_size_mb: parseFloat(sizeRow.size_mb) || 0,
  });
});

adminRouter.get("/admin/db/queries", requireAdmin, async (req: Request, res: Response) => {
  const db = getDb();
  const limit = Math.min(parseInt(req.query.limit as string) || 20, 100);

  const result = await db.query(
    `SELECT
      pid,
      state,
      LEFT(query, 200) as query_preview,
      EXTRACT(EPOCH FROM (now() - query_start)) as duration_seconds,
      usename as username,
      application_name
    FROM pg_stat_activity
    WHERE state != \'idle\'
      AND pid != pg_backend_pid()
    ORDER BY query_start ASC
    LIMIT $1`,
    [limit]
  );

  res.json(
    result.rows.map((row: any) => ({
      pid: row.pid,
      state: row.state,
      query_preview: row.query_preview || "",
      duration_seconds: parseFloat(row.duration_seconds) || 0,
      username: row.username || "",
      application_name: row.application_name,
    }))
  );
});

adminRouter.post("/admin/db/queries/:pid/terminate", requireAdmin, async (req: Request, res: Response) => {
  const db = getDb();
  const pid = parseInt(req.params.pid, 10);

  const currentPidResult = await db.query("SELECT pg_backend_pid()");
  if (pid === currentPidResult.rows[0].pg_backend_pid) {
    res.status(400).json({ error: "Cannot terminate own connection" });
    return;
  }

  const queryResult = await db.query(
    "SELECT LEFT(query, 200) as q FROM pg_stat_activity WHERE pid = $1",
    [pid]
  );
  if (!queryResult.rows.length) {
    res.status(404).json({ error: `No active query with PID ${pid}` });
    return;
  }

  const terminateResult = await db.query("SELECT pg_terminate_backend($1)", [pid]);
  const success = terminateResult.rows[0].pg_terminate_backend;

  res.json({
    success,
    pid,
    terminated_query: success ? queryResult.rows[0].q : null,
    error: success ? null : "Failed to terminate query",
  });
});

adminRouter.post("/admin/db/queries/terminate-long-running", requireAdmin, async (req: Request, res: Response) => {
  const db = getDb();
  const minDuration = Math.max(parseInt(req.query.min_duration_seconds as string) || 30, 5);

  const currentPidResult = await db.query("SELECT pg_backend_pid()");
  const currentPid = currentPidResult.rows[0].pg_backend_pid;

  const result = await db.query(
    `SELECT pid, LEFT(query, 200) as query_preview,
            EXTRACT(EPOCH FROM (now() - query_start)) as duration_seconds,
            usename as username
     FROM pg_stat_activity
     WHERE state != \'idle\'
       AND pid != $1
       AND EXTRACT(EPOCH FROM (now() - query_start)) > $2`,
    [currentPid, minDuration]
  );

  const terminated: any[] = [];
  let failedCount = 0;

  for (const row of result.rows) {
    const termResult = await db.query("SELECT pg_terminate_backend($1)", [row.pid]);
    if (termResult.rows[0].pg_terminate_backend) {
      terminated.push({
        pid: row.pid,
        duration_seconds: parseFloat(row.duration_seconds),
        query_preview: row.query_preview,
        username: row.username,
      });
    } else {
      failedCount++;
    }
  }

  res.json({
    terminated_count: terminated.length,
    failed_count: failedCount,
    terminated,
  });
});

adminRouter.get("/admin/db/pool", requireAdmin, async (req: Request, res: Response) => {
  const db = getDb();
  // Pool stats depend on your pool library (pg.Pool, knex, etc.)
  // This is a placeholder — replace with your actual pool stats
  res.json({
    pool_size: db.totalCount ?? 0,
    pool_checked_in: db.idleCount ?? 0,
    pool_checked_out: (db.totalCount ?? 0) - (db.idleCount ?? 0),
    pool_overflow: 0,
    pool_max: db.options?.max ?? 10,
  });
});

// =============================================================================
// User Management
// =============================================================================

adminRouter.get("/admin/users", requireAdmin, async (req: Request, res: Response) => {
  // TODO: Replace with your User model queries
  const page = parseInt(req.query.page as string) || 1;
  const pageSize = Math.min(parseInt(req.query.page_size as string) || 20, 100);

  res.json({
    users: [],
    total: 0,
    page,
    page_size: pageSize,
  });
});

adminRouter.post("/admin/users/:id/toggle-admin", requireAdmin, async (req: Request, res: Response) => {
  // TODO: Replace with your User model update
  const userId = parseInt(req.params.id, 10);
  res.json({ success: true, user_id: userId, is_admin: false });
});

// =============================================================================
// Logs
// =============================================================================

adminRouter.get("/admin/logs", requireAdmin, async (req: Request, res: Response) => {
  // TODO: Replace with your AuditLog model queries
  const page = parseInt(req.query.page as string) || 1;
  const pageSize = Math.min(parseInt(req.query.page_size as string) || 20, 100);

  res.json({
    logs: [],
    total: 0,
    page,
    page_size: pageSize,
  });
});

// =============================================================================
// Cron Jobs
// =============================================================================

adminRouter.get("/admin/jobs", requireAdmin, async (req: Request, res: Response) => {
  res.json(
    jobRegistry.listJobs().map((j) => ({
      name: j.name,
      schedule: j.schedule,
      description: j.description,
      enabled: j.enabled,
      last_run: j.lastRun?.toISOString() ?? null,
      last_status: j.lastStatus,
      last_error: j.lastError,
    }))
  );
});

adminRouter.get("/admin/jobs/:name/runs", requireAdmin, async (req: Request, res: Response) => {
  const job = jobRegistry.get(req.params.name);
  if (!job) {
    res.status(404).json({ error: `Unknown job: ${req.params.name}` });
    return;
  }

  const limit = Math.min(parseInt(req.query.limit as string) || 20, 100);
  const runs = job.recentRuns.slice(0, limit);

  res.json(
    runs.map((r) => ({
      id: r.id,
      job_name: r.jobName,
      started_at: r.startedAt.toISOString(),
      completed_at: r.completedAt?.toISOString() ?? null,
      duration_seconds: r.durationSeconds != null ? Math.round(r.durationSeconds * 100) / 100 : null,
      status: r.status,
      error: r.error,
      triggered_by: r.triggeredBy,
    }))
  );
});

adminRouter.post("/admin/jobs/:name/trigger", requireAdmin, async (req: Request, res: Response) => {
  try {
    const run = await jobRegistry.trigger(req.params.name);
    res.json({
      success: run.status === "success",
      run_id: run.id,
      status: run.status,
      duration_seconds: run.durationSeconds != null ? Math.round(run.durationSeconds * 100) / 100 : null,
      error: run.error,
    });
  } catch (e: any) {
    res.status(400).json({ error: e.message });
  }
});

// =============================================================================
// Token Manager Integration
// =============================================================================

adminRouter.get("/admin/tokens/status", requireAdmin, async (req: Request, res: Response) => {
  try {
    // Dynamically import token manager if installed
    const { CredentialService } = await import("../stablestack_token_manager/models");
    const { getEncryptor } = await import("../stablestack_token_manager/encryption");

    const db = getDb();
    const creds = await db.credential.findMany({ where: { is_active: true } });
    const now = new Date();

    res.json(
      creds.map((c: any) => ({
        ...CredentialService.toSafe(c),
        days_until_expiry: c.token_expires_at
          ? Math.floor((c.token_expires_at.getTime() - now.getTime()) / 86_400_000)
          : null,
      }))
    );
  } catch {
    res.json({
      available: false,
      message: "Token manager not installed. Run: stablestack add-token-manager",
    });
  }
});
'''


# ---------------------------------------------------------------------------
# React UI (shared by both Python and TypeScript backends)
# ---------------------------------------------------------------------------

_REACT_UI = '''\
/**
 * Admin Dashboard — infrastructure health, DB stats, queries, users.
 *
 * Framework-agnostic React component. Works with any API backend that
 * implements the stablestack_admin_dashboard endpoints.
 *
 * Customize the API_BASE constant to match your backend URL.
 *
 * Installed by: stablestack add-admin-dashboard
 */

import React, { useEffect, useState, useCallback } from "react";

const API_BASE = "/api/admin";

// -- Types -------------------------------------------------------------------

interface HealthCheck {
  name: string;
  status: "healthy" | "degraded" | "unhealthy";
  latency_ms: number;
  error?: string;
}

interface HealthResponse {
  status: string;
  version: string;
  uptime_seconds: number;
  checks: HealthCheck[];
}

interface DbStats {
  xact_commit: number;
  xact_rollback: number;
  tup_inserted: number;
  tup_updated: number;
  tup_deleted: number;
  tup_fetched: number;
  cache_hit_ratio: number;
  database_size_mb: number;
}

interface ActiveQuery {
  pid: number;
  state: string;
  query_preview: string;
  duration_seconds: number;
  username: string;
  application_name?: string;
}

interface PoolStats {
  pool_size: number;
  pool_checked_in: number;
  pool_checked_out: number;
  pool_overflow: number;
  pool_max: number;
}

interface CronJob {
  name: string;
  schedule: string;
  description: string;
  enabled: boolean;
  last_run: string | null;
  last_status: string | null;
  last_error: string | null;
}

interface JobRun {
  id: string;
  job_name: string;
  started_at: string;
  completed_at: string | null;
  duration_seconds: number | null;
  status: "running" | "success" | "failed";
  error: string | null;
  triggered_by: string;
}

// -- Helpers -----------------------------------------------------------------

function healthColor(status: string): string {
  if (status === "healthy") return "#22c55e";
  if (status === "degraded") return "#f59e0b";
  return "#ef4444";
}

function healthBg(status: string): string {
  if (status === "healthy") return "#f0fdf4";
  if (status === "degraded") return "#fffbeb";
  return "#fef2f2";
}

function formatUptime(seconds: number): string {
  const d = Math.floor(seconds / 86400);
  const h = Math.floor((seconds % 86400) / 3600);
  const m = Math.floor((seconds % 3600) / 60);
  if (d > 0) return `${d}d ${h}h`;
  if (h > 0) return `${h}h ${m}m`;
  return `${m}m`;
}

async function fetchJson<T>(url: string, init?: RequestInit): Promise<T> {
  const resp = await fetch(url, {
    headers: { "Content-Type": "application/json" },
    ...init,
  });
  if (!resp.ok) {
    const body = await resp.text().catch(() => "");
    throw new Error(`${resp.status}: ${body || resp.statusText}`);
  }
  return resp.json();
}

// -- Component ---------------------------------------------------------------

type Tab = "health" | "database" | "queries" | "jobs";

export default function AdminDashboard() {
  const [tab, setTab] = useState<Tab>("health");
  const [health, setHealth] = useState<HealthResponse | null>(null);
  const [dbStats, setDbStats] = useState<DbStats | null>(null);
  const [pool, setPool] = useState<PoolStats | null>(null);
  const [queries, setQueries] = useState<ActiveQuery[]>([]);
  const [jobs, setJobs] = useState<CronJob[]>([]);
  const [selectedJob, setSelectedJob] = useState<string | null>(null);
  const [jobRuns, setJobRuns] = useState<JobRun[]>([]);
  const [triggering, setTriggering] = useState<string | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);
  const [killing, setKilling] = useState<number | null>(null);

  const loadHealth = useCallback(async () => {
    try {
      const data = await fetchJson<HealthResponse>(`${API_BASE}/health`);
      setHealth(data);
    } catch (e: any) {
      setError(e.message);
    }
  }, []);

  const loadDb = useCallback(async () => {
    try {
      const [stats, poolData, queryData] = await Promise.all([
        fetchJson<DbStats>(`${API_BASE}/db/stats`),
        fetchJson<PoolStats>(`${API_BASE}/db/pool`),
        fetchJson<ActiveQuery[]>(`${API_BASE}/db/queries`),
      ]);
      setDbStats(stats);
      setPool(poolData);
      setQueries(queryData);
    } catch (e: any) {
      setError(e.message);
    }
  }, []);

  const loadJobs = useCallback(async () => {
    try {
      const data = await fetchJson<CronJob[]>(`${API_BASE}/jobs`);
      setJobs(data);
    } catch {
      // Jobs endpoint may not exist yet — ignore
    }
  }, []);

  const loadJobRuns = useCallback(async (jobName: string) => {
    try {
      const data = await fetchJson<JobRun[]>(`${API_BASE}/jobs/${encodeURIComponent(jobName)}/runs`);
      setJobRuns(data);
    } catch (e: any) {
      setError(e.message);
    }
  }, []);

  const loadAll = useCallback(async () => {
    setLoading(true);
    setError(null);
    await Promise.all([loadHealth(), loadDb(), loadJobs()]);
    setLoading(false);
  }, [loadHealth, loadDb, loadJobs]);

  useEffect(() => {
    loadAll();
  }, [loadAll]);

  const killQuery = useCallback(
    async (pid: number) => {
      setKilling(pid);
      try {
        await fetchJson(`${API_BASE}/db/queries/${pid}/terminate`, {
          method: "POST",
        });
        await loadDb();
      } catch (e: any) {
        setError(e.message);
      } finally {
        setKilling(null);
      }
    },
    [loadDb]
  );

  const triggerJob = useCallback(
    async (jobName: string) => {
      setTriggering(jobName);
      try {
        await fetchJson(`${API_BASE}/jobs/${encodeURIComponent(jobName)}/trigger`, {
          method: "POST",
        });
        await loadJobs();
        if (selectedJob === jobName) await loadJobRuns(jobName);
      } catch (e: any) {
        setError(e.message);
      } finally {
        setTriggering(null);
      }
    },
    [loadJobs, loadJobRuns, selectedJob]
  );

  // -- Styles ----------------------------------------------------------------

  const card: React.CSSProperties = {
    border: "1px solid #e5e7eb",
    borderRadius: 8,
    padding: 20,
    marginBottom: 16,
    background: "#fff",
  };
  const badge = (bg: string, fg: string): React.CSSProperties => ({
    display: "inline-block",
    padding: "2px 10px",
    borderRadius: 9999,
    fontSize: 12,
    fontWeight: 600,
    background: bg,
    color: fg,
  });
  const btn = (variant: "primary" | "secondary" | "danger"): React.CSSProperties => ({
    padding: "6px 14px",
    borderRadius: 6,
    border: variant === "secondary" ? "1px solid #d1d5db" : "none",
    background:
      variant === "primary" ? "#2563eb" : variant === "danger" ? "#ef4444" : "#fff",
    color: variant === "secondary" ? "#374151" : "#fff",
    fontSize: 13,
    fontWeight: 500,
    cursor: "pointer",
  });
  const tabStyle = (active: boolean): React.CSSProperties => ({
    padding: "8px 20px",
    borderRadius: "6px 6px 0 0",
    border: "1px solid #e5e7eb",
    borderBottom: active ? "2px solid #2563eb" : "1px solid #e5e7eb",
    background: active ? "#fff" : "#f9fafb",
    color: active ? "#2563eb" : "#6b7280",
    fontWeight: active ? 600 : 400,
    fontSize: 14,
    cursor: "pointer",
  });
  const stat: React.CSSProperties = {
    textAlign: "center" as const,
    padding: 12,
    borderRadius: 8,
    background: "#f9fafb",
    minWidth: 120,
  };

  // -- Render ----------------------------------------------------------------

  return (
    <div style={{ maxWidth: 1060, margin: "0 auto", padding: 24, fontFamily: "system-ui, sans-serif" }}>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 20 }}>
        <h1 style={{ fontSize: 24, fontWeight: 700, margin: 0 }}>Admin Dashboard</h1>
        <button style={btn("secondary")} onClick={loadAll} disabled={loading}>
          {loading ? "Loading..." : "Refresh"}
        </button>
      </div>

      {error && (
        <div style={{ ...card, borderColor: "#fca5a5", background: "#fef2f2", color: "#991b1b" }}>
          {error}
          <button style={{ marginLeft: 12, ...btn("secondary") }} onClick={() => setError(null)}>
            Dismiss
          </button>
        </div>
      )}

      {/* Tabs */}
      <div style={{ display: "flex", gap: 4, marginBottom: 0 }}>
        <button style={tabStyle(tab === "health")} onClick={() => setTab("health")}>Health</button>
        <button style={tabStyle(tab === "database")} onClick={() => setTab("database")}>Database</button>
        <button style={tabStyle(tab === "queries")} onClick={() => setTab("queries")}>Active Queries</button>
        <button style={tabStyle(tab === "jobs")} onClick={() => setTab("jobs")}>Jobs</button>
      </div>

      {/* ── Health Tab ── */}
      {tab === "health" && health && (
        <div style={card}>
          <div style={{ display: "flex", alignItems: "center", gap: 12, marginBottom: 16 }}>
            <span style={badge(healthBg(health.status), healthColor(health.status))}>
              {health.status.toUpperCase()}
            </span>
            <span style={{ fontSize: 13, color: "#6b7280" }}>v{health.version}</span>
            <span style={{ fontSize: 13, color: "#6b7280" }}>Uptime: {formatUptime(health.uptime_seconds)}</span>
          </div>
          <div style={{ display: "flex", gap: 12, flexWrap: "wrap" }}>
            {health.checks.map((c) => (
              <div
                key={c.name}
                style={{
                  ...stat,
                  background: healthBg(c.status),
                  border: `1px solid ${healthColor(c.status)}33`,
                }}
              >
                <div style={{ fontWeight: 600, marginBottom: 4 }}>{c.name}</div>
                <span style={badge(healthBg(c.status), healthColor(c.status))}>{c.status}</span>
                <div style={{ fontSize: 11, color: "#9ca3af", marginTop: 4 }}>
                  {c.latency_ms.toFixed(0)}ms
                </div>
                {c.error && (
                  <div style={{ fontSize: 11, color: "#ef4444", marginTop: 4 }}>{c.error}</div>
                )}
              </div>
            ))}
          </div>
        </div>
      )}

      {/* ── Database Tab ── */}
      {tab === "database" && (
        <>
          {dbStats && (
            <div style={card}>
              <h3 style={{ fontSize: 16, fontWeight: 600, marginTop: 0, marginBottom: 12 }}>Statistics</h3>
              <div style={{ display: "flex", gap: 12, flexWrap: "wrap" }}>
                {([
                  ["Commits", dbStats.xact_commit.toLocaleString()],
                  ["Rollbacks", dbStats.xact_rollback.toLocaleString()],
                  ["Cache Hit", `${(dbStats.cache_hit_ratio * 100).toFixed(1)}%`],
                  ["DB Size", `${dbStats.database_size_mb.toFixed(1)} MB`],
                  ["Inserted", dbStats.tup_inserted.toLocaleString()],
                  ["Updated", dbStats.tup_updated.toLocaleString()],
                  ["Deleted", dbStats.tup_deleted.toLocaleString()],
                  ["Fetched", dbStats.tup_fetched.toLocaleString()],
                ] as const).map(([label, value]) => (
                  <div key={label} style={stat}>
                    <div style={{ fontSize: 11, color: "#6b7280" }}>{label}</div>
                    <div style={{ fontSize: 18, fontWeight: 700 }}>{value}</div>
                  </div>
                ))}
              </div>
            </div>
          )}
          {pool && (
            <div style={card}>
              <h3 style={{ fontSize: 16, fontWeight: 600, marginTop: 0, marginBottom: 12 }}>Connection Pool</h3>
              <div style={{ display: "flex", gap: 12, flexWrap: "wrap" }}>
                {([
                  ["Size", pool.pool_size],
                  ["Checked In", pool.pool_checked_in],
                  ["Checked Out", pool.pool_checked_out],
                  ["Overflow", pool.pool_overflow],
                  ["Max", pool.pool_max],
                ] as const).map(([label, value]) => (
                  <div key={label} style={stat}>
                    <div style={{ fontSize: 11, color: "#6b7280" }}>{label}</div>
                    <div style={{ fontSize: 18, fontWeight: 700 }}>{value}</div>
                  </div>
                ))}
              </div>
            </div>
          )}
        </>
      )}

      {/* ── Queries Tab ── */}
      {tab === "queries" && (
        <div style={card}>
          <div style={{ display: "flex", justifyContent: "space-between", marginBottom: 12 }}>
            <h3 style={{ fontSize: 16, fontWeight: 600, margin: 0 }}>
              Active Queries ({queries.length})
            </h3>
            <button
              style={btn("danger")}
              onClick={async () => {
                await fetchJson(`${API_BASE}/db/queries/terminate-long-running?min_duration_seconds=30`, {
                  method: "POST",
                });
                await loadDb();
              }}
            >
              Kill Long-Running (&gt;30s)
            </button>
          </div>
          {queries.length === 0 ? (
            <div style={{ textAlign: "center", padding: 32, color: "#9ca3af" }}>
              No active queries
            </div>
          ) : (
            <table style={{ width: "100%", borderCollapse: "collapse", fontSize: 13 }}>
              <thead>
                <tr style={{ borderBottom: "2px solid #e5e7eb", textAlign: "left" }}>
                  <th style={{ padding: "6px 10px" }}>PID</th>
                  <th style={{ padding: "6px 10px" }}>State</th>
                  <th style={{ padding: "6px 10px" }}>Duration</th>
                  <th style={{ padding: "6px 10px" }}>User</th>
                  <th style={{ padding: "6px 10px" }}>Query</th>
                  <th style={{ padding: "6px 10px" }}></th>
                </tr>
              </thead>
              <tbody>
                {queries.map((q) => (
                  <tr key={q.pid} style={{ borderBottom: "1px solid #f3f4f6" }}>
                    <td style={{ padding: "8px 10px", fontFamily: "monospace" }}>{q.pid}</td>
                    <td style={{ padding: "8px 10px" }}>
                      <span
                        style={badge(
                          q.state === "active" ? "#dbeafe" : "#f3f4f6",
                          q.state === "active" ? "#1d4ed8" : "#6b7280"
                        )}
                      >
                        {q.state}
                      </span>
                    </td>
                    <td
                      style={{
                        padding: "8px 10px",
                        fontWeight: 600,
                        color: q.duration_seconds > 30 ? "#ef4444" : q.duration_seconds > 5 ? "#f59e0b" : "#22c55e",
                      }}
                    >
                      {q.duration_seconds.toFixed(1)}s
                    </td>
                    <td style={{ padding: "8px 10px" }}>{q.username}</td>
                    <td
                      style={{
                        padding: "8px 10px",
                        fontFamily: "monospace",
                        fontSize: 11,
                        maxWidth: 400,
                        overflow: "hidden",
                        textOverflow: "ellipsis",
                        whiteSpace: "nowrap",
                      }}
                      title={q.query_preview}
                    >
                      {q.query_preview}
                    </td>
                    <td style={{ padding: "8px 10px" }}>
                      <button
                        style={btn("danger")}
                        onClick={() => killQuery(q.pid)}
                        disabled={killing === q.pid}
                      >
                        {killing === q.pid ? "..." : "Kill"}
                      </button>
                    </td>
                  </tr>
                ))}
              </tbody>
            </table>
          )}
        </div>
      )}

      {/* ── Jobs Tab ── */}
      {tab === "jobs" && (
        <div style={card}>
          <h3 style={{ fontSize: 16, fontWeight: 600, marginTop: 0, marginBottom: 12 }}>
            Cron Jobs ({jobs.length})
          </h3>
          {jobs.length === 0 ? (
            <div style={{ textAlign: "center", padding: 32, color: "#9ca3af" }}>
              No jobs registered. Use <code>jobRegistry.register()</code> at app startup.
            </div>
          ) : (
            <>
              <table style={{ width: "100%", borderCollapse: "collapse", fontSize: 13 }}>
                <thead>
                  <tr style={{ borderBottom: "2px solid #e5e7eb", textAlign: "left" }}>
                    <th style={{ padding: "6px 10px" }}>Job</th>
                    <th style={{ padding: "6px 10px" }}>Schedule</th>
                    <th style={{ padding: "6px 10px" }}>Last Run</th>
                    <th style={{ padding: "6px 10px" }}>Status</th>
                    <th style={{ padding: "6px 10px" }}></th>
                  </tr>
                </thead>
                <tbody>
                  {jobs.map((j) => (
                    <tr
                      key={j.name}
                      style={{
                        borderBottom: "1px solid #f3f4f6",
                        background: selectedJob === j.name ? "#f0f9ff" : undefined,
                        cursor: "pointer",
                      }}
                      onClick={() => {
                        setSelectedJob(j.name);
                        loadJobRuns(j.name);
                      }}
                    >
                      <td style={{ padding: "8px 10px" }}>
                        <div style={{ fontWeight: 600 }}>{j.name}</div>
                        {j.description && (
                          <div style={{ fontSize: 11, color: "#6b7280" }}>{j.description}</div>
                        )}
                      </td>
                      <td style={{ padding: "8px 10px", fontFamily: "monospace", fontSize: 12 }}>
                        {j.schedule}
                      </td>
                      <td style={{ padding: "8px 10px", fontSize: 12 }}>
                        {j.last_run ? new Date(j.last_run).toLocaleString() : "Never"}
                      </td>
                      <td style={{ padding: "8px 10px" }}>
                        {j.last_status && (
                          <span
                            style={badge(
                              j.last_status === "success" ? "#f0fdf4" :
                              j.last_status === "running" ? "#dbeafe" : "#fef2f2",
                              j.last_status === "success" ? "#22c55e" :
                              j.last_status === "running" ? "#2563eb" : "#ef4444"
                            )}
                          >
                            {j.last_status}
                          </span>
                        )}
                      </td>
                      <td style={{ padding: "8px 10px" }}>
                        <button
                          style={btn("primary")}
                          onClick={(e) => { e.stopPropagation(); triggerJob(j.name); }}
                          disabled={triggering === j.name || !j.enabled}
                        >
                          {triggering === j.name ? "Running..." : "Trigger"}
                        </button>
                      </td>
                    </tr>
                  ))}
                </tbody>
              </table>

              {/* Run history for selected job */}
              {selectedJob && (
                <div style={{ marginTop: 20, padding: 16, background: "#f9fafb", borderRadius: 8 }}>
                  <h4 style={{ fontSize: 14, fontWeight: 600, marginTop: 0, marginBottom: 8 }}>
                    Run History: {selectedJob}
                  </h4>
                  {jobRuns.length === 0 ? (
                    <div style={{ color: "#9ca3af", fontSize: 13 }}>No runs recorded yet.</div>
                  ) : (
                    <table style={{ width: "100%", borderCollapse: "collapse", fontSize: 12 }}>
                      <thead>
                        <tr style={{ borderBottom: "1px solid #e5e7eb", textAlign: "left" }}>
                          <th style={{ padding: "4px 8px" }}>Started</th>
                          <th style={{ padding: "4px 8px" }}>Duration</th>
                          <th style={{ padding: "4px 8px" }}>Status</th>
                          <th style={{ padding: "4px 8px" }}>Triggered By</th>
                          <th style={{ padding: "4px 8px" }}>Error</th>
                        </tr>
                      </thead>
                      <tbody>
                        {jobRuns.map((r) => (
                          <tr key={r.id} style={{ borderBottom: "1px solid #f3f4f6" }}>
                            <td style={{ padding: "4px 8px" }}>
                              {new Date(r.started_at).toLocaleString()}
                            </td>
                            <td style={{ padding: "4px 8px" }}>
                              {r.duration_seconds != null ? `${r.duration_seconds.toFixed(1)}s` : "—"}
                            </td>
                            <td style={{ padding: "4px 8px" }}>
                              <span
                                style={badge(
                                  r.status === "success" ? "#f0fdf4" :
                                  r.status === "running" ? "#dbeafe" : "#fef2f2",
                                  r.status === "success" ? "#22c55e" :
                                  r.status === "running" ? "#2563eb" : "#ef4444"
                                )}
                              >
                                {r.status}
                              </span>
                            </td>
                            <td style={{ padding: "4px 8px" }}>
                              <span style={badge("#f3f4f6", "#374151")}>{r.triggered_by}</span>
                            </td>
                            <td style={{ padding: "4px 8px", color: "#ef4444", maxWidth: 300, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }} title={r.error ?? ""}>
                              {r.error || "—"}
                            </td>
                          </tr>
                        ))}
                      </tbody>
                    </table>
                  )}
                </div>
              )}
            </>
          )}
        </div>
      )}

      <div style={{ marginTop: 24, fontSize: 12, color: "#9ca3af", textAlign: "center" }}>
        Admin Dashboard — installed by <code>stablestack add-admin-dashboard</code>
      </div>
    </div>
  );
}
'''


# ---------------------------------------------------------------------------
# Slash command
# ---------------------------------------------------------------------------

_SLASH_COMMAND = '''\
---
description: "Wire up admin dashboard with health checks and monitoring"
version: 1.0.0
---

# /admin-dashboard - Set Up Admin Monitoring Dashboard

<objective>
Wire the admin dashboard router into your app, set up auth middleware,
register health checks for detected infrastructure, register cron jobs
for monitoring, and configure entity count queries.
</objective>

<steps>
1. **Verify admin dashboard is installed**
   - Check that `stablestack_admin_dashboard/` directory exists
   - If not, run `stablestack add-admin-dashboard` to install it

2. **Wire the router into your app**
   - Python (FastAPI):
     ```python
     from stablestack_admin_dashboard import router
     app.include_router(router, prefix="/api")
     ```
   - TypeScript (Express):
     ```typescript
     import { adminRouter } from "./stablestack_admin_dashboard";
     app.use("/api", adminRouter);
     ```

3. **Replace dependency stubs**
   - Replace `get_db()` with your project\'s database session dependency
   - Replace `require_admin()` with your admin authentication middleware
   - Look for `TODO` comments and fill in project-specific queries

4. **Register health checks**
   - Detect what infrastructure the project uses (DB, Redis, external APIs)
   - Register checks in app startup:
     Python:
     ```python
     from stablestack_admin_dashboard.monitor import monitor, make_db_check, make_redis_check
     monitor.register("database", make_db_check(engine))
     monitor.register("redis", make_redis_check(redis_url))
     ```
     TypeScript:
     ```typescript
     import { monitor, makeDbCheck, makeRedisCheck } from "./stablestack_admin_dashboard";
     monitor.register("database", makeDbCheck(pool));
     monitor.register("redis", makeRedisCheck(redisClient));
     ```

5. **Register cron/background jobs**
   - Find all scheduled tasks, cron jobs, or periodic workers in the project
   - Register them at startup so the admin UI can track and trigger them:
     Python:
     ```python
     from stablestack_admin_dashboard.jobs import job_registry
     job_registry.register(
         name="rotate-tokens",
         schedule="0 3 * * *",
         handler=rotate_expired_tokens,
         description="Refresh OAuth tokens expiring within 30 days",
     )
     ```
     TypeScript:
     ```typescript
     import { jobRegistry } from "./stablestack_admin_dashboard/jobs";
     jobRegistry.register({
         name: "rotate-tokens",
         schedule: "0 3 * * *",
         handler: rotateExpiredTokens,
         description: "Refresh OAuth tokens expiring within 30 days",
     });
     ```
   - Wrap each job\'s execution with `startRun` / `completeRun` / `failRun`
     so the dashboard tracks run history, duration, and errors

6. **Fill in entity counts and user queries**
   - Update `/admin/users` to query your User model
   - Add entity count queries for your domain models in the health endpoint
   - Update `/admin/logs` if you have an audit log table

7. **Verify**
   - Start the app and test `GET /api/admin/health`
   - Verify auth middleware blocks non-admin users
   - Test database stats and query listing endpoints
   - Test `GET /api/admin/jobs` shows registered jobs
   - Test manual trigger via `POST /api/admin/jobs/:name/trigger`
</steps>

<rules>
- Always protect admin endpoints with authentication middleware
- Never expose raw SQL errors to the client — log them and return generic messages
- Use parameterized queries for all database operations (already done in scaffold)
- The query termination endpoints are powerful — ensure they\'re behind strict auth
- Health check timeouts default to 10s — adjust per your SLA requirements
</rules>
'''
