"""cortex/api/routes/admin/admin_stats.py — Statistics and metrics endpoints."""

from __future__ import annotations

import logging
from typing import Any, Dict, List, Optional

from fastapi import APIRouter, Depends, HTTPException, Request, Response
from fastapi.responses import JSONResponse

from cortex.api.deps import PREFIX, _resolve_owner_id, db_manager_holder, get_plugin, get_user_storage
from cortex.api.deprecation import add_deprecation_headers
from cortex.api.models import (
    AdminGlobalStatsResponse,
    AdminStatsResponse,
    StatsResponse,
)

logger = logging.getLogger(__name__)


def _resolve_query_owner(effective_owner: str, plugin: Any) -> str:
    """Return the owner_id that should be used for DB queries.

    In self-host mode (no DBManager), ``get_user_storage`` always returns the
    shared ``plugin.storage`` regardless of the requested owner.  All records in
    that DB are stored under ``plugin.config.owner_id`` (e.g. "default" or "eva"),
    so using an arbitrary caller-supplied owner would return zero rows.

    In hosted/DBManager mode each owner gets their own DB, so the requested
    owner_id is the correct query key.

    Fixes #1471.
    """
    if db_manager_holder.get("manager") is None:
        # Self-host: storage is shared — use the configured owner
        return getattr(plugin.config, "owner_id", effective_owner) or effective_owner
    return effective_owner


def _register(router: APIRouter, verify_api_key):
    """Register stats and metrics routes on the given router."""

    @router.get(
        f"{PREFIX}/stats",
        response_model=StatsResponse,
        summary="Memory statistics — DEPRECATED, use /stats/dashboard",
        tags=["system (deprecated)"],
        operation_id="get_stats",
        deprecated=True,
    )
    async def get_stats(
        request: Request,
        response: Response,
        owner_id: Optional[str] = None,
        _: None = Depends(verify_api_key),
        plugin: Any = Depends(get_plugin),
    ) -> StatsResponse:
        """Return aggregate memory statistics from storage.

        .. deprecated::
            Use ``GET /api/v1/stats/dashboard`` for richer statistics.
            Sunset: 2026-06-01  (issue #297)
        """
        add_deprecation_headers(response, f"{PREFIX}/stats/dashboard")
        errors: List[str] = []
        total_claims = 0
        total_sessions = 0
        total_cornerstones = 0
        storage_ok = False

        effective_owner = _resolve_owner_id(request, owner_id)
        query_owner = _resolve_query_owner(effective_owner, plugin)

        try:
            storage = await get_user_storage(effective_owner)
        except Exception:
            storage = getattr(plugin, "storage", None)

        if storage is None:
            errors.append("Storage not available")
            return StatsResponse(storage_ok=False, errors=errors)

        try:
            if hasattr(storage, "count_claims"):
                total_claims = await storage.count_claims(owner_id=query_owner)
            if hasattr(storage, "count_sessions"):
                total_sessions = await storage.count_sessions(owner_id=query_owner)
            if hasattr(storage, "get_cornerstones"):
                cs = await storage.get_cornerstones(owner_id=query_owner)
                total_cornerstones = len(cs)
            storage_ok = True
        except Exception as exc:
            err = f"Stats fetch failed: {exc}"
            logger.error(err, exc_info=True)
            errors.append(err)

        return StatsResponse(
            total_claims=total_claims,
            total_sessions=total_sessions,
            total_cornerstones=total_cornerstones,
            storage_ok=storage_ok,
            errors=errors,
        )

    @router.get(
        f"{PREFIX}/admin/stats",
        response_model=AdminStatsResponse,
        summary="Expanded system statistics",
        tags=["admin"],
        operation_id="admin_stats",
    )
    async def admin_stats(
        request: Request,
        owner_id: Optional[str] = None,
        _: None = Depends(verify_api_key),
        plugin: Any = Depends(get_plugin),
    ) -> AdminStatsResponse:
        """Return comprehensive system statistics."""
        import os

        errors: List[str] = []
        effective_owner = _resolve_owner_id(request, owner_id)
        query_owner = _resolve_query_owner(effective_owner, plugin)

        try:
            storage = await get_user_storage(effective_owner)
        except Exception:
            storage = getattr(plugin, "storage", None)

        claims_total = 0
        claims_by_type: Dict[str, int] = {}
        claims_by_confidence: Dict[str, int] = {}

        if storage is not None:
            try:
                if hasattr(storage, "count_claims"):
                    claims_total = await storage.count_claims(owner_id=query_owner)
                if hasattr(storage, "_conn") and storage._conn is not None:
                    async with storage._conn.execute(
                        "SELECT memory_type, COUNT(*) FROM semantic_claim WHERE owner_id = ? GROUP BY memory_type",
                        (query_owner,),
                    ) as cur:
                        async for row in cur:
                            claims_by_type[row[0]] = row[1]
                    async with storage._conn.execute(
                        "SELECT confidence, COUNT(*) FROM semantic_claim WHERE owner_id = ? GROUP BY confidence",
                        (query_owner,),
                    ) as cur:
                        async for row in cur:
                            claims_by_confidence[str(row[0])] = row[1]
            except Exception as exc:
                errors.append(f"Claims stats failed: {exc}")

        cs_count = 0
        cs_drift_scores: List[float] = []
        try:
            if storage is not None and hasattr(storage, "get_cornerstones"):
                cs_list = await storage.get_cornerstones(owner_id=query_owner)
            else:
                cs_list = await plugin.get_cornerstones()
            cs_count = len(cs_list)
            cs_drift_scores = [
                getattr(cs, "drift_score", 0.0) or 0.0
                for cs in cs_list
            ]
        except Exception as exc:
            errors.append(f"Cornerstone stats failed: {exc}")

        entities_total = 0
        if storage is not None:
            try:
                if hasattr(storage, "list_entities"):
                    entities_raw = await storage.list_entities(
                        owner_id=query_owner,
                        limit=1,
                        offset=0,
                    )
                    if isinstance(entities_raw, tuple):
                        entities_total = entities_raw[1]
                    elif hasattr(storage, "_conn") and storage._conn is not None:
                        async with storage._conn.execute(
                            "SELECT COUNT(*) FROM entity WHERE owner_id = ?",
                            (query_owner,),
                        ) as cur:
                            row = await cur.fetchone()
                            entities_total = row[0] if row else 0
                elif hasattr(storage, "_conn") and storage._conn is not None:
                    async with storage._conn.execute(
                        "SELECT COUNT(*) FROM entity WHERE owner_id = ?",
                        (query_owner,),
                    ) as cur:
                        row = await cur.fetchone()
                        entities_total = row[0] if row else 0
            except Exception as exc:
                errors.append(f"Entity stats failed: {exc}")

        sessions_total = 0
        sessions_active = 0
        if storage is not None:
            try:
                if hasattr(storage, "count_sessions"):
                    sessions_total = await storage.count_sessions(owner_id=query_owner)
                if hasattr(storage, "_conn") and storage._conn is not None:
                    async with storage._conn.execute(
                        "SELECT COUNT(*) FROM session_record WHERE owner_id = ? AND ended_at IS NULL",
                        (query_owner,),
                    ) as cur:
                        row = await cur.fetchone()
                        sessions_active = row[0] if row else 0
                    if sessions_total == 0:
                        async with storage._conn.execute(
                            "SELECT COUNT(*) FROM session_record WHERE owner_id = ?",
                            (query_owner,),
                        ) as cur:
                            row = await cur.fetchone()
                            sessions_total = row[0] if row else 0
            except Exception as exc:
                errors.append(f"Session stats failed: {exc}")

        brain_nodes = 0
        brain_edges = 0
        if storage is not None:
            try:
                if hasattr(storage, "_conn") and storage._conn is not None:
                    async with storage._conn.execute(
                        """
                        SELECT COUNT(*)
                        FROM brain_graph_node n
                        JOIN brain_graph_index g ON g.id = n.graph_id
                        WHERE g.owner_id = ?
                        """,
                        (query_owner,),
                    ) as cur:
                        row = await cur.fetchone()
                        brain_nodes = row[0] if row else 0
                    async with storage._conn.execute(
                        """
                        SELECT COUNT(*)
                        FROM brain_graph_edge e
                        JOIN brain_graph_index g ON g.id = e.graph_id
                        WHERE g.owner_id = ?
                        """,
                        (query_owner,),
                    ) as cur:
                        row = await cur.fetchone()
                        brain_edges = row[0] if row else 0
            except Exception as exc:
                errors.append(f"Brain graph stats failed: {exc}")

        db_size_bytes = 0
        embedding_count = 0
        db_path_str: str = getattr(plugin.config, "storage_db_path", "cortex.db")
        if hasattr(plugin.config, "db_path_resolved"):
            db_path_str = str(plugin.config.db_path_resolved())
        try:
            if os.path.exists(db_path_str):
                db_size_bytes = os.path.getsize(db_path_str)
            if storage is not None and hasattr(storage, "_conn") and storage._conn is not None:
                async with storage._conn.execute(
                    "SELECT COUNT(*) FROM embedding_index"
                ) as cur:
                    row = await cur.fetchone()
                    embedding_count = row[0] if row else 0
        except Exception as exc:
            errors.append(f"Storage stats failed: {exc}")

        return AdminStatsResponse(
            claims={
                "total": claims_total,
                "by_type": claims_by_type,
                "by_confidence": claims_by_confidence,
            },
            cornerstones={
                "count": cs_count,
                "drift_scores": cs_drift_scores,
            },
            entities={"count": entities_total},
            sessions={"total": sessions_total, "active": sessions_active},
            brain_graph={"nodes": brain_nodes, "edges": brain_edges},
            storage={"db_size_bytes": db_size_bytes, "embedding_count": embedding_count},
            errors=errors,
        )

    # ---- Growth metrics (#767) ─────────────────────────────────────────────
    @router.get(
        f"{PREFIX}/admin/metrics/growth",
        summary="Memory count trending — claims per day for the last 30 days",
        tags=["admin"],
        operation_id="admin_metrics_growth",
    )
    async def admin_metrics_growth(
        days: int = 30,
        _: None = Depends(verify_api_key),
        plugin: Any = Depends(get_plugin),
    ):
        """Return total claim count grouped by day for the last *days* days.

        Surfaces whether memory volume is growing or stagnant.

        Returns::

            {
                "days": 30,
                "data": [
                    {"date": "2026-03-01", "count": 12},
                    ...
                ],
                "total": 123
            }
        """
        from datetime import datetime, timezone, timedelta

        storage = getattr(plugin, "storage", None)
        if storage is None:
            raise HTTPException(status_code=503, detail="Storage unavailable")

        conn = getattr(storage, "_conn", None)
        if conn is None:
            raise HTTPException(status_code=503, detail="Storage connection unavailable")

        owner_id = getattr(getattr(plugin, "config", None), "owner_id", "default")
        cutoff = (datetime.now(timezone.utc) - timedelta(days=days)).isoformat(timespec="seconds")

        try:
            async with conn.execute(
                """
                SELECT date(created_at) AS day, COUNT(*) AS cnt
                FROM semantic_claim
                WHERE owner_id = ?
                  AND created_at >= ?
                GROUP BY day
                ORDER BY day ASC
                """,
                (owner_id, cutoff),
            ) as cur:
                rows = await cur.fetchall()
        except Exception as exc:
            logger.error("admin_metrics_growth query failed: %s", exc, exc_info=True)
            raise HTTPException(status_code=500, detail="Internal server error")

        data = [{"date": row[0], "count": row[1]} for row in rows]
        total = sum(r["count"] for r in data)

        return JSONResponse(content={"days": days, "data": data, "total": total})

    # ---- #824: GET /admin/global-stats ----

    @router.get(
        f"{PREFIX}/admin/global-stats",
        response_model=AdminGlobalStatsResponse,
        summary="Global stats: total claims, by owner, by category, embedding coverage",
        tags=["admin"],
        operation_id="admin_global_stats",
    )
    async def admin_global_stats(
        _: None = Depends(verify_api_key),
        plugin: Any = Depends(get_plugin),
    ) -> AdminGlobalStatsResponse:
        """Return global stats across all owners."""
        errors: List[str] = []
        storage = getattr(plugin, "storage", None)
        if storage is None:
            return AdminGlobalStatsResponse(errors=["Storage not available"])
        total_claims = 0
        claims_by_owner: Dict[str, int] = {}
        claims_by_category: Dict[str, int] = {}
        embedding_count = 0
        embedding_coverage_pct = 0.0
        try:
            rows = await storage.execute_fetchall(
                "SELECT COUNT(*) FROM semantic_claim WHERE (is_deleted = 0 OR is_deleted IS NULL)"
            )
            total_claims = rows[0][0] if rows else 0
            rows = await storage.execute_fetchall(
                "SELECT owner_id, COUNT(*) FROM semantic_claim"
                " WHERE (is_deleted = 0 OR is_deleted IS NULL) GROUP BY owner_id ORDER BY 2 DESC"
            )
            claims_by_owner = {r[0]: r[1] for r in rows}
            rows = await storage.execute_fetchall(
                "SELECT category, COUNT(*) FROM semantic_claim"
                " WHERE (is_deleted = 0 OR is_deleted IS NULL) GROUP BY category ORDER BY 2 DESC"
            )
            claims_by_category = {(r[0] or "uncategorized"): r[1] for r in rows}
            rows = await storage.execute_fetchall(
                "SELECT COUNT(DISTINCT sc.id) FROM semantic_claim sc"
                " INNER JOIN embedding_index ei ON ei.item_type = 'claim' AND ei.item_id = sc.id"
                " WHERE (sc.is_deleted = 0 OR sc.is_deleted IS NULL)"
            )
            embedding_count = rows[0][0] if rows else 0
            if total_claims > 0:
                embedding_coverage_pct = round((embedding_count / total_claims) * 100, 2)
        except Exception as exc:
            errors.append(f"Global stats failed: {exc}")
            logger.error("admin_global_stats: %s", exc, exc_info=True)
        return AdminGlobalStatsResponse(
            total_claims=total_claims, claims_by_owner=claims_by_owner,
            claims_by_category=claims_by_category, embedding_coverage_pct=embedding_coverage_pct,
            total_owners=len(claims_by_owner), errors=errors)
