"""
SharedRingBuffer - High-Performance Ring Buffer for Flight Recorder

This module implements a shared ring buffer for holding logs during a request.
It replaces ThreadLocalLogBuffer to support AsyncIO frameworks (FastAPI) where
requests may jump between threads.

Key Features:
- Shared Storage: Logs persist across thread context switches (Trace ID is the key).
- Ring Buffer: Uses collections.deque(maxlen=N) to automatically drop OLDEST logs.
- Thread Safety: Uses a lock for dictionary operations (create/delete buffer).
- Performance: Deque operations are atomic and highly optimized in CPython.

COMPATIBILITY:
- OTel SDK 1.20-1.38: Buffers LogData wrappers
- OTel SDK 1.39+: Buffers ReadableLogRecord directly
"""

import collections
import threading
from typing import Any, Deque, Dict, List, Optional, Tuple

from clarvynn.logging import get_logger

# Note: We use Any for log records to support both OTel SDK versions
# - OTel 1.20-1.38: LogData
# - OTel 1.39+: ReadableLogRecord/ReadWriteLogRecord

logger = get_logger("log_buffer")

import time


class SharedRingBuffer:
    """
    A thread-safe, shared ring buffer for storing logs associated with active traces.

    Uses a shared dictionary protected by a lock for creation/deletion,
    and collections.deque for efficient, thread-safe ring buffering.

    Includes TTL-based cleanup to prevent memory leaks from stale traces.
    """

    def __init__(
        self, max_logs_per_trace: int = 100, max_active_traces: int = 10000, ttl_seconds: int = 300
    ):
        """
        Initialize the buffer.

        Args:
            max_logs_per_trace: Maximum number of logs to buffer for a single trace.
            max_active_traces: Maximum number of concurrent traces to buffer.
            ttl_seconds: Time-to-live for a trace buffer in seconds (default: 300s / 5m).
                         Buffers not accessed for this duration are cleaned up.
        """
        # Dict[trace_id, Tuple[last_access_time, buffer]]
        # Buffer stores Any to support both LogData (1.20-1.38) and ReadableLogRecord (1.39+)
        self._buffers: Dict[int, Tuple[float, Deque[Any]]] = {}
        self._lock = threading.Lock()
        self._max_logs_per_trace = max_logs_per_trace
        self._max_active_traces = max_active_traces
        self._ttl_seconds = ttl_seconds

        # Cleanup management
        self._op_count = 0
        self._cleanup_interval = 100  # Run cleanup every 100 operations

    def add_log(self, trace_id: int, log_record: Any) -> bool:
        """
        Add a log record to the buffer for the given trace_id.
        """
        if not trace_id:
            return False

        current_time = time.time()

        with self._lock:
            # Periodic cleanup
            self._op_count += 1
            if self._op_count >= self._cleanup_interval:
                self._op_count = 0
                self._cleanup_stale_buffers(current_time)

            if trace_id in self._buffers:
                # Update timestamp
                _, buffer = self._buffers[trace_id]
                self._buffers[trace_id] = (current_time, buffer)
                buffer.append(log_record)
                return True
            else:
                # Create new buffer
                if len(self._buffers) >= self._max_active_traces:
                    return False

                buffer = collections.deque(maxlen=self._max_logs_per_trace)
                buffer.append(log_record)
                self._buffers[trace_id] = (current_time, buffer)
                return True

    def get_and_clear(self, trace_id: int) -> List[Any]:
        """
        Retrieve buffered logs for a trace and clear the buffer.
        """
        if not trace_id:
            return []

        with self._lock:
            if trace_id in self._buffers:
                _, buffer = self._buffers.pop(trace_id)
                return list(buffer)
            return []

    def clear(self, trace_id: int) -> None:
        """
        Clear buffered logs for a trace.
        """
        if not trace_id:
            return

        with self._lock:
            if trace_id in self._buffers:
                del self._buffers[trace_id]

    def get_stats(self) -> Dict[str, int]:
        """
        Get global stats for the buffer.
        """
        with self._lock:
            active_traces = len(self._buffers)
            total_logs = sum(len(buf[1]) for buf in self._buffers.values())

        return {"active_traces": active_traces, "total_buffered_logs": total_logs}

    def _cleanup_stale_buffers(self, current_time: float):
        """
        Remove buffers that haven't been accessed within TTL.
        Must be called with lock held.
        """
        # Identify stale keys
        stale_keys = []
        for tid, (last_access, _) in self._buffers.items():
            if current_time - last_access > self._ttl_seconds:
                stale_keys.append(tid)

        # Remove them
        for tid in stale_keys:
            del self._buffers[tid]

        if stale_keys:
            logger.debug(f"Cleaned up {len(stale_keys)} stale trace buffers")
