"""
cortex/debug.py — Shared debug ring buffer and event logger.

Provides a thread-safe in-memory ring buffer for debug logging.
Two types of entries:
  1. HTTP request/response logs (emitted by middleware)
  2. Pipeline event logs (extraction, retrieval — emitted by pipeline hooks)

All entries are JSON-serializable dicts stored in a single deque.
Memory-only — restarts clear the buffer. That's intentional.

Dependencies: None (stdlib only).
"""

from __future__ import annotations

import threading
import time
from collections import deque
from dataclasses import dataclass, field
from typing import Any, Deque, Dict, List, Optional


@dataclass
class DebugLogEntry:
    """A single debug log entry (HTTP request or pipeline event)."""
    timestamp: float
    event_type: str  # "http_request" | "extraction" | "retrieval"
    data: Dict[str, Any] = field(default_factory=dict)

    def to_dict(self) -> Dict[str, Any]:
        return {
            "timestamp": self.timestamp,
            "event_type": self.event_type,
            **self.data,
        }

    def __getattr__(self, name: str) -> Any:
        """Proxy attribute access into the data dict for flat field access.

        Allows callers to use ``entry.method`` instead of ``entry.data['method']``.
        Only invoked when normal attribute lookup fails (i.e. ``name`` is not
        a dataclass field like ``timestamp``, ``event_type``, or ``data``).
        """
        try:
            return self.data[name]
        except KeyError:
            raise AttributeError(
                f"'DebugLogEntry' has no attribute {name!r} "
                f"(also not found in data dict)"
            )


class DebugRingBuffer:
    """Thread-safe ring buffer for debug log entries.

    Uses collections.deque with maxlen for O(1) append and automatic
    eviction of oldest entries when full.
    """

    def __init__(self, max_entries: int = 10_000) -> None:
        self._buffer: Deque[DebugLogEntry] = deque(maxlen=max_entries)
        self._lock = threading.Lock()
        self._total_count: int = 0  # lifetime counter (survives clears)

    @property
    def max_entries(self) -> int:
        return self._buffer.maxlen or 0

    def append(self, entry: DebugLogEntry) -> None:
        with self._lock:
            self._buffer.append(entry)
            self._total_count += 1

    def clear(self) -> int:
        """Clear the buffer. Returns count of entries cleared."""
        with self._lock:
            count = len(self._buffer)
            self._buffer.clear()
            return count

    def query(
        self,
        *,
        last: int = 50,
        session_id: Optional[str] = None,
        endpoint: Optional[str] = None,
        min_latency_ms: Optional[float] = None,
        event_type: Optional[str] = None,
    ) -> List[Dict[str, Any]]:
        """Query entries with optional filters. Returns newest-first."""
        with self._lock:
            entries = list(self._buffer)

        # Apply filters
        if event_type is not None:
            entries = [e for e in entries if e.event_type == event_type]
        if session_id is not None:
            entries = [e for e in entries if e.data.get("session_id") == session_id]
        if endpoint is not None:
            entries = [e for e in entries if e.data.get("path", "").startswith(endpoint)]
        if min_latency_ms is not None:
            entries = [e for e in entries if e.data.get("latency_ms", 0) >= min_latency_ms]

        # Return newest first, limited
        entries.reverse()
        return [e.to_dict() for e in entries[:last]]

    def stats(self) -> Dict[str, Any]:
        """Compute summary statistics from current buffer contents."""
        with self._lock:
            entries = list(self._buffer)

        total = len(entries)
        if total == 0:
            return {
                "total_entries": 0,
                "lifetime_total": self._total_count,
                "buffer_capacity": self.max_entries,
                "http_requests": 0,
                "pipeline_events": 0,
                "endpoints": {},
                "error_count": 0,
                "extraction_events": 0,
                "retrieval_events": 0,
            }

        http_entries = [e for e in entries if e.event_type == "http_request"]
        extraction_entries = [e for e in entries if e.event_type == "extraction"]
        retrieval_entries = [e for e in entries if e.event_type == "retrieval"]

        # Per-endpoint stats
        endpoint_stats: Dict[str, Dict[str, Any]] = {}
        error_count = 0
        for e in http_entries:
            path = e.data.get("path", "unknown")
            latency = e.data.get("latency_ms", 0)
            status = e.data.get("status", 0)

            if path not in endpoint_stats:
                endpoint_stats[path] = {
                    "count": 0,
                    "total_latency_ms": 0.0,
                    "errors": 0,
                }
            endpoint_stats[path]["count"] += 1
            endpoint_stats[path]["total_latency_ms"] += latency
            if status >= 400:
                endpoint_stats[path]["errors"] += 1
                error_count += 1

        # Compute averages
        for ep_stats in endpoint_stats.values():
            count = ep_stats["count"]
            ep_stats["avg_latency_ms"] = round(ep_stats["total_latency_ms"] / count, 2) if count else 0
            del ep_stats["total_latency_ms"]

        # Extraction success rate
        extraction_success = sum(
            1 for e in extraction_entries if e.data.get("claims_stored", 0) > 0
        )
        extraction_success_rate = (
            round(extraction_success / len(extraction_entries), 4)
            if extraction_entries else None
        )

        return {
            "total_entries": total,
            "lifetime_total": self._total_count,
            "buffer_capacity": self.max_entries,
            "http_requests": len(http_entries),
            "pipeline_events": len(extraction_entries) + len(retrieval_entries),
            "endpoints": endpoint_stats,
            "error_count": error_count,
            "extraction_events": len(extraction_entries),
            "retrieval_events": len(retrieval_entries),
            "extraction_success_rate": extraction_success_rate,
        }

    def __len__(self) -> int:
        with self._lock:
            return len(self._buffer)

    def __iter__(self):
        """Iterate over entries (snapshot, newest-last order)."""
        with self._lock:
            snapshot = list(self._buffer)
        return iter(snapshot)

    @property
    def maxlen(self) -> Optional[int]:
        """Expose the underlying deque maxlen for test/introspection."""
        return self._buffer.maxlen


# ---------------------------------------------------------------------------
# Global singleton — importable from anywhere in the codebase
# ---------------------------------------------------------------------------
_global_buffer: Optional[DebugRingBuffer] = None
_global_enabled: bool = False


def get_debug_buffer() -> DebugRingBuffer:
    """Get or create the global debug ring buffer."""
    global _global_buffer
    if _global_buffer is None:
        _global_buffer = DebugRingBuffer()
    return _global_buffer


def init_debug_logging(*, enabled: bool = False, max_entries: int = 10_000) -> DebugRingBuffer:
    """Initialize the global debug logging system.

    Called once at startup from http_server lifespan.
    """
    global _global_buffer, _global_enabled
    _global_enabled = enabled
    _global_buffer = DebugRingBuffer(max_entries=max_entries)
    return _global_buffer


def is_debug_enabled() -> bool:
    """Check if debug logging is enabled."""
    return _global_enabled


def set_debug_enabled(enabled: bool) -> None:
    """Toggle debug logging at runtime."""
    global _global_enabled
    _global_enabled = enabled


# ---------------------------------------------------------------------------
# Convenience loggers for pipeline events
# ---------------------------------------------------------------------------

def log_extraction_event(
    *,
    session_id: str = "",
    claims_extracted: int = 0,
    claims_stored: int = 0,
    entities_resolved: int = 0,
    noise_filtered: int = 0,
) -> None:
    """Log an extraction pipeline event to the debug buffer."""
    if not _global_enabled:
        return
    buf = get_debug_buffer()
    buf.append(DebugLogEntry(
        timestamp=time.time(),
        event_type="extraction",
        data={
            "session_id": session_id,
            "claims_extracted": claims_extracted,
            "claims_stored": claims_stored,
            "entities_resolved": entities_resolved,
            "noise_filtered": noise_filtered,
        },
    ))


def log_retrieval_event(
    *,
    session_id: str = "",
    query: str = "",
    mode: str = "",
    items_returned: int = 0,
    top_score: float = 0.0,
    tokens_used: int = 0,
) -> None:
    """Log a retrieval pipeline event to the debug buffer."""
    if not _global_enabled:
        return
    buf = get_debug_buffer()
    buf.append(DebugLogEntry(
        timestamp=time.time(),
        event_type="retrieval",
        data={
            "session_id": session_id,
            "query": query[:500],
            "mode": mode,
            "items_returned": items_returned,
            "top_score": round(top_score, 4),
            "tokens_used": tokens_used,
        },
    ))
