#!/usr/bin/env python3
# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
"""Reflex LLM — Queue management layer.

Provides _HourlyRateLimiter, enqueue_for_llm(), and _ensure_worker_started().
Imported by reflex_llm.py (public API) and reflex_llm_worker.py (processing core).
"""
from __future__ import annotations

import logging
import queue
import threading
import time

logger = logging.getLogger("pulse.reflex_llm")

# --- Tunables ---
_MAX_CALLS_PER_HOUR = 50
_MIN_INTERACTION_CHARS = 200   # skip trivial exchanges
_QUEUE_MAX_SIZE = 50           # drop oldest if queue saturates
_STALENESS_SECONDS = 600       # 10 min — skip stale items
_TEXT_TRUNCATE = 3000           # max chars per text field in prompt

# --- Module state ---
_queue: queue.Queue = queue.Queue(maxsize=_QUEUE_MAX_SIZE)
_worker_thread = None
_started = False
_lock = threading.Lock()


# ---------------------------------------------------------------------------
# Rate limiter (sliding window, thread-safe)
# ---------------------------------------------------------------------------

class _HourlyRateLimiter:
    """Sliding window rate limiter. Thread-safe."""

    def __init__(self, max_per_hour: int):
        self._max = max_per_hour
        self._timestamps: list[float] = []
        self._lock = threading.Lock()

    def try_acquire(self) -> bool:
        """Return True if a call is allowed, False if rate limit hit."""
        now = time.time()
        cutoff = now - 3600.0
        with self._lock:
            self._timestamps = [t for t in self._timestamps if t > cutoff]
            if len(self._timestamps) >= self._max:
                return False
            self._timestamps.append(now)
            return True

    def seconds_until_available(self) -> float:
        """Seconds until next slot opens. 0 if available now."""
        now = time.time()
        cutoff = now - 3600.0
        with self._lock:
            active = [t for t in self._timestamps if t > cutoff]
            if len(active) < self._max:
                return 0.0
            return active[0] - cutoff


_rate_limiter = _HourlyRateLimiter(_MAX_CALLS_PER_HOUR)


# ---------------------------------------------------------------------------
# Public queue API
# ---------------------------------------------------------------------------

def enqueue_for_llm(
    user_text: str,
    agent_text: str,
    source: str,
    regex_hits: list[dict],
) -> bool:
    """Enqueue an interaction for background LLM analysis.

    Non-blocking. Returns True if enqueued, False if skipped/dropped.
    Called from pulse_bridge._flush_interaction() after check_reflex().
    """
    combined_len = len(user_text or "") + len(agent_text or "")
    if combined_len < _MIN_INTERACTION_CHARS:
        return False

    _ensure_worker_started()

    item = {
        "user_text": (user_text or "")[:_TEXT_TRUNCATE],
        "agent_text": (agent_text or "")[:_TEXT_TRUNCATE],
        "source": source,
        "regex_hits": regex_hits or [],
        "enqueued_at": time.time(),
    }

    try:
        _queue.put_nowait(item)
        return True
    except queue.Full:
        logger.debug("Reflex LLM queue full, dropping interaction")
        return False


def _ensure_worker_started():
    """Start the background worker thread if not already running."""
    global _started, _worker_thread
    if _started:
        return
    with _lock:
        if _started:
            return
        # Import here to avoid circular imports
        from pulse.daemons.bridge.reflex_llm_worker import _worker_loop
        _worker_thread = threading.Thread(
            target=_worker_loop,
            daemon=True,
            name="reflex_llm_worker",
        )
        _worker_thread.start()
        _started = True
        logger.info("Reflex LLM worker thread started")
