"""
cortex/api/debug_log.py — Spec-compliant debug log ring buffer (#453).

Provides:
  - ``DebugEntry``  — dataclass capturing per-request trace fields
  - ``DebugLog``    — thread-safe FIFO ring buffer wrapping the canonical
                      ``cortex.debug.DebugRingBuffer`` singleton

The canonical in-memory store lives in ``cortex.debug`` (single process-wide
singleton).  This module provides:

  1. The ``DebugEntry`` dataclass with the spec fields (method, endpoint,
     status_code, latency_ms, session_id, owner_id, …).
  2. ``DebugLog`` — a thin wrapper around the canonical ring buffer exposing
     the ``log / query / stats`` API defined in #453.
  3. Backward-compatible helpers (``DebugLogEntry``, ``add_entry``,
     ``get_entries``) so ``RequestTracerMiddleware`` keeps working without
     changes.

Writing through ``add_entry`` / ``DebugLog.log`` always writes to the same
canonical buffer that ``GET /api/v1/debug/logs`` reads from — no split-buffer
bugs.

Thread-safety notes:
  - ``collections.deque(maxlen=N)`` append is atomic under the CPython GIL.
  - ``DebugRingBuffer`` additionally wraps operations in a ``threading.Lock``
    for correctness on non-CPython runtimes.
  - No disk I/O; purely in-memory.

Memory footprint (rough):
  - Each ``DebugEntry`` ≈ 1–2 KB (two 500-char string fields + scalars).
  - 10 000 entries ≈ 10–20 MB worst-case.
"""

from __future__ import annotations

import os
import time
from dataclasses import asdict, dataclass
from typing import Any, Dict, List, Optional


# ---------------------------------------------------------------------------
# Spec-compliant DebugEntry (#453)
# ---------------------------------------------------------------------------

@dataclass
class DebugEntry:
    """A single request/response trace entry.

    Fields are capped at 500 characters for string values to prevent memory
    bloat (request_summary / response_summary).  Sensitive headers (auth,
    API keys) are never stored.
    """

    timestamp: str          # ISO 8601 (UTC)
    method: str             # GET / POST / PUT / DELETE …
    endpoint: str           # /api/v1/memories/remember
    status_code: int        # 200, 400, 500 …
    latency_ms: float       # total round-trip time
    session_id: Optional[str]       # extracted from request body when present
    owner_id: Optional[str]         # from auth middleware (request.state.owner_id)
    request_summary: str            # truncated request body (≤ 500 chars)
    response_summary: str           # truncated response body (≤ 500 chars)
    extraction_count: Optional[int] # claims extracted (remember endpoint)
    retrieval_count: Optional[int]  # items returned  (retrieve endpoint)
    error: Optional[str]            # error message if status_code >= 400

    def to_dict(self) -> Dict[str, Any]:
        return asdict(self)


# ---------------------------------------------------------------------------
# DebugLog — thin wrapper around the canonical cortex.debug singleton
# ---------------------------------------------------------------------------

class DebugLog:
    """In-memory FIFO ring buffer for request/response tracing.

    Stores the last N request traces for real-time debugging via API.
    Thread-safe, no persistence (memory only, lost on restart).

    Delegates storage to the process-wide ``cortex.debug.DebugRingBuffer``
    singleton so all writers (middleware, pipeline hooks) share one buffer.
    """

    def __init__(self, max_entries: int = 10_000) -> None:
        self._max_entries = max_entries
        # Lazy import to avoid circular deps at module load time
        self._buf = None  # type: ignore[assignment]

    def _get_buf(self):
        """Return the canonical ring buffer, creating it if needed."""
        if self._buf is None:
            try:
                from cortex.debug import get_debug_buffer
                self._buf = get_debug_buffer()
            except Exception:
                # Fallback: bare deque (shouldn't happen in normal usage)
                import collections
                self._buf = collections.deque(maxlen=self._max_entries)
        return self._buf

    def log(self, entry: DebugEntry) -> None:
        """Append a DebugEntry to the ring buffer."""
        _buf = self._get_buf()
        try:
            # Write through the canonical DebugRingBuffer.append() so the
            # lock and lifetime counter stay consistent.
            from cortex.debug import DebugLogEntry as _DLE
            _buf.append(_DLE(
                timestamp=time.time(),
                event_type="http_request",
                data={
                    "method": entry.method,
                    "path": entry.endpoint,
                    "status": entry.status_code,
                    "latency_ms": entry.latency_ms,
                    "session_id": entry.session_id,
                    "owner_id": entry.owner_id or "",
                    "request_body_summary": entry.request_summary,
                    "response_body_summary": entry.response_summary,
                    "extraction_count": entry.extraction_count,
                    "retrieval_count": entry.retrieval_count,
                    "error": entry.error,
                },
            ))
        except Exception:
            # If cortex.debug is unavailable, fall back to bare deque
            if hasattr(_buf, "append"):
                _buf.append(entry)

    def query(
        self,
        last: int = 50,
        session_id: Optional[str] = None,
        endpoint: Optional[str] = None,
        min_latency_ms: Optional[float] = None,
    ) -> List[Dict[str, Any]]:
        """Return matching entries (newest first)."""
        _buf = self._get_buf()
        try:
            return _buf.query(
                last=last,
                session_id=session_id,
                endpoint=endpoint,
                min_latency_ms=min_latency_ms,
                event_type="http_request",
            )
        except Exception:
            return []

    def stats(self) -> Dict[str, Any]:
        """Return aggregate stats from the ring buffer."""
        _buf = self._get_buf()
        try:
            return _buf.stats()
        except Exception:
            return {"error": "stats unavailable"}

    def __len__(self) -> int:
        _buf = self._get_buf()
        try:
            return len(_buf)
        except Exception:
            return 0


# ---------------------------------------------------------------------------
# Module-level singleton
# ---------------------------------------------------------------------------

_DEFAULT_SIZE = 10_000
try:
    _MAX_SIZE = int(os.getenv("CORTEX_DEBUG_LOG_SIZE", str(_DEFAULT_SIZE)))
    if _MAX_SIZE < 1:
        _MAX_SIZE = _DEFAULT_SIZE
except (ValueError, TypeError):
    _MAX_SIZE = _DEFAULT_SIZE

_debug_log: Optional[DebugLog] = None


def get_debug_log() -> DebugLog:
    """Get or create the module-level DebugLog instance."""
    global _debug_log
    if _debug_log is None:
        _debug_log = DebugLog(max_entries=_MAX_SIZE)
    return _debug_log


# ---------------------------------------------------------------------------
# Backward-compatible API (used by RequestTracerMiddleware et al.)
# ---------------------------------------------------------------------------

@dataclass
class DebugLogEntry:
    """Backward-compatible entry type used by RequestTracerMiddleware.

    Kept for callers that haven't migrated to DebugEntry yet.
    Writes through to the canonical cortex.debug ring buffer via add_entry().
    """

    request_id: str
    method: str
    path: str
    status: int
    latency_ms: float
    owner_id: str
    timestamp: float


def get_debug_buffer():
    """Return the canonical ring buffer (cortex.debug singleton)."""
    try:
        from cortex.debug import get_debug_buffer as _gdb
        return _gdb()
    except Exception:
        import collections
        return collections.deque(maxlen=_MAX_SIZE)


def clear_debug_buffer() -> None:
    """Clear all entries from the canonical ring buffer."""
    try:
        from cortex.debug import get_debug_buffer as _gdb
        _gdb().clear()
    except Exception:
        pass


def add_entry(entry: DebugLogEntry) -> None:
    """Write a DebugLogEntry to the canonical cortex.debug ring buffer.

    This replaces the old module-level deque write so that
    RequestTracerMiddleware entries are visible at GET /api/v1/debug/logs.
    """
    try:
        from cortex.debug import DebugLogEntry as _DLE, get_debug_buffer as _gdb
        _gdb().append(_DLE(
            timestamp=entry.timestamp,
            event_type="http_request",
            data={
                "request_id": entry.request_id,
                "method": entry.method,
                "path": entry.path,
                "status": entry.status,
                "latency_ms": entry.latency_ms,
                "owner_id": entry.owner_id,
            },
        ))
    except Exception:
        pass


def get_entries(
    limit: int = 20,
    owner_id: Optional[str] = None,
    min_latency_ms: Optional[float] = None,
) -> List[DebugLogEntry]:
    """Backward-compatible filtered entry retrieval.

    Returns DebugLogEntry objects rebuilt from the canonical buffer.
    """
    try:
        from cortex.debug import get_debug_buffer as _gdb
        buf = _gdb()
        raw = buf.query(
            last=limit,
            min_latency_ms=min_latency_ms,
            event_type="http_request",
        )
        results = []
        for r in raw:
            if owner_id is not None and r.get("owner_id", "") != owner_id:
                continue
            results.append(DebugLogEntry(
                request_id=r.get("request_id", ""),
                method=r.get("method", ""),
                path=r.get("path", ""),
                status=r.get("status", 0),
                latency_ms=r.get("latency_ms", 0.0),
                owner_id=r.get("owner_id", ""),
                timestamp=r.get("timestamp", 0.0),
            ))
        return results
    except Exception:
        return []


# ---------------------------------------------------------------------------
# FastAPI route (legacy admin/debug-log endpoint — kept for backward compat)
# ---------------------------------------------------------------------------

try:
    from fastapi import APIRouter, Depends
    from cortex.api.deps import PREFIX

    router = APIRouter()

    def _make_routes(verify_api_key: Any) -> None:
        """Register the legacy debug-log admin endpoint."""

        @router.get(
            f"{PREFIX}/admin/debug-log",
            summary="In-memory request debug log (legacy)",
            tags=["admin"],
            operation_id="admin_get_debug_log_legacy",
        )
        async def admin_get_debug_log(
            limit: int = 20,
            owner_id: Optional[str] = None,
            min_latency_ms: Optional[float] = None,
            _: None = Depends(verify_api_key),
        ) -> Dict[str, Any]:
            """Return recent entries from the in-memory request ring buffer."""
            effective_limit = max(1, min(limit, _MAX_SIZE))
            entries = get_entries(
                limit=effective_limit,
                owner_id=owner_id,
                min_latency_ms=min_latency_ms,
            )
            return {
                "entries": [asdict(e) for e in entries],
                "total": len(entries),
                "buffer_size": _MAX_SIZE,
            }

except ImportError:
    router = None  # type: ignore[assignment]

    def _make_routes(verify_api_key: Any) -> None:  # type: ignore[misc]
        pass
