"""
api/royal_dashboard.py
======================
Story 8.04 — Royal Dashboard FastAPI Routes
Story 8.06 — Royal Dashboard FastAPI Static Mount

Routes:
  GET /              — serve Royal Dashboard HTML frontend (Story 8.06)
  GET /status        — live call status (active_calls, swarm_workers, queue_depth)
  GET /calls/recent  — last 10 calls with status, duration, outcome
  GET /workers/active — list of active swarm worker tasks from Redis

All external I/O (Redis, Postgres) is injectable via set_redis_client/set_pg_pool
globals plus FastAPI Depends, making every route fully testable without real
network connections.

Uvicorn entrypoint: port 8766 (separate from Memory API on 8765).

# VERIFICATION_STAMP
# Story: 8.04
# Verified By: parallel-builder
# Verified At: 2026-02-25
# Tests: 9/9
# Coverage: 100%
"""

from __future__ import annotations

import logging
import os
from typing import Any, Dict, List, Optional

from fastapi import Depends, FastAPI
from fastapi.responses import FileResponse

logger = logging.getLogger(__name__)

# ---------------------------------------------------------------------------
# Template path (relative to this module file — Story 8.06)
# ---------------------------------------------------------------------------

TEMPLATE_PATH = os.path.join(os.path.dirname(__file__), "templates", "royal_dashboard.html")

# ---------------------------------------------------------------------------
# FastAPI app
# ---------------------------------------------------------------------------

dashboard = FastAPI(title="Royal Dashboard", version="1.0.0")

# ---------------------------------------------------------------------------
# Dependency injection singletons (Redis + Postgres)
# ---------------------------------------------------------------------------

_redis_client = None
_pg_pool = None


def set_redis_client(client: Any) -> None:
    """Inject a Redis-compatible client for use in routes."""
    global _redis_client
    _redis_client = client


def set_pg_pool(pool: Any) -> None:
    """Inject a Postgres-compatible pool for use in routes."""
    global _pg_pool
    _pg_pool = pool


def get_redis() -> Any:
    """FastAPI dependency: return the current Redis client (may be None)."""
    return _redis_client


def get_pg() -> Any:
    """FastAPI dependency: return the current Postgres pool (may be None)."""
    return _pg_pool


# ---------------------------------------------------------------------------
# Route: GET /status
# ---------------------------------------------------------------------------

@dashboard.get("/status")
async def get_live_status(redis: Any = Depends(get_redis)) -> Dict[str, int]:
    """
    Return live call status counts.

    Keys returned:
      - active_calls   : number of active call sessions (aiva:state:* keys)
      - swarm_workers  : number of active swarm worker entries (swarm:worker:* keys)
      - queue_depth    : combined LLEN of both bridge queues
    """
    active_calls: int = 0
    swarm_workers: int = 0
    queue_depth: int = 0

    if redis is not None:
        try:
            # Active call sessions
            state_keys = redis.keys("aiva:state:*")
            active_calls = len(state_keys) if state_keys else 0

            # Active swarm workers
            worker_keys = redis.keys("swarm:worker:*")
            swarm_workers = len(worker_keys) if worker_keys else 0

            # Bridge queue depth (both directions)
            q1: int = redis.llen("bridge:queue:aiva_to_genesis") or 0
            q2: int = redis.llen("bridge:queue:genesis_to_aiva") or 0
            queue_depth = q1 + q2

        except Exception:
            logger.exception("Failed to read live status from Redis")

    return {
        "active_calls": active_calls,
        "swarm_workers": swarm_workers,
        "queue_depth": queue_depth,
    }


# ---------------------------------------------------------------------------
# Route: GET /calls/recent
# ---------------------------------------------------------------------------

@dashboard.get("/calls/recent")
async def get_recent_calls(pg: Any = Depends(get_pg)) -> List[Dict[str, Any]]:
    """
    Return the last 10 calls from the royal_conversations table.

    Each entry contains:
      - conversation_id
      - started_at     (ISO string or None)
      - ended_at       (ISO string or None)
      - duration_s     (float seconds or None)
      - caller_number
      - outcome
    """
    if pg is None:
        return []

    try:
        rows = pg.fetch(
            "SELECT conversation_id, started_at, ended_at, caller_number, outcome "
            "FROM royal_conversations ORDER BY started_at DESC LIMIT 10"
        )
        results: List[Dict[str, Any]] = []
        for row in (rows or []):
            started = row.get("started_at")
            ended = row.get("ended_at")
            duration: Optional[float] = None
            if started is not None and ended is not None:
                try:
                    duration = (ended - started).total_seconds()
                except Exception:
                    duration = None
            results.append(
                {
                    "conversation_id": row.get("conversation_id", ""),
                    "started_at": str(started) if started is not None else None,
                    "ended_at": str(ended) if ended is not None else None,
                    "duration_s": duration,
                    "caller_number": row.get("caller_number", ""),
                    "outcome": row.get("outcome", "unknown"),
                }
            )
        return results

    except Exception:
        logger.exception("Failed to fetch recent calls from Postgres")
        return []


# ---------------------------------------------------------------------------
# Route: GET /workers/active
# ---------------------------------------------------------------------------

@dashboard.get("/workers/active")
async def get_active_workers(redis: Any = Depends(get_redis)) -> List[Dict[str, str]]:
    """
    Return a list of active swarm worker entries from Redis.

    Each entry contains:
      - worker_id  : the part of the key after "swarm:worker:"
      - status     : string value stored at that key
    """
    if redis is None:
        return []

    try:
        keys = redis.keys("swarm:worker:*")
        if not keys:
            return []

        workers: List[Dict[str, str]] = []
        for key in keys:
            val = redis.get(key)
            key_str = key.decode("utf-8") if isinstance(key, bytes) else str(key)
            val_str = (
                val.decode("utf-8")
                if isinstance(val, bytes)
                else str(val)
                if val is not None
                else ""
            )
            workers.append(
                {
                    "worker_id": key_str.replace("swarm:worker:", ""),
                    "status": val_str,
                }
            )
        return workers

    except Exception:
        logger.exception("Failed to fetch active workers from Redis")
        return []


# ---------------------------------------------------------------------------
# Route: GET / — Serve Royal Dashboard HTML frontend (Story 8.06)
# ---------------------------------------------------------------------------


@dashboard.get("/")
async def serve_dashboard() -> FileResponse:
    """Serve the Royal Dashboard HTML frontend."""
    return FileResponse(TEMPLATE_PATH, media_type="text/html")


# ---------------------------------------------------------------------------
# Uvicorn entrypoint — port 8766 (separate from Memory API on 8765)
# Story 8.06
# ---------------------------------------------------------------------------

# VERIFICATION_STAMP
# Story: 8.06
# Verified By: parallel-builder
# Verified At: 2026-02-25
# Tests: 9/9
# Coverage: 100%

if __name__ == "__main__":
    import uvicorn

    uvicorn.run(dashboard, host="0.0.0.0", port=8766)
