#!/usr/bin/env python3
# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
"""Reflex LLM -- Processing core.

Background worker loop and per-item LLM dispatch/write logic.
Runs in a daemon thread started by reflex_llm_queue._ensure_worker_started().
"""
from __future__ import annotations

import hashlib
import logging
import queue as _queue_module
import time
from datetime import datetime, timezone

from pulse.core.brain_io import atomic_update_json
from pulse.core.brain_utils import ANTI_PATTERNS_DIR

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

REFLEX_FILE = ANTI_PATTERNS_DIR / "reflex_patterns.json"
_MAX_ENTRIES = 500

# ---------------------------------------------------------------------------
# LLM extraction prompt
# ---------------------------------------------------------------------------

_REFLEX_LLM_PROMPT = (
    "You are a mistake-detection specialist for a software engineering team. "
    "Analyze this conversation between a user and an AI agent.\n\n"
    "Extract ONLY actionable engineering knowledge. Focus on:\n"
    "1. Implicit debugging narratives (agent discovered something broke but did not say the bug was)\n"
    "2. Architectural decisions with rationale (why something was chosen over alternatives)\n"
    "3. Subtle regressions or side effects mentioned in passing\n"
    "4. Workflow corrections (doing X before Y matters, order-dependent operations)\n"
    "5. Environment-specific gotchas (Windows vs Linux, encoding, path separators)\n"
    "6. Declarative facts: concrete technical truths (API endpoints, config values, file paths, version constraints, data formats)\n\n"
    "ALREADY DETECTED by regex (DO NOT repeat these):\n{regex_hits}\n\n"
    "RULES:\n"
    "- Each item: 30-200 chars, complete sentence, actionable, specific\n"
    "- NO status updates (was completed, has been deployed)\n"
    "- NO vague observations (the code was refactored)\n"
    "- If NO new insights beyond what regex already caught: return empty arrays\n"
    "- Be CONSERVATIVE: only extract items you are confident are genuine engineering knowledge\n\n"
    "OUTPUT -- strict JSON, no markdown fences:\n"
    '{"lessons": ["..."], "failures": ["..."], "patterns": ["..."], "facts": ["..."]}\n\n'
    "Conversation ({source} agent):\n"
    "USER: {user_text}\n"
    "AGENT: {agent_text}\n"
)

# Map LLM output keys -> (pattern_name, confidence)
_CATEGORY_MAP = {
    "failures": ("llm_failure", 0.70),
    "lessons":  ("llm_lesson", 0.65),
    "patterns": ("llm_pattern", 0.65),
    "facts":    ("llm_fact", 0.75),
}


# ---------------------------------------------------------------------------
# Worker loop
# ---------------------------------------------------------------------------

def _worker_loop():
    """Background worker: process queue items sequentially via LLM."""
    # Import here to avoid circular imports at module load time
    from pulse.daemons.bridge.reflex_llm_queue import (
        _queue,
        _rate_limiter,
        _STALENESS_SECONDS,
    )

    while True:
        try:
            item = _queue.get(timeout=30.0)
        except _queue_module.Empty:
            continue

        # Rate limit
        if not _rate_limiter.try_acquire():
            wait = _rate_limiter.seconds_until_available()
            logger.debug("Rate limited, waiting %.0fs", wait)
            try:
                _queue.put_nowait(item)
            except _queue_module.Full:
                pass
            time.sleep(min(wait + 1, 60))
            continue

        # Staleness check
        if time.time() - item["enqueued_at"] > _STALENESS_SECONDS:
            _queue.task_done()
            continue

        try:
            _process_item(item)
        except Exception as e:
            logger.debug("Reflex LLM processing error: %s", e)
        finally:
            _queue.task_done()


# ---------------------------------------------------------------------------
# Per-item processing
# ---------------------------------------------------------------------------

def _process_item(item: dict):
    """Call LLM and write results to reflex_patterns.json."""
    # Check provider availability
    try:
        from pulse.core.provider_state import check_availability
        avail = check_availability()
        if not any(avail.values()):
            logger.debug("No LLM providers available, skipping")
            return
    except Exception:
        return

    # Build dedup context from regex hits
    regex_descriptions = []
    for hit in item.get("regex_hits", []):
        desc = hit.get("description", "")
        if desc:
            regex_descriptions.append(desc)
    regex_context = "\n".join(f"- {d}" for d in regex_descriptions) if regex_descriptions else "None"

    # Build prompt
    prompt = _REFLEX_LLM_PROMPT.format(
        regex_hits=regex_context,
        source=item["source"],
        user_text=item["user_text"],
        agent_text=item["agent_text"],
    )

    # Dispatch via fast cascade
    from pulse.core.llm_router import dispatch, extract_json
    try:
        raw = dispatch(prompt, task_type="fast")
    except RuntimeError:
        return  # all providers failed -- graceful degradation

    parsed = extract_json(raw)

    # Collect valid items
    all_items = []
    for category, (pattern_name, confidence) in _CATEGORY_MAP.items():
        for desc in parsed.get(category, []):
            if isinstance(desc, str) and 30 <= len(desc) <= 200:
                all_items.append((desc, pattern_name, confidence))

    if not all_items:
        return

    # Fuzzy dedup sets
    regex_descs_lower = {d.lower().strip() for d in regex_descriptions}
    regex_word_sets = [set(d.lower().split()) for d in regex_descriptions]

    now_iso = datetime.now(timezone.utc).isoformat()
    source_upper = item["source"].upper()
    written = 0

    for desc, pattern_name, confidence in all_items:
        # Exact dedup vs regex
        if desc.lower().strip() in regex_descs_lower:
            continue

        # Fuzzy dedup: >60% word overlap with any regex hit
        desc_words = set(desc.lower().split())
        skip = False
        for rx_words in regex_word_sets:
            if rx_words and desc_words:
                overlap = len(desc_words & rx_words) / max(len(desc_words), len(rx_words))
                if overlap > 0.6:
                    skip = True
                    break
        if skip:
            continue

        desc_hash = hashlib.md5(desc.lower().strip().encode()).hexdigest()

        def _updater(data, _desc=desc, _hash=desc_hash, _iso=now_iso,
                     _src=source_upper, _pname=pattern_name, _conf=confidence):
            if not isinstance(data, dict):
                data = {"entries": [], "stats": {"total": 0, "last_detection": None}}
            entries = data.setdefault("entries", [])
            stats = data.setdefault("stats", {"total": 0, "last_detection": None})
            # Hash dedup (same as reflex_arc.py)
            if _hash in {e.get("_hash") for e in entries}:
                return data
            seq = stats.get("total", 0) + 1
            entries.append({
                "id": f"RX-{_iso[:10].replace('-', '')}-{seq:03d}",
                "description": _desc,
                "pattern_type": "llm_detected",
                "pattern_name": _pname,
                "source_agent": _src,
                "confidence": _conf,
                "severity": "medium",
                "domain": "general",
                "status": "active",
                "timestamp": _iso,
                "_hash": _hash,
            })
            if len(entries) > _MAX_ENTRIES:
                data["entries"] = entries[-_MAX_ENTRIES:]
            stats["total"] = seq
            stats["last_detection"] = _iso
            return data

        try:
            # P47: SQLite-first write (was JSON-only, unlike reflex_arc.py)
            try:
                from pulse.brain.sqlite_writers import upsert_anti_pattern
                upsert_anti_pattern(
                    description=desc,
                    rule="",
                    reason="LLM-detected",
                    domain="general",
                    source="reflex_llm",
                    keywords=[])
            except Exception:
                pass
            atomic_update_json(REFLEX_FILE, _updater,
                               default={"entries": [], "stats": {"total": 0}})
            written += 1
            # Push to hot cache for instant brain search visibility
            try:
                from pulse.brain.memory.hot_cache import write_hot
                write_hot({"content": desc, "type": pattern_name,
                           "salience": 6.0, "confidence": confidence,
                           "domain": "general", "source": f"reflex_llm_{source_upper}"})
            except Exception:
                pass
            # Facts go to auto_facts.json too for persistent search
            if pattern_name == "llm_fact":
                try:
                    from pulse.brain.persistence.fact_store import store_facts
                    store_facts([{"fact": desc, "fact_type": "reflex_detection",
                                  "confidence": confidence,
                                  "domain": "general", "temporal": "current"}],
                                source=f"reflex_llm_{source_upper}")
                except Exception:
                    pass
        except Exception:
            pass  # never crash the worker

    if written:
        logger.info("Reflex LLM: wrote %d entries from %s", written, source_upper)
    return written
